1. 程式人生 > >Spring中@Transactional事務回滾

Spring中@Transactional事務回滾

一、Spring 預設事務

Spring中@Transactional事務,預設情況下只對 RuntimeException 回滾。

即:

  1. 如果被註解的資料庫操作方法中發生了unchecked異常(RuntimeException),所有的資料庫操作將rollback;
  2. 如果發生的異常是checked異常(Exception),預設情況下資料庫操作還是會提交的。

也就是說,預設情況下,如果程式丟擲的是Exception 及 Exception的子類異常,Spring的@Transactional事務 是不會回滾的。

如果想 unchecked異常,checked異常時都回滾,可以這樣配置:

@Transactional(rollbackFor={RuntimeException.class, Exception.class})

看例子:


//不指定異常,預設就是RuntimeException。丟擲Exception時,事務不會回滾。
@Transactional()
public void methodName() {
   throw new Exception("註釋");
}

//預設就是RuntimeException,丟擲RuntimeException時,事務肯定會回滾。
@Transactional()
public void methodName() {
   throw
new RuntimeException("註釋"); } //指定拋 Exception時回滾,遇到異常Exception時回滾, //拋 RuntimeException 異常是不會回滾的 @Transactional(rollbackFor=Exception.class) public void methodName() {    throw new Exception("註釋"); } //指定不回滾,遇到執行期例外(throw new RuntimeException("註釋");)會回滾 @Transactional(noRollbackFor=Exception.class) public
ItimDaoImpl getItemDaoImpl() {    throw new RuntimeException("註釋"); }

二、 Spring + myBatis常用的事務配置

@Transactional的事務配置:

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

<tx:annotation-driven proxy-target-class="false" transaction-manager="txManager" />

整合spring配置如下:

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
		http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
	
	<!-- 讀取屬性檔案 -->
	<context:property-placeholder 
		location="classpath:/properties/jdbc.properties,classpath:/properties/config.properties,classpath:/properties/rabbitmq-config.properties,classpath:/properties/redis.properties" 
		ignore-unresolvable="true" />

	<!-- 阿里 druid資料庫連線池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
		<!-- 資料庫基本資訊配置 -->
		<property name="url" value="${mysql.url}" />
		<property name="username" value="${mysql.username}" />
		<property name="password" value="${mysql.password}" />
		<property name="driverClassName" value="${mysql.driverClassName}" />
		<property name="filters" value="${mysql.filters}" />
		<!-- 最大併發連線數 -->
		<property name="maxActive" value="${mysql.maxActive}" />
		<!-- 初始化連線數量 -->
		<property name="initialSize" value="${mysql.initialSize}" />
		<!-- 配置獲取連線等待超時的時間 -->
		<property name="maxWait" value="${mysql.maxWait}" />
		<!-- 最小空閒連線數 -->
		<property name="minIdle" value="${mysql.minIdle}" />
		<!-- 最大空閒連線數 -->
		<property name="maxIdle" value="${mysql.maxIdle}" />	
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:/sqlmap/sqlmap-config.xml" />

		 <!-- 自動掃描mapping.xml檔案
        <property name="mapperLocations" value="classpath:/sqlmap/po/*.xml,classpath:sqlmap/base/*.xml"></property> -->
	</bean>	

	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
		<property name="dataSource" ref="dataSource"></property>
	</bean>	
	
	<tx:annotation-driven proxy-target-class="false" transaction-manager="txManager" />

	<!-- 自動掃描註解的bean -->
    <context:component-scan base-package="com.aop8.springmvc_study.service" />

</beans>

三、Spring中 @Transactional 事務回滾注事項

  1. 在需要事務管理的地方加 @Transactional 註解。@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上。

  2. @Transactional 註解只能應用到 public 可見度的方法上。

    如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。

  3. 注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種元資料。必須在配置檔案中使用配置元素,才真正開啟了事務行為。

  4. 通過元素的 proxy-target-class 屬性值來控制是基於介面的還是基於類的代理被建立。

    如果 proxy-target-class屬值被設定為false 或者這個屬性被省略,那麼標準的JDK基於介面的代理將起作用。
    如果 proxy-target-class屬值被設定為true,那麼基於類的代理將起作用(這時需要CGLIB庫)。

  5. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何介面上。

    在介面上使用 @Transactional 註解,只能當你設定了基於介面的代理時它才生效。因為註解是 不能繼承 的,這就意味著如果正在使用基於類的代理時,那麼事務的設定將不能被基於類的代理所識別,而且物件也將不會被事務代理所包裝。

  6. @Transactional 的事務開啟,或者是基於介面的,或者是基於類的代理被建立。所以在同一個類中一個方法呼叫另一個方法有事務的方法,事務是不會起作用的。

原因:
spring 在掃描bean的時候會掃描方法上是否包含@Transactional 註解,如果包含,spring會為這個bean動態地生成一個子類(即代理類,proxy),代理類是繼承原來那個bean的。

此時,當這個有註解的方法被呼叫的時候,實際上是由代理類來呼叫的,代理類在呼叫之前就會啟動 transaction

然而,如果這個有註解的方法是被同一個類中的其他方法呼叫的,那麼該方法的呼叫並沒有通過代理類,而是直接通過原來的那個bean,所以就不會啟動transaction,我們看到的現象就是 @Transactional 註解無效

總結:
同一個類中,一個沒有事務的方法A,去呼叫另一個有事務的方法B時,因為是直接呼叫,而不是呼叫的代理類,所以事務不起用的。