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

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

事務的介紹

1.數據庫事物特性

  • 原子性
    多個數據庫操做是不可分割的,只有全部的操做都執行成功,事物才能被提交;只要有一個操做執行失敗,那麼全部的操做都要回滾,數據庫狀態必須回覆到操做以前的狀態
  • 一致性
    事物操做成功後,數據庫的狀態和業務規則必須一致。例如:從A帳戶轉帳100元到B帳戶,不管數據庫操做成功失敗,A和B兩個帳戶的存款總額是不變的。
  • 隔離性
    當併發操做時,不一樣的數據庫事物之間不會相互干擾(固然這個事物隔離級別也是有關係的)
  • 持久性
    事物提交成功以後,事物中的全部數據都必須持久化到數據庫中。即便事物提交以後數據庫馬上崩潰,也須要保證數據能可以被恢復。

2.事物隔離級別

當數據庫併發操做時,可能會引發髒讀、不可重複讀、幻讀、第一類丟失更新、第二類更新丟失等現象。java

  • 髒讀
    事物A讀取事物B還沒有提交的更改數據,並作了修改;此時若是事物B回滾,那麼事物A讀取到的數據是無效的,此時就發生了髒讀。
  • 不可重複讀
    一個事務執行相同的查詢兩次或兩次以上,每次都獲得不一樣的數據。如:A事物下查詢帳戶餘額,此時恰巧B事物給帳戶裏轉帳100元,A事物再次查詢帳戶餘額,那麼A事物的兩次查詢結果是不一致的。
  • 幻讀
    A事物讀取B事物提交的新增數據,此時A事物將出現幻讀現象。幻讀與不可重複讀容易混淆,如何區分呢?幻讀是讀取到了其餘事物提交的新數據,不可重複讀是讀取到了已經提交事物的更改數據(修改或刪除)

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

隔離級別 髒讀 不可重複讀 幻象讀
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

若是當前存在事物,則在嵌套事物內執行;spring

若是當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同sql

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

mysql默認的事務隔離級別爲 可重複讀repeatable-read數據庫

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

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         // 一、初始化jdbcTemplate
26         DataSource dataSource = getDataSource(); 27         jdbcTemplate = new JdbcTemplate(dataSource); 28 
29         // 二、建立物管理器
30         txManager = new DataSourceTransactionManager(); 31  txManager.setDataSource(dataSource); 32 
33         // 三、定義事物屬性
34         txDefinition = new DefaultTransactionDefinition(); 35  txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); 36 
37         // 三、開啓事物
38         TransactionStatus txStatus = txManager.getTransaction(txDefinition); 39 
40         // 四、執行業務邏輯
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註解的聲明式事物的的源碼實現。

 
 

 

原文出處:https://www.cnblogs.com/java-chen-hao/p/11635525.html

相關文章
相關標籤/搜索