了解嵌套 Spring @Transactional 的工作原理

2024-01-09

我正在将 EJB 应用程序移植到 Spring,但遇到了一些问题。 该应用程序使用 eclipselink 独立运行(这就是我们选择 spring 的原因)。

在此应用程序中,我需要创建一个订单,为此我首先需要创建一个客户、OrderLines,然后为此订单添加付款。

问题是我想在单个事务中完成所有插入,这样如果付款无法持久化,则不必保留任何内容。我试图实现这一目标,但看起来我正在跨越多个独立事务,因为如果发生故障,数据会持久保存到数据库(例如:付款失败,无论如何都会创建客户)。

这是入口点:

public static void main(String[] args) {
    AbstractApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
    BeanFactory beanFactory = (BeanFactory) context;
    MyService service = beanFactory.getBean(MyService.class);
    service.getNewOrders(true);
}

这是我正在解析的bean(使用beanFactory.getBean) :

@Component
@Scope("prototype")
public class MyService {

    @Autowired
    private CustomerService customerService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentService paymentService;

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public void getNewOrders(boolean formattedOutput) {
        try {
            List<RawData> rawData = // Acquire data from a remote web service (http rest based)

            for (RawData data : rawData) {
                try {
                    this.handleOrder(data);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private Order handleOrder(RawData rawData) throws Exception {
        Customer customer = new Customer();
        // Fill customer with rawData
        this.customerService.create(customer);

        Order order = new Order();
        order.setCustomer(customer);
        // Fill order with rawData
        this.orderService.create(order);

        Payment payment = new Payment();
        payment.setOrder(order);
        // Fill payment with rawData
        this.paymentService.create(payment);

        return order;
    }
}

每个服务如下所示:

@Service
@Transactional
public class CustomerService {

    @Autowired
    private CustomerDao customerDao;

    public void create(Customer customer) {
        // some works on customer fields (checking values etc)
        this.customerDao.create(customer);
    }
}

这些都是由 Dao 支持的:

@Repository
public class CustomerDao {

    @PersistenceContext
    protected EntityManager em;

    public void create(Customer customer) {
        this.em.persist(customer);
    }
}

以下是 maven pom.xml 的一些依赖项:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>com.jolbox</groupId>
        <artifactId>bonecp</artifactId>
        <version>0.8.0-rc1</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.25</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>eclipselink</artifactId>
        <version>2.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>javax.persistence</artifactId>
        <version>2.0.3</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
        <version>2.3.2</version>
    </dependency>

这是 persistence.xml :

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <shared-cache-mode>NONE</shared-cache-mode>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/db" />
            <property name="javax.persistence.jdbc.user" value="root" />
            <property name="javax.persistence.jdbc.password" value="" />
        </properties>
    </persistence-unit>
</persistence>

弹簧配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

    <bean id="myDataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db" />
        <property name="username" value="root" />
        <property name="password" value="" />
    </bean>

    <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="myDataSource" />
        <property name="jpaPropertyMap">
            <map>
                <entry key="eclipselink.weaving" value="false" />
            </map>
        </property>
    </bean>

    <bean id="myTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="myEmf" />
    </bean>

    <tx:annotation-driven transaction-manager="myTxManager" />

    <context:component-scan base-package="com.application" />

</beans>

EDIT

添加了从 main 中创建 ApplicationContext 以及从 MyService.java 中省略的方法


既然服务方法是私有的,那么如何调用它呢?它应该是公开的。私有方法不能被 Spring 事务拦截器拦截,也不能被 CGLib 动态代理覆盖。

编辑:好的,这是常见的问题。您正在从 main 方法调用该方法getNewOrders(),这是公开的和交易的。 Spring拦截器拦截该方法调用。由于没有事务并且该方法被标记为 SUPPORTS,因此 Spring 不会启动任何事务。

然后这个方法调用私有的handleOrder()方法。注意,不仅方法是私有的,这使得Spring无法拦截方法调用,而且调用是从组件中的方法到同一组件中的方法。因此,即使该方法是公共的,Spring也无法拦截该方法的调用。事务处理是基于代理的:

method --> transactional proxy --> Spring bean 1 --> transactional proxy --> Spring bean2

在这种情况下,由于您没有调用另一个组件的方法,因此没有代理拦截,并且不会启动任何事务。

method --> transactional proxy --> Spring bean 1 --> transactional proxy --> Spring bean2
                                      ^   |
                                      |___|

所以,你要做的是

  • 创建另一个 Spring bean 'OrderHandler,例如)
  • 将handleOrder()方法移至此Spring bean,并将其公开
  • 在 MyService 中注入 OrderHandler

它会工作得很好。

这在Spring文档 http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/htmlsingle/#tx-decl-explained.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

了解嵌套 Spring @Transactional 的工作原理 的相关文章

随机推荐