Spring Transaction Management Practical Use Cases

       This is an actual business scenario encountered in the past two days. It is a rare opportunity to combine the knowledge of spring transaction management with the actual business scenario to generate a certain business value and record it. The following will give the solution directly, and then give the attention points when using transaction management.

What you need to know: Detailed introduction and simple examples of spring transaction management

Business scenario: Synchronize Amazon platform orders according to the customer dimension (by calling amazon mws api).

Configuration of spring transaction manager:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
	   http://www.springframework.org/schema/tx
	   http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
	   http://www.springframework.org/schema/aop
	   http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
	   http://www.springframework.org/schema/context
	   http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    <context:component-scan base-package="com.best.global.glink.dao"/>

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/jdbc/glink"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
        <constructor-arg index="1" value="REUSE"/>
    </bean>

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="txPointcut"
                      expression="(execution (* com.best.global.glink.service..*.*(..))) || (execution (* com.best.global.glink.facade..*.*(..)))"/>
        <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
    </aop:config>

</beans>

       From this configuration, it can be seen that the service and facade layers open the default transaction through spring aop, and the transaction propagation type is REQUIRED (this transaction type indicates that if there is a current transaction, the currently existing transaction is used, otherwise a new transaction is opened. ), the transaction manager uses JDBC's transaction manager (this is important because it can use NESTED level transaction propagation behavior).

 before fixing:

        Synchronization method: All clients complete the synchronization operation in one transaction (the same service), the code is as follows:

@Override
    public Boolean syncAmazonOrders(Date fromTime, Date toTime) throws DatatypeConfigurationException {
        if (fromTime == null || toTime == null || toTime.before(fromTime)) {
            BizLogger.syserror("From time: " + fromTime + ", to time: " + toTime);
            return false;
        }
        //so far, we just have Amazon sales channel, so all means amazon.
        List<CustomerSalesChannelVO> channels = customerService.getAllCustomerSalesChannel();
        if (channels == null) {
            return false;
        }
        for (CustomerSalesChannelVO channel : channels) {
            logger.info("Customer info: {}.", channel);
            try {
                syncAmazonOrders(channel, fromTime, toTime);
            } catch (Exception e) {
                BizLogger.syserror(String.format("Amazon order sync error, CustomerCode : %s", channel.getCustomerCode()), e);
            }
        }
        return true;
    }

        Disadvantages: The synchronization of different customer orders will affect each other. In fact, the most encountered situation is: the amazon mws api verification information of some customers is not updated in time, resulting in an error when calling the api, and the transaction is marked as rollbackonly.

 After modification:

        Since the service layer has a default transaction, and the synchronization of amazon order is triggered by a job, the loop in the above syncAmazonOrders method can be moved out to achieve multi-transaction synchronization of orders of different customers.

@Override
    public ProcessResult execute(BestJobAdapter jobAdapter) throws Exception {
        /************      
        /*other codes
        /************
        for (CustomerSalesChannelVO channel : channels) {
            doSyncAmazonOrders(channel, fromTime, toTime);
        }
        return new ProcessResult(result, "Successfully sync amazon order.");
    }

    private void doSyncAmazonOrders(CustomerSalesChannelVO channel, Date fromTime, Date toTime) {
        logger.info("Customer info: {}.", channel);
        try {
            amazonOrderService.syncAmazonOrders(channel, fromTime, toTime);
        } catch (Exception e) {
            BizLogger.syserror(String.format("Amazon order sync error, CustomerCode : %s", channel.getCustomerCode()), e);
        }
    }

The actual test passed!

Let’s talk about some other practices and existing problems during the adjustment process:

         Add a method to the class where the syncAmazonOrders method is located to manually open the transaction:

/**
     * 此处事务不生效
     * 目前事务切点为service层,所以syncAmazonOrders本身带有事务
     * 此处doSyncAmazonOrders开启嵌套事务,事务传播级别Propagation.NESTED,子事务的失败不会影响到外部事务的整体提交,外部事务的失败会回滚所有子事务
     * 此处其实最好能够使用外部事务与内部事务互不影响的事务传播级别PROPAGATION_REQUIRES_NEW,但其需要使用JtaTransactionManager作为事务管理器,而我们目前使用的是DataSourceTransactionManager
     * 此处由于按照客户的维度已经区分开数据,因此不同的事务不会操作到相同的数据,不需要设置事务的隔离级别
     */
    @Transactional(propagation = Propagation.NESTED)
    private void doSyncAmazonOrders(CustomerSalesChannelVO channel, Date fromTime, Date toTime) {
        logger.info("Customer info: {}.", channel);
        try {
            syncAmazonOrders(channel, fromTime, toTime);
        } catch (Exception e) {
            BizLogger.syserror(String.format("Amazon order sync error, CustomerCode : %s", channel.getCustomerCode()), e);
        }
    }

        I also wrote a lot of notes here, and finally found that the affairs here did not take effect.

        Reason: JDK's built-in Proxy dynamic proxy can dynamically generate bytecode at runtime without having to write proxy classes for each class. In the middle, an interface InvocationHandler and Proxy.newProxyInstance static method are mainly used. The parameters are described as follows: There is a problem with using the built-in Proxy to implement dynamic proxy: the class to be proxied must implement the interface. If the interface is not implemented, the dynamic proxy cannot be completed. Here, different methods in the same class call each other, and the called method will not start new things.

expand:

The difference between Spring's two proxy JDK and CGLIB:

        Java dynamic proxy uses the reflection mechanism to generate an anonymous class that implements the proxy interface, and calls InvokeHandler to process it before calling the specific method. The cglib dynamic proxy uses the asm open source package, loads the class file of the proxy object class, and generates subclasses by modifying its bytecode.

1. If the target object implements the interface, the dynamic proxy of the JDK will be used to implement AOP by default. 
2. If the target object implements the interface, you can force the use of CGLIB to implement AOP. 

3. If the target object does not implement the interface, the CGLIB library must be used, and spring will automatically convert between the JDK dynamic proxy and CGLIB

How to force AOP to be implemented using CGLIB?
 (1) Add the CGLIB library, SPRING_HOME/cglib/*.jar
 (2) Add <aop:aspectj-autoproxy proxy-target-class="true"/> to the spring configuration file


Difference between JDK dynamic proxy and CGLIB bytecode generation?
 (1) JDK dynamic proxy can only generate proxy for the class that implements the interface, but not for the class
 (2) CGLIB implements the proxy for the class, mainly to generate a subclass for the specified class, and override the method
   because it is inheritance, So it is best not to declare the class or method as final 

Reference: On the difference between Spring's two proxies JDK and CGLIB

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325445192&siteId=291194637