Spring事務的實現方式和實現原理
Spring事務的本質其實就是數據庫對事務的支持,沒有數據庫的事務支持,spring是無法提供事務功能的。真正的數據庫層的事務提交和回滾是通過binlog或者redo log實現的。
什么是事務
數據庫事務是指作為單個邏輯工作單元執行的一系列操作,這些操作要么一起成功,要么一起失敗,是一個不可分割的工作單元。
在我們日常工作中,涉及到事務的場景非常多,一個 service 中往往需要調用不同的 dao 層方法,這些方法要么同時成功要么同時失敗,我們需要在 service 層確保這一點
事務的四大特性:A:原子性 C:一致性 I:隔離性 D:持久性
Spring支持的事務管理類型, spring 事務實現方式有哪些?
Spring支持兩種類型的事務管理:
編程式事務管理:這意味你通過編程的方式管理事務,給你帶來極大的靈活性,但是難維護。
聲明式事務管理:這意味著你可以將業務代碼和事務管理分離,你只需用注解和XML配置來管理事務。
Spring中事務的實現方式
1、編程式—實現事務
? 在applicationContext.xml中配置好數據源,和事務管理器:
以上這種方式 不推薦使用,代碼入侵太多。大量的處理事務的代碼穿插到業務代碼中
2、聲明式—實現事務
(1)、聲明式事務:xml形式 提前配置好數據源
- 配置事務管理器
- 配置通知,添加事務的切面
- Aop的織入,將切面和切入點綁定起來
(2)、configration配置類的形式配置聲明式事務
? 1、配置好數據源信息 2、配置事務管理器 3、開啟事務的注解支持
將該配置類添加到包掃描路徑下,接來下就可以直接在service的方法或者類上使用@Transactional注解給方法添加事務
(3)、xml+注解方式配置聲明式事務
配置完成后,只需要在想要開啟注解的方法上加上@Transactional注解就可以了
說一下Spring的事務傳播行為
spring事務的傳播行為說的是,當多個事務同時存在的時候,spring如何處理這些事務的行為。
① PROPAGATION_REQUIRED:默認的事務傳播,如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,該設置是最常用的設置。
② PROPAGATION_SUPPORTS:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。
③ PROPAGATION_MANDATORY:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常。
④ PROPAGATION_REQUIRES_NEW:創建新事務,無論當前存不存在事務,都創建新事務。
⑤ PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
⑥ PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
⑦ PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則按REQUIRED屬性執行。
在一個事務執行的過程中,調用另一個事務時候(比如一個service方法調用另一個service方法),這個事務將以何種狀態存在,是兩個事務共存呢,還是一個事務是另一個事務的子事務,還是一個事務加入另一個事務的子事務呢……利用事務的傳播性來解決這個問題。
? 1、REQUIRED: spring默認的事務的傳播性
? REQUIRED 表示如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
@Service
public class AccountService {@AutowiredJdbcTemplate jdbcTemplate;@Transactionalpublic void handle1() {jdbcTemplate.update("update user set money = ? where id=?;", 1, 2);}
}
@Service
public class AccountService2 {@AutowiredJdbcTemplate jdbcTemplate;@AutowiredAccountService accountService;public void handle2() {jdbcTemplate.update("update user set money = ? where username=?;", 1, "zhangsan");accountService.handle1();}
}
如果 handle2 方法本身是有事務的,則 handle1 方法就會加入到 handle2 方法所在的事務中,這樣兩個方法將處于同一個事務中,一起成功或者一起失敗(不管是 handle2 還是 handle1 誰拋異常,都會導致整體回滾)。
如果 handle2 方法本身是沒有事務的,則 handle1 方法就會自己開啟一個新的事務。
? 2、REQUIRES_NEW
? REQUIRES_NEW 表示創建一個新的事務,如果當前存在事務,則把當前事務掛起。換言之,不管外部方法是否有事務,REQUIRES_NEW 都會開啟自己的事務。
? 3、NESTED
? NESTED 表示如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于 TransactionDefinition.PROPAGATION_REQUIRED。
? 4、MANDATORY
? MANDATORY 表示如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
? 5、SUPPORTS
? NOT_SUPPORTED 表示以非事務方式運行,如果當前存在事務,則把當前事務掛起。
? 6、NOT_SUPPORTED
? NOT_SUPPORTED 表示以非事務方式運行,如果當前存在事務,則把當前事務掛起。
? 7、NEVER
? NEVER 表示以非事務方式運行,如果當前存在事務,則拋出異常。
spring事務的實現原理
? 底層是通過aop進行實現,@Transactional注解使用環繞通知,在進入方法前開啟事務 。使用try catch包含目標方法,執行目標方法,執行完成后如果沒有拋出異常,就提交事務。如果拋出異常就進行回滾。
代碼實現:
定義注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface rkTransactional {
}
切面:
@Aspect
@Component
@Slf4j
public class ExtrkThreadAop {@Autowiredprivate RkTransaction rkTransaction;/*** 只要方法上有加上rkTransactional 走around()* 異常通知* @param joinPoint* @throws Throwable*/@Around(value = "@annotation(com.rk.aop.rkTransactional)")public Object around(ProceedingJoinPoint joinPoint) {// 在目標方法之前開啟事務 底層實現:將事務狀態保存在當前線程里面TransactionStatus transactionStatus = rkTransaction.begin();try {Object result = joinPoint.proceed();//目標方法log.info("目標方法之后執行");//提交事務rkTransaction.commit(transactionStatus);return result;} catch (Throwable throwable) {// 目標方法執行向外拋出異常之后 手動回滾rkTransaction.rollback(transactionStatus);return "fail";}}
}
注解類:
@Component
public class RkTransaction {@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;// 開啟事務public TransactionStatus begin() {TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());return transaction;}// 提交事務public void commit(TransactionStatus transactionStatus) {dataSourceTransactionManager.commit(transactionStatus);}// 回滾事務public void rollback(TransactionStatus transactionStatus) {dataSourceTransactionManager.rollback(transactionStatus);}
}
test: 測試
/*** 使用事務注解 事務到底在什么時候提交呢?該方法沒有拋出異常的情況下就會自動提交事務* aop* @param name* @return*/@GetMapping("/insertUser")@rkTransactionalpublic String insertUser(String name) {int result = userMapper.insertUser(name);if ("rk".equals(name)) {int j = 1 / 0;}return result > 0 ? "ok" : "fail";}
}