[SpringBoot]SpringBoot中使用redis事務


本文基於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

  1. REQUIRED - 方法A調用時候沒有事務新建一個事務,在方法A中調用方法B,將使用相同的事務,如果方法B發生異常需要回滾,整個事務回滾。

  2. REQUIRES_NEW - 方法A調用方法B時,無論是否存在事務都開啟一個新事務,這樣B方法異常不會導致A的數據回滾。

  3. NESTED - 和REQUIRES_NEW類似,但是只支持JDBC,不支持JPA或Hibernate

  4. SUPPORTS - 方法調用時有事務就用事務,沒事務就不用事務

  5. NOT_SUPPORTED - 強制方法不在事務中執行,若有事務,在方法調用到結束階段先掛起事務。

  6. NEVER - 強制不能有事務,若有事務就拋出異常

  7. MANDATORY - 強制必須有事務,如果沒有事務就拋出異常

  • rollbackFor

指定哪些異常可以導致事務回滾,默認是Throwable的子類

  • noRollbackFor

執行哪些異常不可用引起事務回滾,默認是Throwable的子類

2、@Transactional事務失效的情況

  1. 只對public方法生效。默認的protected和private方法上寫上@Transactional不會報錯,但該方法上的事務不生效,官方原文:Method visibility and @Transactional
  2. 默認情況(只寫@Transactional不填寫rollbackFor參數)下此注解會對unchecked異常進行回滾,對checked異常不回滾;
  3. 類內部未開啟事務的方法調用開啟事務的方法
    前兩條很好理解,針對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、示例代碼及參考:

示例代碼: redis-transaction

  1. Transaction Management
  2. Transaction Propagation
  3. Transactional Support
  4. 《Spring全家桶》丁雪豐

歡迎掃碼關注我的個人公眾號,獲取最新文章↓


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM