學習的文章 [小姐姐非要問我:spring編程式事務是啥? (qq.com)](https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648936779&idx=2&sn=a6255c7d436a62af380dfa6b326fd4e7&chk ...
學習的文章
小姐姐非要問我:spring編程式事務是啥? (qq.com)
阿裡3面:Spring聲明式事務連環炮,讓我措手不及。。 (qq.com)
帶你讀懂Spring 事務——事務的傳播機制 - 知乎 (zhihu.com)
spring 事務失效的 12 種場景_事務什麼時候失效_hanjq_code的博客-CSDN博客
什麼是事務
-
事務是併發操作的單位
-
是用戶定義的操作序列
-
事務如果成功,則會提交
-
事務如果失敗,則會回滾
事務的四大特性
-
原子性
- 事務中操作,要麼不做,要麼都做
-
持久性
- 一個事務一旦提交,它對資料庫的改變是永久的
-
一致性
-
事務讓資料庫從一個一致性狀態轉移到另一個一致性狀態
-
比如
-
事務前,A有50,B有50,總共有100
-
事務中,A送給B20
-
事務後,A有30,B有70,總共還是有100
-
-
-
隔離性
-
一個事務的執行不能被其他事務所干擾
-
分成不同的等級
-
事務併發訪問導致的數據問題
臟讀
讀到了修改但還沒有提交的數據
-
A事務修改了某條記錄的欄位c,但還沒有提交
-
B事務在此時讀取了欄位c
-
A事務發生了回滾,欄位c恢復了修改前的狀態
-
但B事務持有的還是修改後的狀態
不可重覆讀
某個事務在執行過程中,兩次讀同一個條記錄,但結果不一樣
-
A事務讀取了記錄c,值為10
-
B事務修改了記錄c,值為0
-
A事務再次讀取記錄c,值為0
幻讀
某個事務在執行過程中,前後兩次讀取記錄,數據總量不一樣
-
A事務統計了表c中的記錄總數,結果為10
-
B事務刪除了表c中的5條記錄
-
A事務再次統計了表c中的記錄總數,結果為5
和不可重覆讀的區別在於,幻讀針對的是記錄條數,不可重覆讀針對的是記錄內容
事務的隔離級別
針對事務併發訪問時出現的問題,設置了四種事務的隔離級別
讀未提交
可以的讀取還沒有提交的數據
沒有限制,三種問題都有可能發生
讀已提交
只能讀取已經提交了的數據
不會發生臟讀,但是會發生不可重覆讀和幻讀
可重覆讀
一個事務前後讀取的同一條記錄的結果必須一致
不會發生臟讀和不可重覆讀,但會發生幻讀
mysql中預設的事務隔離級別
串列化
所有的事務必須依次執行
不會發生臟讀、不可重讀讀和幻讀
效率比較低
Spring事務的使用方法
Spring分為兩種控制事務的方法
-
編程式事務
-
方法1:通過PlatformTransactionManager控制事務
-
方法2:通過TransactionTemplate控制事務
-
-
聲明式事務
- 常用
編程式事務的使用
使用PlatformTransactionManager
@Test
public void test1() throws Exception {
//定義一個數據源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
//定義一個JdbcTemplate,用來方便執行資料庫增刪改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定義事務管理器,給其指定一個數據源(可以把事務管理器想象為一個人,這個人來負責事務的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定義事務屬性:TransactionDefinition,TransactionDefinition可以用來配置事務的屬性信息,比如事務隔離級別、事務超時時間、事務傳播方式、是否是只讀事務等等。
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.開啟事務:調用platformTransactionManager.getTransaction開啟事務操作,得到事務狀態(TransactionStatus)對象
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
//4.執行業務操作,下麵就執行2個插入操作
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
//5.提交事務:platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
} catch (Exception e) {
//6.回滾事務:platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
使用TransactionTemplate
@Test
public void test1() throws Exception {
//定義一個數據源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
//定義一個JdbcTemplate,用來方便執行資料庫增刪改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定義事務管理器,給其指定一個數據源(可以把事務管理器想象為一個人,這個人來負責事務的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定義事務屬性:TransactionDefinition,TransactionDefinition可以用來配置事務的屬性信息,比如事務隔離級別、事務超時時間、事務傳播方式、是否是只讀事務等等。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(10);//如:設置超時時間10s
//3.創建TransactionTemplate對象
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
/**
* 4.通過TransactionTemplate提供的方法執行業務操作
* 主要有2個方法:
* (1).executeWithoutResult(Consumer<TransactionStatus> action):沒有返回值的,需傳遞一個Consumer對象,在accept方法中做業務操作
* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要傳遞一個TransactionCallback對象,在doInTransaction方法中做業務操作
* 調用execute方法或者executeWithoutResult方法執行完畢之後,事務管理器會自動提交事務或者回滾事務。
* 那麼什麼時候事務會回滾,有2種方式:
* (1)transactionStatus.setRollbackOnly();將事務狀態標註為回滾狀態
* (2)execute方法或者executeWithoutResult方法內部拋出異常
* 什麼時候事務會提交?
* 方法沒有異常 && 未調用過transactionStatus.setRollbackOnly();
*/
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
@Override
public void accept(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
}
});
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
聲明式事務的使用
-
在配置類上使用@EnableTransactionManagement
- Springboot可以加在啟動類上
-
定義事務管理器
-
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
-
springboot中有預設的事務管理器
-
-
在需要事務的目標上加上@Transaction註解
-
作用位置
-
註意:@Transaction只對public方法有效
-
@Transacion放在介面上,介面的所有實現類中所有的public方法都自動加上事務
-
@Transaction放在類上,當前類以及其下無限級子類中的public方法都被加上事務
-
@Transaction放在public方法上,方法被加上事務
-
-
屬性
-
"transactionManager"或"value"
-
指定事務管理器的bean對象
-
為空的話,預設按類型獲取
-
-
“propagation”
-
指定事務的傳播類型
-
預設為REQUIRED
-
-
“rollbackFor”
- 自定義回滾異常
-
-
事務的傳播類型
-
REQUIRED
-
如果當前有事務,則加入當前事務
-
如果當前沒有事務,則自己新建一個事務
-
-
SUPPORTS
-
如果當前有事務,則加入當前事務
-
如果當前沒有事務,則以非事務方式執行
-
-
MANDATORY
-
如果當前有事務,則加入當前事務
-
如果當前沒有事務,則拋出異常
-
-
REQUIERES_NEW
-
如果當前有事務,則將該事務掛起,另外新創建一個事務
-
如果當前沒有事務,則新創建一個事務
-
-
NOT_SUPPORTED
-
如果當前有事務,則將該事務掛起,以非事務方式執行
-
如果當前沒有事務,則以非事務方式執行
-
-
NEVER
-
如果當前有事務,則拋出異常
-
如果當前沒有事務,以非事務方式執行
-
-
NESTED
-
如果當前有事務,則在當前事務中嵌套一個事務執行
-
如果當前沒有事務,則新建一個事務執行
-
事務失效或回滾異常的12種情況
事務失效
-
訪問許可權問題
- 事務只能對public的方法方法生效
-
方法用final或static修飾
-
spring事務是使用動態代理的方式實現的
-
如果加了final或public方法,則方法無法被代理
-
-
方法內部調用
-
@Service public class UserService { @Autowired private UserMapper userMapper; public void add(UserModel userModel) { userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional public void updateStatus(UserModel userModel) { doSameThing(); } }
-
在add方法中是通過this來調用updateStatus方法的,沒有通過代理
-
解決方法:
-
1.註入自己
-
@Servcie public class ServiceA { @Autowired prvate ServiceA serviceA; public void save(User user) { queryData1(); queryData2(); serviceA.doSave(user); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { addData1(); updateData2(); } }
-
-
2.通過AopContext.currentProxy()獲取代理對象
-
@Servcie public class ServiceA { public void save(User user) { queryData1(); queryData2(); ((ServiceA)AopContext.currentProxy()).doSave(user); } @Transactional(rollbackFor=Exception.class) public void doSave(User user) { addData1(); updateData2(); } }
-
-
-
-
未被spring管理
-
忘了給類添加註釋了
-
沒有被放到IOC容器中
-
-
多線程調用
-
spring事務是通過資料庫連接來實現的
-
在多線程中,每一個線程用的都是不同的資料庫連接
-
-
表不支持事務
- MyISAM引擎的表不支持事務
-
未開啟事務
-
springboot需要在啟動類上加上@EnableTransactionManagement的註解
-
傳統spring項目需要在applicationContext.xml中進行相關的配置
-
事務回滾異常
-
使用了錯誤的傳播特性
-
手動捕獲了異常
-
如果想要spring事務能夠正常回滾,必須拋出它能夠處理的異常
-
使用try/catch將異常捕獲,將導致事務不會回滾
-
-
拋得異常類型不正確
- spring事務,預設情況下只會回滾RuntimeException或Error
-
自定義回滾異常不匹配
- 比如定義了BusinessException,但拋出的是SqlException
-
嵌套事務回滾範圍多了
-
public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); roleService.doOtherThing(); } } @Service public class RoleService { @Transactional(propagation = Propagation.NESTED) public void doOtherThing() { System.out.println("保存role表數據"); } }
-
roleService.doOtherThing()
如果發生回滾,它拋出的異常沒有被處理,會繼續往上級拋 -
add()在捕獲到向上拋的異常後也會發生回滾
-
應該手動進行捕獲
-
@Slf4j @Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); try { roleService.doOtherThing(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
-
-