Spring中@Transactional事務回滾例項及原始碼

小寶鴿發表於2016-05-03

一、使用場景舉例

在瞭解@Transactional怎麼用之前我們必須要先知道@Transactional有什麼用。下面舉個例子:比如一個部門裡面有很多成員,這兩者分別儲存在部門表和成員表裡面,在刪除某個部門的時候,假設我們預設刪除對應的成員。但是在執行的時候可能會出現這種情況,我們先刪除部門,再刪除成員,但是部門刪除成功了,刪除成員的時候出異常了。這時候我們希望如果成員刪除失敗了,之前刪除的部門也取消刪除。這種場景就可以使用@Transactional事物回滾。

二、checked異常和unchecked異常

這裡之所以讓大家清楚checked異常和unchecked異常概念,是因為:

Spring使用宣告式事務處理,預設情況下,如果被註解的資料庫操作方法中發生了unchecked異常,所有的資料庫操作將rollback;如果發生的異常是checked異常,預設情況下資料庫操作還是會提交的。

checked異常:

表示無效,不是程式中可以預測的。比如無效的使用者輸入,檔案不存在,網路或者資料庫連結錯誤。這些都是外在的原因,都不是程式內部可以控制的。

必須在程式碼中顯式地處理。比如try-catch塊處理,或者給所在的方法加上throws說明,將異常拋到呼叫棧的上一層。

繼承自java.lang.Exception(java.lang.RuntimeException除外)。

unchecked異常:

表示錯誤,程式的邏輯錯誤。是RuntimeException的子類,比如IllegalArgumentException, NullPointerException和IllegalStateException。

不需要在程式碼中顯式地捕獲unchecked異常做處理。

繼承自java.lang.RuntimeException(而java.lang.RuntimeException繼承自java.lang.Exception)。

看下面的異常結構圖或許層次感更加深些:

這裡寫圖片描述

三、@Transactional的使用例項

本例項採用的是Eclipse+maven,maven只是作為jar管理,即便不瞭解的maven的猿友也可以讀懂。

3.1、spring的配置檔案

裡面必須先配置tx名字空間如下:

這裡寫圖片描述

為了使用基於@Transactional的事務管理,需要在Spring中進行如下的配置:

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

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

博主的整個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:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="  

http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-2.5.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-3.0.xsd


http://www.springframework.org/schema/aop


http://www.springframework.org/schema/aop/spring-aop-3.0.xsd


http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

     <!-- 引入jdbc配置檔案 -->  
     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
               <value>classpath:properties/*.properties</value>
                <!--要是有多個配置檔案,只需在這裡繼續新增即可 -->
            </list>
        </property>
    </bean>

    <!-- 配置資料來源 -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 不使用properties來配置 -->
        <!-- <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
            <property name="url" value="jdbc:mysql://localhost:3306/learning" /> 
            <property name="username" value="root" /> 
            <property name="password" value="christmas258@" /> -->
       <!-- 使用properties來配置 -->
        <property name="driverClassName">
            <value>${jdbc_driverClassName}</value>
        </property>
        <property name="url">
            <value>${jdbc_url}</value>
        </property>
        <property name="username">
            <value>${jdbc_username}</value>
        </property>
        <property name="password">
            <value>${jdbc_password}</value>
        </property>
    </bean>

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

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

    <!-- 自動掃描了所有的XxxxMapper.xml對應的mapper介面檔案,這樣就不用一個一個手動配置Mpper的對映了,只要Mapper介面類和Mapper對映檔案對應起來就可以了。 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage"
            value="com.luo.dao" />
    </bean>

    <!-- 配置Mybatis的檔案 ,mapperLocations配置**Mapper.xml檔案位置,configLocation配置mybatis-config檔案位置-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>  
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
        <!-- <property name="typeAliasesPackage" value="com.tiantian.ckeditor.model" 
            /> -->
    </bean>

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

</beans>

3.2、使用@Transactional,在新增使用者實現類方法加上註解

@Transactional(propagation=Propagation.REQUIRED)
public void addUser(User user) {
    userDao.addUser(user);
    String string  = null;
    if(string.equals("")) {
        int i = 0;
    }
}

上面的方法我故意讓其出現空指標異常,會事物回滾

3.3、執行單元測試類

@Test  
public void addUserTest(){  
    User user = new User();
    user.setUserName("luoguohui1");
    user.setUserPassword("luoguohui1");
    userService.addUser(user);
}

發現無法插入進去,但是如果把@Transactional去掉,即程式碼如下,雖然出現異常,但是資料庫中還是有新增對應資料的:

這裡寫圖片描述

3.4、原始碼下載

本文的工程是在mybatis入門(含例項教程和原始碼)的基礎上修改的,該文包含了資料庫指令碼及工程搭建的詳細流程。

本文最終原始碼下載:

http://download.csdn.net/detail/u013142781/9381184

四、Spring中的@Transactional必須要了解的概念

Spring中的@Transactional基於動態代理的機制,提供了一種透明的事務管理機制,方便快捷解決在開發中碰到的問題。

一般使用是通過如下程式碼對方法或介面或類註釋:

@Transactional(propagation=Propagation.NOT_SUPPORTED)

Propagation支援7種不同的傳播機制:

REQUIRED:如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。

SUPPORTS: 如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

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

REQUIRESNEW:總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

MANDATORY:如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常。

NEVER:總是非事務地執行,如果存在一個活動事務,則丟擲異常

NESTED:如果一個活動的事務存在,則執行在一個巢狀的事務中。如果沒有活動事務,則按REQUIRED屬性執行。

下面是一些需要注意的事項,必須必須必須要看,不然遇到各種坑別說博主沒有提醒你哦

下面是一些需要注意的事項,必須必須必須要看,不然遇到各種坑別說博主沒有提醒你哦

下面是一些需要注意的事項,必須必須必須要看,不然遇到各種坑別說博主沒有提醒你哦

  • 在需要事務管理的地方加@Transactional 註解。@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上。
  • @Transactional 註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。
  • 注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種後設資料。必須在配置檔案中使用配置元素,才真正開啟了事務行為。
  • 通過 元素的 “proxy-target-class” 屬性值來控制是基於介面的還是基於類的代理被建立。如果 “proxy-target-class” 屬值被設定為 “true”,那麼基於類的代理將起作用(這時需要CGLIB庫cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 屬值被設定為 “false” 或者這個屬性被省略,那麼標準的JDK基於介面的代理將起作用。
  • Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何介面上。在介面上使用 @Transactional 註解,只能當你設定了基於介面的代理時它才生效。因為註解是 不能繼承 的,這就意味著如果正在使用基於類的代理時,那麼事務的設定將不能被基於類的代理所識別,而且物件也將不會被事務代理所包裝。
  • @Transactional 的事務開啟 ,或者是基於介面的 或者是基於類的代理被建立。所以在同一個類中一個方法呼叫另一個方法有事務的方法,事務是不會起作用的。

相關文章