spring5 源碼深度解析----- @Transactional注解的聲明式事物介紹(100%理解事務)


面的幾個章節已經分析了spring基於@AspectJ的源碼,那么接下來我們分析一下Aop的另一個重要功能,事物管理。

事務的介紹

1.數據庫事物特性

  • 原子性
    多個數據庫操作是不可分割的,只有所有的操作都執行成功,事物才能被提交;只要有一個操作執行失敗,那么所有的操作都要回滾,數據庫狀態必須回復到操作之前的狀態
  • 一致性
    事物操作成功后,數據庫的狀態和業務規則必須一致。例如:從A賬戶轉賬100元到B賬戶,無論數據庫操作成功失敗,A和B兩個賬戶的存款總額是不變的。
  • 隔離性
    當並發操作時,不同的數據庫事物之間不會相互干擾(當然這個事物隔離級別也是有關系的)
  • 持久性
    事物提交成功之后,事物中的所有數據都必須持久化到數據庫中。即使事物提交之后數據庫立刻崩潰,也需要保證數據能能夠被恢復。

2.事物隔離級別

當數據庫並發操作時,可能會引起臟讀、不可重復讀、幻讀、第一類丟失更新、第二類更新丟失等現象。

  • 臟讀
    事物A讀取事物B尚未提交的更改數據,並做了修改;此時如果事物B回滾,那么事物A讀取到的數據是無效的,此時就發生了臟讀。
  • 不可重復讀
    一個事務執行相同的查詢兩次或兩次以上,每次都得到不同的數據。如:A事物下查詢賬戶余額,此時恰巧B事物給賬戶里轉賬100元,A事物再次查詢賬戶余額,那么A事物的兩次查詢結果是不一致的。
  • 幻讀
    A事物讀取B事物提交的新增數據,此時A事物將出現幻讀現象。幻讀與不可重復讀容易混淆,如何區分呢?幻讀是讀取到了其他事物提交的新數據,不可重復讀是讀取到了已經提交事物的更改數據(修改或刪除)

對於以上問題,可以有多個解決方案,設置數據庫事物隔離級別就是其中的一種,數據庫事物隔離級別分為四個等級,通過一個表格描述其作用。

隔離級別 臟讀 不可重復讀 幻象讀
READ UNCOMMITTED 允許 允許 允許
READ COMMITTED 臟讀 允許 允許
REPEATABLE READ 不允許 不允許 允許
SERIALIZABLE 不允許 不允許 不允許

3.Spring事物支持核心接口

 
  • TransactionDefinition-->定義與spring兼容的事務屬性的接口
public interface TransactionDefinition {
    // 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中。
    int PROPAGATION_REQUIRED = 0;
    // 支持當前事物,如果當前沒有事物,則以非事物方式執行。
    int PROPAGATION_SUPPORTS = 1;
    // 使用當前事物,如果當前沒有事物,則拋出異常。
    int PROPAGATION_MANDATORY = 2;
    // 新建事物,如果當前已經存在事物,則掛起當前事物。
    int PROPAGATION_REQUIRES_NEW = 3;
    // 以非事物方式執行,如果當前存在事物,則掛起當前事物。
    int PROPAGATION_NOT_SUPPORTED = 4;
    // 以非事物方式執行,如果當前存在事物,則拋出異常。
    int PROPAGATION_NEVER = 5;
    // 如果當前存在事物,則在嵌套事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同
    int PROPAGATION_NESTED = 6;
    // 使用后端數據庫默認的隔離級別。
    int ISOLATION_DEFAULT = -1;
    // READ_UNCOMMITTED 隔離級別
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    // READ_COMMITTED 隔離級別
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    // REPEATABLE_READ 隔離級別
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    // SERIALIZABLE 隔離級別
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
    // 默認超時時間
    int TIMEOUT_DEFAULT = -1;
    // 獲取事物傳播特性
    int getPropagationBehavior();
    // 獲取事物隔離級別
    int getIsolationLevel();
    // 獲取事物超時時間
    int getTimeout();
    // 判斷事物是否可讀
    boolean isReadOnly();
    // 獲取事物名稱
    @Nullable
    String getName();
}
  1. Spring事物傳播特性表:
傳播特性名稱 說明
PROPAGATION_REQUIRED 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中
PROPAGATION_SUPPORTS 支持當前事物,如果當前沒有事物,則以非事物方式執行
PROPAGATION_MANDATORY 使用當前事物,如果當前沒有事物,則拋出異常
PROPAGATION_REQUIRES_NEW 新建事物,如果當前已經存在事物,則掛起當前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式執行,如果當前存在事物,則掛起當前事物
PROPAGATION_NEVER 以非事物方式執行,如果當前存在事物,則拋出異常
PROPAGATION_NESTED

如果當前存在事物,則在嵌套事物內執行;

如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同

  1. Spring事物隔離級別表:
事務隔離級別  臟讀  不可重復讀  幻讀
 讀未提交(read-uncommitted)  是  是  是
 不可重復讀(read-committed)  否   是  是
 可重復讀(repeatable-read)  否   否   是
 串行化(serializable)  否   否   否 

mysql默認的事務隔離級別為 可重復讀repeatable-read

  3.PlatformTransactionManager-->Spring事務基礎結構中的中心接口

public interface PlatformTransactionManager {
    // 根據指定的傳播行為,返回當前活動的事務或創建新事務。
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    // 就給定事務的狀態提交給定事務。
    void commit(TransactionStatus status) throws TransactionException;
    // 執行給定事務的回滾。
    void rollback(TransactionStatus status) throws TransactionException;
}

Spring將事物管理委托給底層的持久化框架來完成,因此,Spring為不同的持久化框架提供了不同的PlatformTransactionManager接口實現。列舉幾個Spring自帶的事物管理器:

事物管理器 說明
org.springframework.jdbc.datasource.DataSourceTransactionManager 提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS或MyBatis框架的事務管理
org.springframework.orm.jpa.JpaTransactionManager 提供對單個javax.persistence.EntityManagerFactory事務支持,用於集成JPA實現框架時的事務管理
org.springframework.transaction.jta.JtaTransactionManager 提供對分布式事務管理的支持,並將事務管理委托給Java EE應用服務器事務管理器

 

  • TransactionStatus-->事物狀態描述
  1. TransactionStatus接口
public interface TransactionStatus extends SavepointManager, Flushable {
    // 返回當前事務是否為新事務(否則將參與到現有事務中,或者可能一開始就不在實際事務中運行)
    boolean isNewTransaction();
    // 返回該事務是否在內部攜帶保存點,也就是說,已經創建為基於保存點的嵌套事務。
    boolean hasSavepoint();
    // 設置事務僅回滾。
    void setRollbackOnly();
    // 返回事務是否已標記為僅回滾
    boolean isRollbackOnly();
    // 將會話刷新到數據存儲區
    @Override
    void flush();
    // 返回事物是否已經完成,無論提交或者回滾。
    boolean isCompleted();
}
  1. SavepointManager接口
public interface SavepointManager {
    // 創建一個新的保存點。
    Object createSavepoint() throws TransactionException;
    // 回滾到給定的保存點。
    // 注意:調用此方法回滾到給定的保存點之后,不會自動釋放保存點,
    // 可以通過調用releaseSavepoint方法釋放保存點。
    void rollbackToSavepoint(Object savepoint) throws TransactionException;
    // 顯式釋放給定的保存點。(大多數事務管理器將在事務完成時自動釋放保存點)
    void releaseSavepoint(Object savepoint) throws TransactionException;
}

Spring編程式事物

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `balance` int(11) DEFAULT NULL COMMENT '賬戶余額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='--賬戶表'
  • 實現
 1 import org.apache.commons.dbcp.BasicDataSource;
 2 import org.springframework.dao.DataAccessException;
 3 import org.springframework.jdbc.core.JdbcTemplate;
 4 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 5 import org.springframework.transaction.TransactionDefinition;
 6 import org.springframework.transaction.TransactionStatus;
 7 import org.springframework.transaction.support.DefaultTransactionDefinition;
 8 
 9 import javax.sql.DataSource;
10 
11 /**
12  * Spring編程式事物
13  * @author: Chenhao
14  * @create: 2019-10-08 11:41
15  */
16 public class MyTransaction {
17 
18     private JdbcTemplate jdbcTemplate;
19     private DataSourceTransactionManager txManager;
20     private DefaultTransactionDefinition txDefinition;
21     private String insert_sql = "insert into account (balance) values ('100')";
22 
23     public void save() {
24 
25         // 1、初始化jdbcTemplate
26         DataSource dataSource = getDataSource();
27         jdbcTemplate = new JdbcTemplate(dataSource);
28 
29         // 2、創建物管理器
30         txManager = new DataSourceTransactionManager();
31         txManager.setDataSource(dataSource);
32 
33         // 3、定義事物屬性
34         txDefinition = new DefaultTransactionDefinition();
35         txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
36 
37         // 3、開啟事物
38         TransactionStatus txStatus = txManager.getTransaction(txDefinition);
39 
40         // 4、執行業務邏輯
41         try {
42             jdbcTemplate.execute(insert_sql);
43             //int i = 1/0;
44             jdbcTemplate.execute(insert_sql);
45             txManager.commit(txStatus);
46         } catch (DataAccessException e) {
47             txManager.rollback(txStatus);
48             e.printStackTrace();
49         }
50 
51     }
52 
53     public DataSource getDataSource() {
54         BasicDataSource dataSource = new BasicDataSource();
55         dataSource.setDriverClassName("com.mysql.jdbc.Driver");
56         dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
57         dataSource.setUsername("root");
58         dataSource.setPassword("chenhao1991@");
59         return dataSource;
60     }
61 
62 }
  • 測試類及結果
public class MyTest {
    @Test
    public void test1() {
        MyTransaction myTransaction = new MyTransaction();
        myTransaction.save();
    }
}

運行測試類,在拋出異常之后手動回滾事物,所以數據庫表中不會增加記錄。

基於@Transactional注解的聲明式事物

其底層建立在 AOP 的基礎之上,對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。通過聲明式事物,無需在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過等價的基於標注的方式),便可以將事務規則應用到業務邏輯中。

  • 接口
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 賬戶接口
 * @author: ChenHao
 * @create: 2019-10-08 18:38
 */
@Transactional(propagation = Propagation.REQUIRED)
public interface AccountServiceImp {
    void save() throws RuntimeException;
}
  • 實現
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 賬戶接口實現
 * @author: ChenHao
 * @create: 2019-10-08 18:39
 */
public class AccountServiceImpl implements AccountServiceImp {

    private JdbcTemplate jdbcTemplate;

    private static String insert_sql = "insert into account(balance) values (100)";


    @Override
    public void save() throws RuntimeException {
        System.out.println("==開始執行sql");
        jdbcTemplate.update(insert_sql);
        System.out.println("==結束執行sql");

        System.out.println("==准備拋出異常");
        throw new RuntimeException("==手動拋出一個異常");
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
  • 配置文件
<?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:tx="http://www.springframework.org/schema/tx"
       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">

    <!--開啟tx注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

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

    <!--數據源-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="chenhao1991@"/>
    </bean>

    <!--jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--業務bean-->
    <bean id="accountService" class="com.chenhao.aop.AccountServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

</beans>
  • 測試
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: ChenHao
 * @create: 2019-10-08 18:45
 */
public class MyTest {

    @Test
    public void test1() {
        // 基於tx標簽的聲明式事物
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
        AccountServiceImp studentService = ctx.getBean("accountService", AccountServiceImp.class);
        studentService.save();
    }
}
  • 測試
==開始執行sql
==結束執行sql
==准備拋出異常

java.lang.RuntimeException: ==手動拋出一個異常

    at com.lyc.cn.v2.day09.AccountServiceImpl.save(AccountServiceImpl.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

測試方法中手動拋出了一個異常,Spring會自動回滾事物,查看數據庫可以看到並沒有新增記錄。

注意:默認情況下Spring中的事務處理只對RuntimeException方法進行回滾,所以,如果此處將RuntimeException替換成普通的Exception不會產生回滾效果。

接下來我們就分析基於@Transactional注解的聲明式事物的的源碼實現。

 
 

 


免責聲明!

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



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