Spring事務的配置、參數詳情及其原理介紹(Transactional)


  Spring 事務管理分為編程式和聲明式的兩種方式。編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。

            

  聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於 @Transactional 注解的方式。

  需要明確幾點:

  1、默認配置下 Spring 只會回滾運行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。
  2、@Transactional 注解只能應用到 public 方法才有效。

  3、@Transactional 注解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。然而僅僅 @Transactional 注解的出現不足以開啟事務行為,它僅僅是一種元數據,能夠被可以識別 @Transactional 注解和上述的配置適當的具有事務行為的beans所使用。其實是 <tx:annotation-driven/>元素的出現開啟了事務行為。

  4、注解不可以繼承,建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。當然可以在接口上使用 @Transactional 注解,但是這將只有當你設置了基於接口的代理時它才生效。

  Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSourceTransactionManager代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。

  DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際為SessionFactory,TransactionManager的實現為HibernateTransactionManager。

  具體如下圖:

   事務的TransactionManager事務管理器總共有5種,與DataSource關聯關系如圖所示:

   根據代理機制的不同,總結了五種Spring事務的配置方式,配置文件如下:

一、Spring事務配置方式

  1、XML配置每個Bean都有一個代理

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定義事務管理器(聲明式的事務) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="userDao"  
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
           <!-- 配置事務管理器 -->  
        <property name="transactionManager" ref="transactionManager" />     
        <property name="target" ref="userDaoTarget" />  
        <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事務屬性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>  
        </property>  
    </bean>  
</beans>

  2、XML所有Bean共享一個代理基類:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定義事務管理器(聲明式的事務) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
   <!-- 此處所有transactionBase所有Bean共用 -->
<bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true"> <!-- 配置事務管理器 --> <property name="transactionManager" ref="transactionManager" /> <!-- 配置事務屬性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- 配置DAO --> <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userDao" parent="transactionBase" > <property name="target" ref="userDaoTarget" /> </bean> </beans>

  3、XML配置攔截器:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定義事務管理器(聲明式的事務) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 
   
    <bean id="transactionInterceptor"  
        class="org.springframework.transaction.interceptor.TransactionInterceptor">  
        <property name="transactionManager" ref="transactionManager" />  
        <!-- 配置事務屬性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>  
            </props>  
        </property>  
    </bean>
      
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
        <property name="beanNames">  
            <list>  
                <value>*Dao</value>
            </list>  
        </property>  
        <property name="interceptorNames">  
            <list>  
                <value>transactionInterceptor</value>  
            </list>  
        </property>  
    </bean>  
  
    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

  四、XML使用tx標簽配置AOP切面:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
 
    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定義事務管理器(聲明式的事務) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
 
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />        
    </aop:config>      
</beans>

  五、全注解方式:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
 
    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />
 
    <tx:annotation-driven transaction-manager="transactionManager"/>
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定義事務管理器(聲明式的事務) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
</beans>

  或者SpringBoot在Application啟動類通過注解開啟事務管理:

@EnableTransactionManagement // 啟注解事務管理,等同於xml配置方式的 <tx:annotation-driven />

  然后在具體的DAO類中添加注解@Transaction

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
 
    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }
}

二、Spring事務屬性配置(注解示例)

  我們在使用Spring聲明式事務時,有一個非常重要的概念就是事務屬性。事務屬性通常由事務的傳播行為,事務的隔離級別,事務的超時值和事務只讀標志組成。我們在進行事務划分時,需要進行事務定義,也就是配置事務的屬性。 

  Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。

TransactionDefinition  
public interface TransactionDefinition {  
    int getPropagationBehavior();   //事務的傳播性
    int getIsolationLevel();   //事務的隔離性 
    int getTimeout();   //超時
    boolean isReadOnly();    //只讀
} 

  1、事務的傳播性

  getPropagationBehavior()返回事務的傳播行為,由是否有一個活動的事務來決定一個事務調用。 在TransactionDefinition接口中定義了七個事務傳播行為。

   配置方式:

@Transactional(propagation=Propagation.REQUIRED)    //默認方式,跟@Transactional同效果

PROPAGATION_REQUIRED 表示業務方法需要在一個事務中處理,如果業務方法執行時已經在一個事務中,則加入該事務,否則重新開啟一個事務。這也是默認的事務傳播行為;

PROPAGATION_SUPPORTS 該屬性指定,如果業務方法在一個既有的事務中進行,則加入該事務;否則,業務方法將在一個沒有事務的環境下進行;但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。 

PROPAGATION_MANDATORY 該屬性指定業務方法只能在一個已經存在的事務中進行,業務方法不能發起自己的事務;如果業務方法沒有在一個既有的事務中進行,容器將拋出異常; 

PROPAGATION_REQUIRES_NEW 表明業務方法需要在一個單獨的事務中進行,如果業務方法進行時已經在一個事務中,則這個事務被掛起,並重新開啟一個事務來執行這個業務方法,業務方法執行完畢后,原來的事務恢復進行;

PROPAGATION_NOT_SUPPORTED  總是非事務地執行,並掛起任何存在的事務。 

PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常。

PROPAGATION_NESTED 該屬性指定,如果業務方法在一個既有的事務中執行,則該業務方法將在一個嵌套的事務中進行;否則,按照TransactionDefinition.PROPAGATION_REQUIRED來對待。它使用一個單獨的事務,這個事務可以有多個rollback點,內部事務的rollback對外部事務沒有影響,但外部事務的rollback會導致內部事務的rollback。這個行為只對DataSourceTransactionManager有效。

  1.1、PROPAGATION_REQUIRED  

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    ……
    methodB();
    …… 
} 
//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodB{
    …… 
}

  單獨調用MethodB時,Spring保證在methodB方法中所有的調用都獲得到一個相同的連接。在調用methodB時,沒有一個存在的事務,所以獲得一個新的連接,開啟了一個新的事務。 

  調用MethodA時,環境中沒有事務,所以開啟一個新的事務. 當在MethodA中調用MethodB時,環境中已經有了一個事務,所以methodB就加入當前事務。 相當於把B方法直接移到A方法中,同時回滾或者同時提交。

  1.2 PROPAGATION_SUPPORTS

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    ……
    methodB();
    …… 
} 
//事務屬性 PROPAGATION_SUPPORTS
@Transactional(propagation=Propagation.SUPPORTS)
methodB{
    …… 
}

  單獨調用MethodB時,methodB方法是非事務的執行的。  

  調用MethodA時,methodB則加入到了methodA的事務中,事務地執行。 

  1.3 PROPAGATION_MANDATORY

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    ……
    methodB();
    …… 
} 
//事務屬性 PROPAGATION_MANDATORY
@Transactional(propagation=Propagation.MANDATORY)
methodB{
    …… 
}

  當單獨調用methodB時,因為當前沒有一個活動的事務,則會拋出異常 throw new IllegalTransactionStateException("Transactionpropagation 'mandatory' but no existing transaction found"); 

  當調用methodA時,methodB則加入到methodA的事務中,事務地執行。

  1.4 PROPAGATION_REQUIRES_NEW

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事務屬性 PROPAGATION_REQUIRES_NEW
@Transactional(propagation=Propagation.REQUIRES_NEW)
methodB{
    …… 
}

  當單獨調用methodB時,相當於把methodb聲明為REQUIRED。開啟一個新的事務,事務地執行。 

  當調用methodA時,類似如下效果:

main(){
    TransactionManager tm nulltry{
        //獲得一個JTA事務管理器
        tm = getTransactionManager(); 
        tm.begin();//開啟一個新的事務
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//掛起當前事務
        try{
            tm.begin();//重新開啟第二個事務
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二個事務
        }Catch(RunTimeException ex){
            ts2.rollback();//回滾第二個事務
        }finally{  
            //釋放資源
        }

        //methodB執行完后,復恢第一個事務
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一個事務
    }catch(RunTimeException ex){
        ts1.rollback();//回滾第一個事務
    }finally{
        //釋放資源 
    }
}

  在這里,我把ts1稱為外層事務,ts2稱為內層事務。從上面的代碼可以看出,ts2與ts1是兩個獨立的事務,互不相干。Ts2是否成功並不依賴於ts1。如果methodA方法在調用methodB方法后的doSomeThingB方法失敗了,而methodB方法所做的結果依然被提交。而除了methodB之外的其它代碼導致的結果卻被回滾了。 

  使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作為事務管理器。 

  注意:

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("A");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("B");
    userMapper.insertSelective(user);
}

  運行之后,發現數據並沒有插入數據庫。查看日志內容。

   從日志內容可以看出,其實兩個方法都是處於同一個事務中,method1 方法並沒有創建一個新的事務。根據 Spring 官方文檔:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

  大概意思:在默認的代理模式下,只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。在同一個類中的兩個方法直接調用,是不會被 Spring 的事務攔截器攔截,就像上面的 save 方法直接調用了同一個類中的 method1方法,method1 方法不會被 Spring 的事務攔截器攔截。可以使用 AspectJ 取代 Spring AOP 代理來解決這個問題,但是這里暫不討論。

  為了解決這個問題,我們可以新建一個類。

@Service
public class OtherServiceImpl implements OtherService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void method1() {
        User user = new User("XXXXX");
        userMapper.insertSelective(user);
    }
}

  然后在 save 方法中調用 otherService.method1 方法。

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();
    User user = new User("YYYYY");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 拋異常了");
    }
}

  這下,otherService.method1 方法的數據插入成功,save 方法的數據未插入,事務回滾。

  繼續看一下日志內容:

  從日志可以看出,首先創建了 save 方法的事務,由於 otherService.method1 方法的 @Transactional 的 propagation 屬性為 Propagation.REQUIRES_NEW ,所以接着暫停了 save 方法的事務,重新創建了 otherService.method1 方法的事務,接着 otherService.method1 方法的事務提交,接着 save 方法的事務回滾。這就印證了只有目標方法由外部調用,才能被 Spring 的事務攔截器攔截。

  1.5、PROPAGATION_NOT_SUPPORTED

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事務屬性 PROPAGATION_NOT_SUPPORTED
Transactional(propagation=Propagation.NOT_SUPPORTED)
methodB{
    …… 
}

  當單獨調用methodB時,不啟用任何事務機制,非事務地執行。 

  當調用methodA時,相當於下面的效果:

main(){
    TransactionManager tm null;
    try{
        //獲得一個JTA事務管理器
        tm = getTransactionManager();
        tm.begin();//開啟一個新的事務
        Transaction ts1 = tm.getTransaction();
        doSomeThingA();
        tm.suspend();//掛起當前事務
        methodB();
        //methodB執行完后,復恢第一個事務
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一個事務
    }catch(RunTimeException ex){
        ts1.rollback();//回滾第一個事務
    }finally{
        //釋放資源
    }
}

  使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務管理器。

  1.6、PROPAGATION_NEVER

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事務屬性 PROPAGATION_NEVER
Transactional(propagation=Propagation.NEVER)
methodB{
    …… 
}

  單獨調用methodB,則非事務的執行。 

  調用methodA則會拋出異常throw new IllegalTransactionStateException("Transaction propagation 'never' butexisting transaction found"); 

  1.7PROPAGATION_NESTED

  這是一個嵌套事務,使用JDBC 3.0驅動時,僅僅支持DataSourceTransactionManager作為事務管理器。需要JDBC 驅動的java.sql.Savepoint類。有一些JTA的事務管理器實現可能也提供了同樣的功能。 

  使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設為true; 而nestedTransactionAllowed屬性值默認為false; 

//事務屬性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事務屬性 PROPAGATION_NESTED
Transactional(propagation=Propagation.NESTED)
methodB{
    …… 
}

  如果單獨調用methodB方法,則按REQUIRED屬性執行。 
  如果調用methodA方法,相當於下面的效果:

main(){
    Connection con null;
    Savepoint savepoint null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        }catch(RuntimeException ex){
            con.rollback(savepoint);
        }finally{
            //釋放資源
        }
        doSomeThingB();
        con.commit();
    }catch(RuntimeException ex){
        con.rollback();
    }finally{
        //釋放資源
    }
} 

  當methodB方法調用之前,調用setSavepoint方法,保存當前的狀態到savepoint。如果methodB方法調用失敗,則恢復到之前保存的狀態。但是需要注意的是,這時的事務並沒有進行提交,如果后續的代碼(doSomeThingB()方法)調用失敗,則回滾包括methodB方法的所有操作。 

  嵌套事務一個非常重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾

  PROPAGATION_NESTED與PROPAGATION_REQUIRES_NEW的區別:

  相同點:都是一個嵌套事務,如果不存在一個活動的事務,都會開啟一個新的事務。使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩個獨立的事務一樣,一旦內層事務進行了提交后,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的嵌套事務。同時它需要JTA事務管理器的支持。 

  不同點:使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的嵌套事務。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時,需要JDBC 3.0以上驅動及1.4以上的JDK版本支持。其它的JTA TrasactionManager實現可能有不同的支持方式。 

  注意事項:

  1、Transactional的異常控制,默認是Check Exception 不回滾,unCheck Exception回滾;

  2、如果配置了rollbackFor 和 noRollbackFor 且兩個都是用同樣的異常,那么遇到該異常,還是回滾;

  3、rollbackFor 和noRollbackFor 配置也許不會含蓋所有異常,對於遺漏的按照Check Exception 不回滾,unCheck Exception回滾;

  4、@Transactional只能被應用到public方法上, 對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.

  5、Spring使用聲明式事務處理,默認情況下,如果被注解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。這種默認的行為是可以改變的。使用@Transactional注解的noRollbackFor和rollbackFor屬性:

如:@Transactional(rollbackFor=Exception.class)可以使checked異常發生時,數據庫操作也rollback、@Transactional(noRollbackFor=RuntimeException.class)可以使unchecked異常發生時也提交數據庫操作。

  6、可以使用noRollbackForClassName、rollbackForClassName屬性來指定一個異常類名的String數組來改變默認的行為。

  7、讀取數據庫中的數據時是不需要事務管理的,這種情況下可以使用事務的傳播行為來告訴Spring不需要開啟事務,如:@Transactional(propagation =Propagation.NOT_SUPPORTED)。

  2、事物的隔離級別

  使用@Transactional的Isolation屬性可以指定事務的隔離級別。但事務的隔離級別是由底層的數據庫實現的,並不是由Spring來實現。

READ_UNCOMMITTED:會出現臟讀、不可重復讀和幻讀問題;

READ_COMMITTED:會出現不可重復讀和幻讀問題;

REPEATABLE_READ:會出現幻讀問題;

SERIALIZABLE:串行化,不會出現上面的問題。

  一般的數據庫默認提供的是READ_COMMITTED隔離級別,如sqlserver2000Mysql默認提供的是REPEATABLE_READ

  注意:在MySQL的眾多存儲引擎中,只有InnoDB支持事務,所以說的事務隔離級別指的是InnoDB下的事務隔離級別。隔離級別引發的讀問題如下:

  3、事務超時

  設置事務超時時間

@Transactional (propagation = Propagation.REQUIRED,timeout=30)

  Spring事務超時 = 事務開始時到最后一個Statement創建時時間 + 最后一個Statement的執行時超時時間(即其queryTimeout)。

  設置了超時時間,如DataSourceTransactionManager首先開啟事物會調用其doBegin方法:

int timeout = determineTimeout(definition);  
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {  
    txObject.getConnectionHolder().setTimeoutInSeconds(timeout);  
}  

  其中determineTimeout用來獲取我們設置的事務超時時間;然后設置到ConnectionHolder對象上(其是ResourceHolder子類),接着看ResourceHolderSupport的setTimeoutInSeconds實現:

public void setTimeoutInSeconds(int seconds) {  
    setTimeoutInMillis(seconds * 1000);  
}  
  
public void setTimeoutInMillis(long millis) {  
    this.deadline = new Date(System.currentTimeMillis() + millis);  
}  

  可以看到,其會設置一個deadline時間,用來判斷事務超時時間的;在JdbcTemplate中,執行sql之前,會調用其applyStatementSettings:其會調用DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());設置超時時間;

public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {  
    Assert.notNull(stmt, "No Statement specified");  
    Assert.notNull(dataSource, "No DataSource specified");  
    ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
    if (holder != null && holder.hasTimeout()) {  
        // Remaining transaction timeout overrides specified value.  
        stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());  
    }  
    else if (timeout > 0) {  
        // No current transaction timeout -> apply specified value.  
        stmt.setQueryTimeout(timeout);  
    }  
}  

  在調用getTimeToLiveInSeconds和getTimeToLiveInMillis,會檢查是否超時,如果超時設置事務回滾,並拋出TransactionTimedOutException異常。

public int getTimeToLiveInSeconds() {  
    double diff = ((double) getTimeToLiveInMillis()) / 1000;  
    int secs = (int) Math.ceil(diff);  
    checkTransactionTimeout(secs <= 0);  
    return secs;  
}  
  
public long getTimeToLiveInMillis() throws TransactionTimedOutException{  
    if (this.deadline == null) {  
        throw new IllegalStateException("No timeout specified for this resource holder");  
    }  
    long timeToLive = this.deadline.getTime() - System.currentTimeMillis();  
    checkTransactionTimeout(timeToLive <= 0);  
    return timeToLive;  
}  
private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {  
    if (deadlineReached) {  
        setRollbackOnly();  
        throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);  
    }  
}  

  因此,在請求之后sleep並不會超時。如代碼

public void testTimeout() throws InterruptedException {
    //放在此處會發生timeout
    Thread.sleep(3000L); 
    System.out.println(System.currentTimeMillis());  
    JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);  
    jdbcTemplate.execute(" update test set hobby = hobby || '1'");  
    System.out.println(System.currentTimeMillis());  
    //放在此處並不會發生timeout
    Thread.sleep(3000L);  
}  

  4、事務的只讀readonly

  readOnly=true只讀,不能更新,刪除

@Transactional (propagation = Propagation.REQUIRED,readOnly=true)   

  注意:一次執行多次查詢來統計某些信息,這時為了保證數據整體的一致性,要用只讀事務。

  由於只讀事務不存在數據的修改,因此數據庫將會為只讀事務提供一些優化手段,例如Oracle對於只讀事務,不啟動回滾段,不記錄回滾log。

  1、在JDBC中,指定只讀事務的辦法為: connection.setReadOnly(true);

  2、在Hibernate中,指定只讀事務的辦法為: session.setFlushMode(FlushMode.NEVER); 

  此時,Hibernate也會為只讀事務提供Session方面的一些優化手段

  3、在Spring的Hibernate封裝中,指定只讀事務的辦法為: bean配置文件中,prop屬性增加“readOnly”,或者用注解方式@Transactional(readOnly=true)

  【 if the transaction is marked as read-only, Spring will set the Hibernate Session’s flush mode to FLUSH_NEVER, and will set the JDBC transaction to read-only】也就是說在Spring中設置只讀事務是利用上面兩種方式。

三、@Transactional 事務實現機制

  在應用系統調用聲明了 @Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據 @Transactional 的屬性配置信息,這個代理對象決定該聲明 @Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行之前創建並加入事務,並執行目標方法的邏輯, 最后根據執行情況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操作數據源 DataSource 提交或回滾事務。

  Spring AOP 代理有 CglibAopProxyJdkDynamicAopProxy 兩種,以 CglibAopProxy 為例,對於 CglibAopProxy,需要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,需要調用其 invoke 方法。

  正如上文提到的,事務管理的框架是由抽象事務管理器 AbstractPlatformTransactionManager 來提供的,而具體的底層事務處理實現,由 PlatformTransactionManager 的具體實現類來實現,如事務管理器 DataSourceTransactionManager。不同的事務管理器管理不同的數據資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。

四、指定事務管理器

  關於事務管理器,不管是JPA還是JDBC等都實現自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依賴,框架會默認注入 DataSourceTransactionManager 實例。如果你添加的是 spring-boot-starter-data-jpa 依賴,框架會默認注入 JpaTransactionManager 實例。

  可以在啟動類中添加如下方法,Debug測試,就能知道自動注入的是 PlatformTransactionManager 接口的哪個實現類。

@EnableTransactionManagement // 啟注解事務管理,等同於xml配置方式的 <tx:annotation-driven />
@SpringBootApplication
public class ProfiledemoApplication {

    @Bean
    public Object testBean(PlatformTransactionManager platformTransactionManager){
        System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
        return new Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(ProfiledemoApplication.class, args);
    }
}

  可以在啟動項目時,指定事務管理器,代碼如下:

@EnableTransactionManagement
@SpringBootApplication
public class ProfiledemoApplication {

    // 其中 dataSource 框架會自動為我們注入
    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);     }

    @Bean
    public Object testBean(PlatformTransactionManager platformTransactionManager) {
        System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
        return new Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(ProfiledemoApplication.class, args);
    }
}

  在Spring容器中,我們手工注解@Bean 將被優先加載,框架不會重新實例化其他的 PlatformTransactionManager 實現類。然后在Service中,被 @Transactional 注解的方法,將支持事務。如果注解在類上,則整個類的所有方法都默認支持事務。對於同一個工程中存在多個事務管理器要怎么處理,請看下面的實例,具體說明請看代碼中的注釋。

@EnableTransactionManagement // 開啟注解事務管理,等同於xml配置文件中的 <tx:annotation-driven />
@SpringBootApplication
public class ProfiledemoApplication implements TransactionManagementConfigurer {

    @Resource(name="txManager2")
    private PlatformTransactionManager txManager2;

    // 創建事務管理器1
    @Bean(name = "txManager1")
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // 創建事務管理器2
    @Bean(name = "txManager2")
    public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }

    // 實現接口 TransactionManagementConfigurer 方法,其返回值代表在擁有多個事務管理器的情況下默認使用的事務管理器
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager2;
    }

    public static void main(String[] args) {
        SpringApplication.run(ProfiledemoApplication.class, args);
    }

}

@Component
public class DevSendMessage implements SendMessage {

    // 使用value具體指定使用哪個事務管理器
    @Transactional(value="txManager1")
    @Override
    public void send() {
        System.out.println(">>>>>>>>Dev Send()<<<<<<<<");
        send2();
    }

    // 在存在多個事務管理器的情況下,如果使用value具體指定
    // 則默認使用方法 annotationDrivenTransactionManager() 返回的事務管理器
    @Transactional
    public void send2() {
        System.out.println(">>>>>>>>Dev Send2()<<<<<<<<");
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM