本文基於SpringBoot 2.X
事務在關系型數據庫的開發中經常用到,其實非關系型數據庫,比如redis也有對事務的支持,本文主要探討在SpringBoot中如何使用redis事務。
事務的相關介紹可以參考:
0、起因
在一次線上事故中,我們定位到redis的使用存在大value,超過了dubbo的最大數據量限制,於是緊急將這個大的對象value拆分成單個的string value。
為了保持數據庫和redis雙寫一致,在對數據庫進行更新,刪除,插入操作時,要從redis刪除指定的key。
一切都是使用redis的常規操作,但雷就埋在其中一個數據庫的update方法里,這個方法上開啟了事務@Transactional,導致里面的刪除redis key操作也加入了事務。
上線后出現報錯:
這個報錯明確指出,集群模式的redis不支持事務。集群不支持事務的原因可參考此文:Is there any Redis client (Java prefered) which supports transactions on Redis cluster?
基於此次問題,總結出本文內容
1、Spring中的事務
所有數據訪問技術都有事務機制,這些技術提供了API來開啟事務、提交事務完成數據操作, 或者在發生錯誤的時候回滾數據。
Spring采用統一的機制來處理不同的數據訪問技術的事務, Spring的事務提供一個PlatformTransactionManager
的接口,不同的數據訪問技術使用不同的接口實現。
數據訪問技術 | 實現 |
---|---|
JDBC | DataSourceTransactionManager |
JPA | JPATransactionManager |
Hibernate | HibernateTransactionManager |
JDO | JDOTransactionManager |
分布式事務 | JtaTransactionManager |
在SpringBoot中開啟事務非常簡單,只需要在方法或類上使用注解@Transactional即可。
Spring官方文檔中還要求使用@EnableTransactionManagement 開啟事務,但SpringBoot通過自動配置已經幫我們做了,所以SpringBoot中不用寫該注解
這里重點講下@Transactional注解的幾個常用屬性
-
propagation
事務的傳播機制,主要有以下幾種,默認是REQUIRED:
-
REQUIRED - 方法A調用時候沒有事務新建一個事務,在方法A中調用方法B,將使用相同的事務,如果方法B發生異常需要回滾,整個事務回滾。
-
REQUIRES_NEW - 方法A調用方法B時,無論是否存在事務都開啟一個新事務,這樣B方法異常不會導致A的數據回滾。
-
NESTED - 和REQUIRES_NEW類似,但是只支持JDBC,不支持JPA或Hibernate
-
SUPPORTS - 方法調用時有事務就用事務,沒事務就不用事務
-
NOT_SUPPORTED - 強制方法不在事務中執行,若有事務,在方法調用到結束階段先掛起事務。
-
NEVER - 強制不能有事務,若有事務就拋出異常
-
MANDATORY - 強制必須有事務,如果沒有事務就拋出異常
-
rollbackFor
指定哪些異常可以導致事務回滾,默認是Throwable的子類
-
noRollbackFor
執行哪些異常不可用引起事務回滾,默認是Throwable的子類
2、@Transactional事務失效的情況
- 只對public方法生效。默認的protected和private方法上寫上@Transactional不會報錯,但該方法上的事務不生效,官方原文:Method visibility and @Transactional;
- 默認情況(只寫@Transactional不填寫rollbackFor參數)下此注解會對unchecked異常進行回滾,對checked異常不回滾;
- 類內部未開啟事務的方法調用開啟事務的方法
前兩條很好理解,針對3,引用丁雪豐的《Spring全家桶》視頻中的解釋:
Spring的聲明式事務本質上是通過AOP來增強了類的功能
Spring的AOP本質上就是為類做了一個代理看似在調用自己寫的類,實際用的是增強后的代理類
下圖描述了方法被事務代理時的流程,來源:Spring AOP
3、SpringBoot整合Redis事務實踐
下面我們搭建一個最簡單的SpringBoot整合redis的工程用代碼來驗證redis事務
-
SpringBoot整合Redis
SpringBoot整合redis使用的是spring-boot-starter-data-redis
,redis事務依賴於jdbc的事務管理,所以還需要引入jdbc
pom相關引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
-
開啟Redis事務
編寫redis配置類,開啟redis事務,配置事務管理
@Configuration
public class RedisConfig {
@Bean
public StringRedisTemplate StringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
/**
* description 開啟redis事務(僅支持單機,不支持cluster)
**/
template.setEnableTransactionSupport(true);
return template;
}
/**
* description 配置事務管理器
**/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
-
代碼驗證
針對本文討論,設計了四個驗證方法,可自行驗證
/**
* description 不帶事務set
* return java.lang.String
* author 鄭曉龍
* createTime 2019/12/12 16:36
**/
@GetMapping("put")
public void put(String key, String value) {
redisService.put(key, value);
}
/**
* description 帶事務set
* return java.lang.String
* author 鄭曉龍
* createTime 2019/12/12 16:36
**/
@GetMapping("putWithTx")
public void putWithTx(String key, String value) {
redisService.putWithTx(key, value);
}
/**
* description 調用帶事務方法不生效的情況
* return java.lang.String
* author 鄭曉龍
* createTime 2019/12/12 16:36
**/
@GetMapping("invokeWithPutTx")
public void invokeWithPutTx(String key, String value) {
redisService.invokePutWithTx(key, value);
}
/**
* description 調用帶事務方法生效的情況
* return java.lang.String
* author 鄭曉龍
* createTime 2019/12/12 16:36
**/
@GetMapping("invokeWithPutTx2")
public void invokeWithPutTx2(String key, String value) {
redisService.invokePutWithTx2(key, value);
}
4、總結:
- redis事務只支持單機,不支持cluster
- 需要開啟事務時,只需要在對應的方法或類上使用@Transactional注解即可,SpringBoot自動開啟了@EnableTransactionManagement
- 需要注意事務不生效的幾種情況
- redis事務依賴於jdbc的事務管理
5、示例代碼及參考:
- Transaction Management
- Transaction Propagation
- Transactional Support
- 《Spring全家桶》丁雪豐
歡迎掃碼關注我的個人公眾號,獲取最新文章↓