zoukankan      html  css  js  c++  java
  • [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全家桶》丁雪丰

    欢迎扫码关注我的个人公众号,获取最新文章↓

  • 相关阅读:
    函数集
    2019.1.7
    第九次团队作业-测试报告与用户使用手册
    α版本升升备忘录下载链接
    系统设计和任务分配(个人)
    团队作业说明
    备忘录-团队选题报告
    需求分析与原型设计---升升备忘录
    项目——四则运算器
    Hello Django
  • 原文地址:https://www.cnblogs.com/zhengxl5566/p/12028293.html
Copyright © 2011-2022 走看看