Spring事務管理實際使用案例

       這是近兩天實際遇到的業務場景,可貴有機會將spring事務管理的知識與實際業務場景相結合,產生必定的業務價值,遂記錄。如下會直接給出解決方案,而後給出使用事務管理時的注意點。java

您需瞭解:spring事務管理詳細介紹與簡單實例spring

業務場景:按照客戶的維度去同步Amazon平臺訂單(by calling amazon mws api)。sql

spring事務管理器的配置:express

<?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>

       由此配置能夠看出,service及facade層經過spring aop的方式開啓了默認事務,且事務傳播類型爲REQUIRED(該事務類型表示,若是當前存在事務則使用當前已存在的事務,不然新開啓一個事務),事務管理器使用JDBC的事務管理器(這一點很重要,由於它可使用NESTED級別的事務傳播行爲)。api

 修改前:mybatis

        同步方式:全部客戶均在一個事務中去完成同步操做(同一個service),代碼以下:ide

@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;
    }

        缺點:不一樣客戶訂單的同步會彼此影響,實際遇到最多的狀況是:部分客戶的amazon mws api驗證信息更新不及時致使調用api時出錯,事務被標記爲rollbackonly。測試

 修改後:spa

        因爲service層有默認的事務,且amazon order的同步是有Job觸發,所以將上述syncAmazonOrders方法中的循環外移便可實現多事務同步不一樣客戶的訂單。.net

@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);
        }
    }

實際測試經過!

如下再談談在調整過程當中嘗試的一些其餘作法及存在的問題:

         在syncAmazonOrders方法所在類中添加方法手工開啓事務:

/**
     * 此處事務不生效
     * 目前事務切點爲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);
        }
    }

        此處還洋洋灑灑地寫了一大片註釋,最後發現這裏的事務並無生效。

        緣由:JDK內置的Proxy動態代理能夠在運行時動態生成字節碼,而不必針對每一個類編寫代理類。中間主要使用到了一個接口InvocationHandler與Proxy.newProxyInstance靜態方法,參數說明以下:使用內置的Proxy實現動態代理有一個問題:被代理的類必須實現接口,未實現接口則沒辦法完成動態代理。這裏在同一個類裏不一樣方法的相互調用,被調用的方法是不會開啓新事物的。

拓展:

Spring的兩種代理JDK和CGLIB的區別:

        java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,經過修改其字節碼生成子類來處理。

一、若是目標對象實現了接口,默認狀況下會採用JDK的動態代理實現AOP 
二、若是目標對象實現了接口,能夠強制使用CGLIB實現AOP 

三、若是目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

如何強制使用CGLIB實現AOP?
 (1)添加CGLIB庫,SPRING_HOME/cglib/*.jar
 (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>


JDK動態代理和CGLIB字節碼生成的區別?
 (1)JDK動態代理只能對實現了接口的類生成代理,而不能針對類
 (2)CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法
   由於是繼承,因此該類或方法最好不要聲明成final 

參考資料:Spring的兩種代理JDK和CGLIB的區別淺談

相關文章
相關標籤/搜索