Spring 事務處理

前言:

事務處理的本質

在學習事務處理前,須要明確一點:java

數據庫操做最終都要使用到JDBC,那麼不管上層如何封裝,底層都是調用Connection的commit,rollback來完成mysql

煩人的事務處理:

在平常開發中,數據訪問層(DAO)必然須要進行事務的處理,可是咱們會發現,事務處理的代碼一般是簡單的重複的,編寫這樣的重複代碼會浪費大量的時間,因此咱們須要找到一種方案能夠將這些重複的代碼進行抽取,以便與管理維護和複用,spring

咱們的需求:在一系列數據庫操做上的方法上增長額外的事務處理代碼,讓原來的方法中只關注具體的數據處理,即在本來以及存在的數據庫操做方法上添加額外的事務處理邏輯sql

到這裏你應該想到AOP了,沒錯! 這樣的場景下AOP是最好的解決方案;數據庫

解決方案:AOP

回顧一下Spring的AOP:在結合目前的需求express

1.將目標對象(DAO)放入Spring容器api

2.告知Spring你的通知代碼是什麼(事務處理)session

3.告知Spring 哪些方法(DAO的CRUD)要應用那些通知(不一樣的事務處理代碼)mybatis

4.從Spring中獲取代理對象來完成本來的CRUD,代理對象會自動完成事務處理app

Spring 事務處理API

Spring做爲框架,須要進行詳細的設計,全方位的考慮事務處理的各個方面,而不只是簡單的幫你執行commit,rollback;

Spring對事務處理進行了抽象定義,造成了一套具體的API結構,以下:

image-20200114134619820

  • TransactionDefinition:定義事務的具體屬性,如隔離級別,超時設置,傳播行爲等

  • TransactionStatus: 用於獲取當前事務的狀態信息

  • PlatformTransactionMananger: 主要的事務管理接口,提供三個實現類對應不一樣場景

類型 場景
DataSourceTransactionManager 使用Spring JDBC或 iBatis 進行持久化數據時使用
HibernateTransactionManager 使用Hibernate3.0版本 進行持久化數據時使用
JpaTransactionManager 使用JPA進行持久化時 使用
JtaTransactionManager 使用一個JTA實現來管理事務,跨數據源時使用

注意其分佈在不一樣的jar包中,使用時根據須要導入對應jar包

事務的傳播行爲控制

這是一個新概念可是也很是簡單,即在一個執行sql的方法中調用了另外一個方法時,該如何處理這兩個方法之間的事務

Spring定義了7種不一樣的處理方式:

常量名 含義
PROPAGATION_REQUIRED 支持當前事務。若是 A 方法已經在事 務中,則 B 事務將直接使用。不然將 建立新事務
PROPAGATION_SUPPORTS 支持當前事務。若是 A 方法已經在事 務中,則 B 事務將直接使用。不然將 以非事務狀態執行
PROPAGATION_MANDATORY 支持當前事務。若是 A 方法沒有事 務,則拋出異常
PROPAGATION_REQUIRES_NEW 將建立新的事務,若是 A 方法已經在 事務中,則將 A 事務掛起
PROPAGATION_NOT_SUPPORTED 不支持當前事務,老是以非事務狀態 執行。若是 A 方法已經在事務中,則 將其掛起
PROPAGATION_NEVER 不支持當前事務,若是 A 方法在事務 中,則拋出異常
PROPAGATION.NESTED 嵌套事務,當外層出現異常則連同內層一塊兒回滾,若外層正常而內部異常,僅回滾內部操做

上述涉及的掛起,意思是開啓一個獨立的事務,已存在的事務暫停執行,等待新事務執行完畢後繼續執行,兩個事務不會互相影響

Spring 整合MyBatis

在開始前咱們先完成一個基礎的CURD功能,後續開發中Spring + MyBatis項目是很常見的,那要將MyBatis整合到Spring中來,要明確一下二者的關係和定位

  • Spring Java開發框架,其本質是一個對象容器,能夠幫助咱們完成IOC,DI,AOP

  • MyBatis是一個持久層框架,用於簡化對數據庫的操做

將二者整合起來,就是將MyBatis中的對象交給Spring來管理,且將這些對象的依賴也交給Spring來管理;

添加依賴:

Spring 3.0 的開發在 MyBatis 3.0 官方發佈前就結束了,因而MyBatis社區本身召集開發者完成了這一部分工做,因而有了mybatis-spring項目,後續Spring也就沒有必要在開發一個新的模塊了,因此該jar是MyBatis提供的

<!-- Spring整合MyBatis依賴 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.3</version>
</dependency>

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.2</version>
</dependency>



<!--JDBC-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.44</version>
</dependency>

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
</dependency>


<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>

<!--Spring JDBC-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>
<!--事務管理-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>
<!--AspectJ-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.0</version>
</dependency>

SM基礎使用

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--    加載properties-->
    <context:property-placeholder location="jdbc.properties"/>

<!--    數據源 後續可更換爲其餘更方便的數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${url}"/>
        <property name="username" value="${usr}"/>
        <property name="password" value="${password}"/>
        <property name="driverClassName" value="${driver}"/>
    </bean>
  
<!--    MyBatis核心對象SqlSessionFactory-->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    掃描Mapper 將代理對象放入Spring-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yh.dao"/>
    </bean>
</beans>

jdbc.properties:

driver = com.mysql.jdbc.Driver
url = jdbc:mysql:///SMDB?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
usr = root
password = admin
location = /Users/jerry/.m2/repository/mysql/mysql-connector-java/8.0.17/mysql-connector-java-8.0.17.jar

測試代碼:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class Test1 {
    @Autowired
    private StudentMapper studentMapper;
    @Test
    public  void test(){
        Student student = studentMapper.selectByPrimaryKey(1);
        System.out.println(student);
    }
}

編碼式事務

編碼式事務,即在源代碼中加入 事務處理的代碼, 即commit,rollback等,這是很是原始的作法僅做爲了解

純手動管理事務

配置文件:

<!--    在以前的配置中添加內容-->

<!--事務管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    事務定義 -->
    <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
<!--        隔離級別 可缺省-->
        <property name="isolationLevelName" value="ISOLATION_REPEATABLE_READ"/>
<!--        傳播行爲 可缺省-->
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
    </bean>

測試代碼:

@Autowired
private StudentMapper studentMapper;
@Autowired
private DataSourceTransactionManager manager;
@Autowired
private DefaultTransactionDefinition definition;

@Test
public  void test(){
    TransactionStatus transactionStatus = manager.getTransaction(definition);
    try{
        Student student = studentMapper.selectByPrimaryKey(1);
        System.out.println(student);
        student.setAge(201);
        studentMapper.updateByPrimaryKey(student);
      
        int i = 1/0;
        manager.commit(transactionStatus);
    }catch (Exception e){
        e.printStackTrace();
        manager.rollback(transactionStatus);
    }
}

上述代碼僅用於測試事務處理的有效性;

咱們已經在Spring中配置了MyBatis,並進行了事務處理,可是沒有解決重複代碼的問題

使用事務模板

事務模板原理是將要執行的具體代碼交給模板,模板會在執行這寫代碼的同時處理事務,當這寫代碼出現異常時則自動回滾事務,以此來簡化書寫

配置文件:

<!-- 在上述配置基礎上刪除事務定義 添加模板Bean-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  <!--       傳播行爲隔離級別等參數  可缺省-->
  <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
  <property name="transactionManager" ref="transactionManager"/>
</bean>

測試代碼:

public class Test2 {

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private TransactionTemplate transactionTemplate;


    @Test
    public  void test(){
        transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus transactionStatus) {
                Student student = studentMapper.selectByPrimaryKey(1);
                System.out.println(student);
                student.setAge(1101);
                studentMapper.updateByPrimaryKey(student);
//                int i = 1/0;
                return null;
            }
        });
    }
}

能夠看到事務模板要求提供一個實現類來提交原始的數據庫操做給模板,從而完成事務代碼的加強

不管是純手動管理仍是利用模板,依然存在大量與業務無關的重複代碼,這也是編碼式事務最大的問題;

聲明式事務

即不須要在原來的業務邏輯代碼中加入任何事務相關的代碼,而是經過xml,或者註解的方式,來告訴框架,哪些方法須要添加事務處理代碼,讓框架來完成在原始業務邏輯先後增長事務處理的代碼(經過AOP),這也是AOP使用較多的場景之一;

基於tx名稱空間的配置

配置文件:

須要引入aop和tx名稱空間

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="mybatis-beans.xml"/>
    <context:component-scan base-package="com.yh.service"/>

<!--    添加事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    事務通知-->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
<!--            name指定要應用的方法名稱 還有其餘事務經常使用屬性如隔離級別傳播行爲等..-->
            <tx:method name="*" read-only="false"/>
        </tx:attributes>
    </tx:advice>

<!--    切點信息-->
    <aop:config >
<!--        根據表達式中的信息能夠自動查找到目標對象 從而進行加強 先查找目標再生產代理-->
        <aop:pointcut id="pointcut" expression="execution(* com.yh.service.*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

tx:method屬性:

屬性名 含義
name 匹配的方法名稱
isolation 事務隔離級別
read-only 是否採用優化的只 讀事務
timeout 超時
rollback-for 須要回滾的異常類
propagation 傳播行爲
no-rollback-for 不須要回滾的異常類

Service:

@Service
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;
    public Student getStudent(int id ){
        return studentMapper.selectByPrimaryKey(id);
    }
    public void update(Student student){
        studentMapper.updateByPrimaryKey(student);
        int i  = 1/0;
    }
}

測試代碼:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class Test3 {
    @Autowired
    StudentService studentService;

    @Test
    public void test(){
        Student student = studentService.getStudent(1);
        System.out.println(student);
        student.setAge(8818);
        studentService.update(student);
    }
}

強調:事務加強應該應用到Service層,即業務邏輯層,應爲一個業務方法可能涉及多個數據庫操做,當某個操做遇到異常時須要將全部操做所有回滾

基於註解的配置

Spring固然也支持採用註解形式來處理事務

開啓註解事務支持:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--爲了分離關注點,故將MyBatis相關配置放到其餘配置文件了-->
    <import resource="mybatis-beans.xml"/>
    <context:component-scan base-package="com.yh.service"/>
  
<!--    添加事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    開啓註解事務管理-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

Service中增長方法:

@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transactionTest(){
    Student student = getStudent(1);
    student.setAge(1);
    update(student);
    int i = 1/0;
    student.setName("jack");
    update(student);
}
//固然註解上的參數都是可選的採用默認值便可

測試代碼

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class Test4 {

    @Autowired
    StudentService studentService;

    @Test
    public void test(){
        studentService.transactionTest();
    }
}

你可能會以爲註解的方式比xml配置簡單的多,可是考慮一下,當你的項目特別大,涉及的表不少的時候呢,你可能須要些不少不少的註解,假設後期須要修改某些屬性,還得一個個改;

因此大項目建議採用XML,小項目使用註解也ok;

原理簡述

聲明式事務其底層用的仍是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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="mybatis-beans.xml"/>

<!--    添加事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    要進行事務加強的目標對象-->
    <bean id="serviceTarget" class="com.yh.service.StudentService"/>
<!--    事務通知-->
    <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 id="orderService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="serviceTarget"/>
        <property name="interceptorNames">
            <list>
                <idref bean="transactionInterceptor"/>
            </list>
        </property>
    </bean>
</beans>

測試代碼:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext5.xml")
public class Test5 {

    @Autowired
    @Qualifier("orderService")
    StudentService studentService;

    @Test
    public void test(){
        Student student = studentService.getStudent(1);
        student.setAge(1);
        studentService.update(student);
    }
}
相關文章
相關標籤/搜索