Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理
正文
面的幾個(gè)章節(jié)已經(jīng)分析了spring基于@AspectJ
的源碼,那么接下來我們分析一下Aop的另一個(gè)重要功能,事物管理
事務(wù)的介紹
1.數(shù)據(jù)庫事物特性
- 原子性
多個(gè)數(shù)據(jù)庫操作是不可分割的,只有所有的操作都執(zhí)行成功,事物才能被提交;只要有一個(gè)操作執(zhí)行失敗,那么所有的操作都要回滾,數(shù)據(jù)庫狀態(tài)必須回復(fù)到操作之前的狀態(tài) - 一致性
事物操作成功后,數(shù)據(jù)庫的狀態(tài)和業(yè)務(wù)規(guī)則必須一致。例如:從A賬戶轉(zhuǎn)賬100元到B賬戶,無論數(shù)據(jù)庫操作成功失敗,A和B兩個(gè)賬戶的存款總額是不變的。 - 隔離性
當(dāng)并發(fā)操作時(shí),不同的數(shù)據(jù)庫事物之間不會相互干擾(當(dāng)然這個(gè)事物隔離級別也是有關(guān)系的) - 持久性
事物提交成功之后,事物中的所有數(shù)據(jù)都必須持久化到數(shù)據(jù)庫中。即使事物提交之后數(shù)據(jù)庫立刻崩潰,也需要保證數(shù)據(jù)能能夠被恢復(fù)。
2.事物隔離級別
當(dāng)數(shù)據(jù)庫并發(fā)操作時(shí),可能會引起臟讀、不可重復(fù)讀、幻讀、第一類丟失更新、第二類更新丟失等現(xiàn)象。
- 臟讀
事物A讀取事物B尚未提交的更改數(shù)據(jù),并做了修改;此時(shí)如果事物B回滾,那么事物A讀取到的數(shù)據(jù)是無效的,此時(shí)就發(fā)生了臟讀。 - 不可重復(fù)讀
一個(gè)事務(wù)執(zhí)行相同的查詢兩次或兩次以上,每次都得到不同的數(shù)據(jù)。如:A事物下查詢賬戶余額,此時(shí)恰巧B事物給賬戶里轉(zhuǎn)賬100元,A事物再次查詢賬戶余額,那么A事物的兩次查詢結(jié)果是不一致的。 - 幻讀
A事物讀取B事物提交的新增數(shù)據(jù),此時(shí)A事物將出現(xiàn)幻讀現(xiàn)象?;米x與不可重復(fù)讀容易混淆,如何區(qū)分呢?幻讀是讀取到了其他事物提交的新數(shù)據(jù),不可重復(fù)讀是讀取到了已經(jīng)提交事物的更改數(shù)據(jù)(修改或刪除)
對于以上問題,可以有多個(gè)解決方案,設(shè)置數(shù)據(jù)庫事物隔離級別就是其中的一種,數(shù)據(jù)庫事物隔離級別分為四個(gè)等級,通過一個(gè)表格描述其作用。
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻象讀 |
---|---|---|---|
READ UNCOMMITTED | 允許 | 允許 | 允許 |
READ COMMITTED | 臟讀 | 允許 | 允許 |
REPEATABLE READ | 不允許 | 不允許 | 允許 |
SERIALIZABLE | 不允許 | 不允許 | 不允許 |
3.Spring事物支持核心接口
- TransactionDefinition-->定義與spring兼容的事務(wù)屬性的接口
public interface TransactionDefinition { // 如果當(dāng)前沒有事物,則新建一個(gè)事物;如果已經(jīng)存在一個(gè)事物,則加入到這個(gè)事物中。 int PROPAGATION_REQUIRED = 0; // 支持當(dāng)前事物,如果當(dāng)前沒有事物,則以非事物方式執(zhí)行。 int PROPAGATION_SUPPORTS = 1; // 使用當(dāng)前事物,如果當(dāng)前沒有事物,則拋出異常。 int PROPAGATION_MANDATORY = 2; // 新建事物,如果當(dāng)前已經(jīng)存在事物,則掛起當(dāng)前事物。 int PROPAGATION_REQUIRES_NEW = 3; // 以非事物方式執(zhí)行,如果當(dāng)前存在事物,則掛起當(dāng)前事物。 int PROPAGATION_NOT_SUPPORTED = 4; // 以非事物方式執(zhí)行,如果當(dāng)前存在事物,則拋出異常。 int PROPAGATION_NEVER = 5; // 如果當(dāng)前存在事物,則在嵌套事物內(nèi)執(zhí)行;如果當(dāng)前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同 int PROPAGATION_NESTED = 6; // 使用后端數(shù)據(jù)庫默認(rèn)的隔離級別。 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; // 默認(rèn)超時(shí)時(shí)間 int TIMEOUT_DEFAULT = -1; // 獲取事物傳播特性 int getPropagationBehavior(); // 獲取事物隔離級別 int getIsolationLevel(); // 獲取事物超時(shí)時(shí)間 int getTimeout(); // 判斷事物是否可讀 boolean isReadOnly(); // 獲取事物名稱 @Nullable String getName(); }
- Spring事物傳播特性表:
傳播特性名稱 | 說明 |
---|---|
PROPAGATION_REQUIRED | 如果當(dāng)前沒有事物,則新建一個(gè)事物;如果已經(jīng)存在一個(gè)事物,則加入到這個(gè)事物中 |
PROPAGATION_SUPPORTS | 支持當(dāng)前事物,如果當(dāng)前沒有事物,則以非事物方式執(zhí)行 |
PROPAGATION_MANDATORY | 使用當(dāng)前事物,如果當(dāng)前沒有事物,則拋出異常 |
PROPAGATION_REQUIRES_NEW | 新建事物,如果當(dāng)前已經(jīng)存在事物,則掛起當(dāng)前事物 |
PROPAGATION_NOT_SUPPORTED | 以非事物方式執(zhí)行,如果當(dāng)前存在事物,則掛起當(dāng)前事物 |
PROPAGATION_NEVER | 以非事物方式執(zhí)行,如果當(dāng)前存在事物,則拋出異常 |
PROPAGATION_NESTED | 如果當(dāng)前存在事物,則在嵌套事物內(nèi)執(zhí)行;如果當(dāng)前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同 |
- Spring事物隔離級別表:
事務(wù)隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
讀未提交(read-uncommitted) | 是 | 是 | 是 |
不可重復(fù)讀(read-committed) | 否 | 是 | 是 |
可重復(fù)讀(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
MySQL默認(rèn)的事務(wù)隔離級別為 可重復(fù)讀repeatable-read
PlatformTransactionManager-->Spring事務(wù)基礎(chǔ)結(jié)構(gòu)中的中心接口
public interface PlatformTransactionManager { // 根據(jù)指定的傳播行為,返回當(dāng)前活動(dòng)的事務(wù)或創(chuàng)建新事務(wù)。 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; // 就給定事務(wù)的狀態(tài)提交給定事務(wù)。 void commit(TransactionStatus status) throws TransactionException; // 執(zhí)行給定事務(wù)的回滾。 void rollback(TransactionStatus status) throws TransactionException; }
Spring將事物管理委托給底層的持久化框架來完成,因此,Spring為不同的持久化框架提供了不同的PlatformTransactionManager接口實(shí)現(xiàn)。列舉幾個(gè)Spring自帶的事物管理器:
事物管理器 | 說明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 提供對單個(gè)javax.sql.DataSource事務(wù)管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事務(wù)管理 |
org.springframework.orm.jpa.JpaTransactionManager | 提供對單個(gè)javax.persistence.EntityManagerFactory事務(wù)支持,用于集成JPA實(shí)現(xiàn)框架時(shí)的事務(wù)管理 |
org.springframework.transaction.jta.JtaTransactionManager | 提供對分布式事務(wù)管理的支持,并將事務(wù)管理委托給Java EE應(yīng)用服務(wù)器事務(wù)管理器 |
- TransactionStatus-->事物狀態(tài)描述
- TransactionStatus接口
public interface TransactionStatus extends SavepointManager, Flushable { // 返回當(dāng)前事務(wù)是否為新事務(wù)(否則將參與到現(xiàn)有事務(wù)中,或者可能一開始就不在實(shí)際事務(wù)中運(yùn)行) boolean isNewTransaction(); // 返回該事務(wù)是否在內(nèi)部攜帶保存點(diǎn),也就是說,已經(jīng)創(chuàng)建為基于保存點(diǎn)的嵌套事務(wù)。 boolean hasSavepoint(); // 設(shè)置事務(wù)僅回滾。 void setRollbackOnly(); // 返回事務(wù)是否已標(biāo)記為僅回滾 boolean isRollbackOnly(); // 將會話刷新到數(shù)據(jù)存儲區(qū) @Override void flush(); // 返回事物是否已經(jīng)完成,無論提交或者回滾。 boolean isCompleted(); }
- SavepointManager接口
public interface SavepointManager { // 創(chuàng)建一個(gè)新的保存點(diǎn)。 Object createSavepoint() throws TransactionException; // 回滾到給定的保存點(diǎn)。 // 注意:調(diào)用此方法回滾到給定的保存點(diǎn)之后,不會自動(dòng)釋放保存點(diǎn), // 可以通過調(diào)用releaseSavepoint方法釋放保存點(diǎn)。 void rollbackToSavepoint(Object savepoint) throws TransactionException; // 顯式釋放給定的保存點(diǎn)。(大多數(shù)事務(wù)管理器將在事務(wù)完成時(shí)自動(dòng)釋放保存點(diǎn)) 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='--賬戶表'
- 實(shí)現(xiàn)
import org.apache.commons.dbcp.BasicDataSource; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; public class MyTransaction { private JdbcTemplate jdbcTemplate; private DataSourceTransactionManager txManager; private DefaultTransactionDefinition txDefinition; private String insert_sql = "insert into account (balance) values ('100')"; public void save() { // 1、初始化jdbcTemplate DataSource dataSource = getDataSource(); jdbcTemplate = new JdbcTemplate(dataSource); // 2、創(chuàng)建物管理器 txManager = new DataSourceTransactionManager(); txManager.setDataSource(dataSource); // 3、定義事物屬性 txDefinition = new DefaultTransactionDefinition(); txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 3、開啟事物 TransactionStatus txStatus = txManager.getTransaction(txDefinition); // 4、執(zhí)行業(yè)務(wù)邏輯 try { jdbcTemplate.execute(insert_sql); //int i = 1/0; jdbcTemplate.execute(insert_sql); txManager.commit(txStatus); } catch (DataAccessException e) { txManager.rollback(txStatus); e.printStackTrace(); } } public DataSource getDataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8"); dataSource.setUsername("root"); dataSource.setPassword("dabin1991@"); return dataSource; } }
- 測試類及結(jié)果
public class MyTest { @Test public void test1() { MyTransaction myTransaction = new MyTransaction(); myTransaction.save(); } }
運(yùn)行測試類,在拋出異常之后手動(dòng)回滾事物,所以數(shù)據(jù)庫表中不會增加記錄。
基于@Transactional注解的聲明式事物
其底層建立在 AOP 的基礎(chǔ)之上,對方法前后進(jìn)行攔截,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個(gè)事務(wù),在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。通過聲明式事物,無需在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼,只需在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或通過等價(jià)的基于標(biāo)注的方式),便可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中。
- 接口
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Transactional(propagation = Propagation.REQUIRED) public interface AccountServiceImp { void save() throws RuntimeException; }
- 實(shí)現(xiàn)
import org.springframework.jdbc.core.JdbcTemplate; 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("==開始執(zhí)行sql"); jdbcTemplate.update(insert_sql); System.out.println("==結(jié)束執(zhí)行sql"); System.out.println("==準(zhǔn)備拋出異常"); throw new RuntimeException("==手動(dòng)拋出一個(gè)異常"); } 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> <!--數(shù)據(jù)源--> <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&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="dabin1991@"/> </bean> <!--jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!--業(yè)務(wù)bean--> <bean id="accountService" class="com.dabin.aop.AccountServiceImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> </beans>
- 測試
import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test1() { // 基于tx標(biāo)簽的聲明式事物 ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml"); AccountServiceImp studentService = ctx.getBean("accountService", AccountServiceImp.class); studentService.save(); } }
- 測試
==開始執(zhí)行sql
==結(jié)束執(zhí)行sql
==準(zhǔn)備拋出異常java.lang.RuntimeException: ==手動(dòng)拋出一個(gè)異常
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)
測試方法中手動(dòng)拋出了一個(gè)異常,Spring會自動(dòng)回滾事物,查看數(shù)據(jù)庫可以看到并沒有新增記錄。
注意:默認(rèn)情況下Spring中的事務(wù)處理只對RuntimeException方法進(jìn)行回滾,所以,如果此處將RuntimeException替換成普通的Exception不會產(chǎn)生回滾效果。
后面在分析基于@Transactional注解的聲明式事物的的源碼實(shí)現(xiàn),
以上就是Spring @Transactional注解的聲明式事務(wù)簡化業(yè)務(wù)邏輯中的事務(wù)管理的詳細(xì)內(nèi)容,更多關(guān)于Spring @Transactiona聲明式事務(wù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Idea2020 無法share項(xiàng)目到svn的解決方法
這篇文章主要介紹了Idea2020 無法share項(xiàng)目到svn的解決方法,需要的朋友可以參考下2020-09-09Java 動(dòng)態(tài)加載jar和class文件實(shí)例解析
這篇文章主要介紹了Java 動(dòng)態(tài)加載jar和class文件實(shí)例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02Sentinel熱門詞匯限流的實(shí)現(xiàn)詳解
這篇文章主要介紹了使用Sentinel對熱門詞匯進(jìn)行限流的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Java基礎(chǔ)之打印萬年歷的簡單實(shí)現(xiàn)(案例)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)之打印萬年歷的簡單實(shí)現(xiàn)(案例)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07