【redis事务】@Transactional对Redis事务起作用(包含redis+lua)

一、前言

最近工作中涉及到的事务比较多,也有用到redis事务,之前的文章也介绍过redis事务,为了方便redis使用,我们把redis事务结合springboot的@Transactional注解使用,这里为了方便大家使用,写一个demo验证一下;

注:同时也验证一下redis+lua脚本执行中,@Transactional注解事务是否生效

二、准备

为了对比事务,我们同时使用数据库(postgresql)事务redis事务

引入pom.xml依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <!-- postgresql 配置-->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

三、StringRedisTemplate 开启事务

package com.sk.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration
@RequiredArgsConstructor
public class RedisConfig {

    private final StringRedisTemplate stringRedisTemplate;

    @Bean
    public void getRedisTemplate(){
        stringRedisTemplate.setEnableTransactionSupport(true);
    }

}

四、关键代码(验证@Transactional对redis事务是否生效)

1、正常执行

package com.sk.service.impl;

import com.sk.mapper.StudentMapper;
import com.sk.service.AnimalService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("dogService")
@RequiredArgsConstructor
//@ConditionalOnProperty(prefix = "formatter",name="enabled",havingValue = "true")
public class DogServiceImpl implements AnimalService {

    private final StringRedisTemplate stringRedisTemplate;

    private final StudentMapper studentMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void play() {

        System.out.println("狗拿耗子!!!");
        boolean insert = studentMapper.insertUser(3,"刘英","男","123456","");
        System.out.println("pg插入结果:"+insert);
        stringRedisTemplate.opsForValue().set("3","liuying");
        System.out.println("==========执行完成======");
        //throw new RuntimeException("redis事务测试");
    }

    @Override
    public void eat() {
        System.out.println("狗爱吃骨头!!!");
    }
}

执行结果:

2022-12-11 11:46:37.268  INFO 9752 --- [nio-8089-exec-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
狗拿耗子!!!
pg插入结果:true
==========执行完成======

在这里插入图片描述

127.0.0.1:6379> get 3
"liuying"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> 

2、事务回滚

package com.sk.service.impl;

import com.sk.mapper.StudentMapper;
import com.sk.service.AnimalService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("dogService")
@RequiredArgsConstructor
//@ConditionalOnProperty(prefix = "formatter",name="enabled",havingValue = "true")
public class DogServiceImpl implements AnimalService {

    private final StringRedisTemplate stringRedisTemplate;

    private final StudentMapper studentMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void play() {

        System.out.println("狗拿耗子!!!");
        boolean insert = studentMapper.insertUser(3,"刘英","男","123456","");
        System.out.println("pg插入结果:"+insert);
        stringRedisTemplate.opsForValue().set("3","liuying");
        System.out.println("==========执行完成======");
        throw new RuntimeException("redis事务测试");
    }

    @Override
    public void eat() {
        System.out.println("狗爱吃骨头!!!");
    }
}

执行结果

2022-12-11 11:51:28.699  INFO 12864 --- [nio-8089-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
狗拿耗子!!!
pg插入结果:true
==========执行完成======
2022-12-11 11:51:29.978 ERROR 12864 --- [nio-8089-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: redis事务测试] with root cause

java.lang.RuntimeException: redis事务测试
	at com.sk.service.impl.DogServiceImpl.play(DogServiceImpl.java:28) ~[classes/:na]
	at com.sk.service.impl.DogServiceImpl$$FastClassBySpringCGLIB$$6e845405.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.23.jar:5.3.23]

在这里插入图片描述

127.0.0.1:6379> get 5
(nil)
127.0.0.1:6379> 
127.0.0.1:6379> 

由结果可知,pg数据库redis中数据并没有存储成功,证明事务生效;

五、关键代码(验证@Transactional对redis+lua是否生效)

1、lua脚本:

local key = KEYS[1]
redis.call("INCR",key);

2、初始化lua脚本:

/**
     * 读取限流脚本
     *
     * @return
     */
    @Bean
    public DefaultRedisScript<Number> redisluaScript() {
        DefaultRedisScript<Number> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/test.lua")));
        redisScript.setResultType(Number.class);
        return redisScript;
    }

3、正常执行

package com.sk.service.impl;

import com.sk.mapper.StudentMapper;
import com.sk.service.AnimalService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service("dogService")
@RequiredArgsConstructor
//@ConditionalOnProperty(prefix = "formatter",name="enabled",havingValue = "true")
public class DogServiceImpl implements AnimalService {

    private final StringRedisTemplate stringRedisTemplate;

    private final StudentMapper studentMapper;

    private final DefaultRedisScript defaultRedisScript;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void play() {
        System.out.println("执行lua脚本");
        List<String> keys = new ArrayList();
        keys.add("count");
        stringRedisTemplate.execute(defaultRedisScript, keys);
        System.out.println("==========执行完成======");
        //throw new RuntimeException("redis事务测试");
    }

    @Override
    public void eat() {
        System.out.println("狗爱吃骨头!!!");
    }
}

执行结果

127.0.0.1:6379> get count
"1"
127.0.0.1:6379> 
127.0.0.1:6379> 

4、执行失败

package com.sk.service.impl;

import com.sk.mapper.StudentMapper;
import com.sk.service.AnimalService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service("dogService")
@RequiredArgsConstructor
//@ConditionalOnProperty(prefix = "formatter",name="enabled",havingValue = "true")
public class DogServiceImpl implements AnimalService {

    private final StringRedisTemplate stringRedisTemplate;

    //private final StudentMapper studentMapper;

    private final DefaultRedisScript defaultRedisScript;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void play() {
        System.out.println("执行lua脚本");
        List<String> keys = new ArrayList();
        keys.add("count");
        stringRedisTemplate.execute(defaultRedisScript, keys);
        System.out.println("==========执行完成======");
        throw new RuntimeException("redis事务测试");
    }

    @Override
    public void eat() {
        System.out.println("狗爱吃骨头!!!");
    }
}

执行结果

执行lua脚本

==========执行完成======
2022-12-11 12:19:33.272 ERROR 12736 --- [nio-8089-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: redis事务测试] with root cause

java.lang.RuntimeException: redis事务测试
	at com.sk.service.impl.DogServiceImpl.play(DogServiceImpl.java:39) ~[classes/:na]
127.0.0.1:6379> get count
"1"
127.0.0.1:6379> 
127.0.0.1:6379> 

由上可知,当程序执行异常时,lua脚本中的内容同样没有生效

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
@RedisLock 和 @Transactional 是两个在代码中使用的注解。 @RedisLock 是一个自定义注解,用于在代码中实现分布式锁的功能。在引用和引用[2]的代码中,可以看到在执行某个方法之前先获取了一个分布式锁(redisLock.lock(id)),如果获取锁成功,则执行该方法的业务逻辑,然后释放锁(redisLock.unlock(id))。如果获取锁失败,则抛出异常表示当前操作无法进行。这样可以确保在分布式环境中同一时间只有一个线程能够执行被锁定的代码块。 @Transactional 是一个用于事务管理的注解。它可以用于类或方法上,用于标识需要被事务管理的代码块。在引用和引用的代码中,可以看到被 @Transactional 注解标记的方法表示该方法需要在事务中执行。在事务中,可以保证一系列的数据库操作要么全部成功提交,要么全部失败回滚。 需要注意的是,在引用中提到的一个问题是,当 @RedisLock 和 @Transactional 注解同时存在时,可能会导致分布式锁失效。因为事务的提交是在分布式锁释放之后进行的,这意味着在事务提交前,分布式锁已经被释放,可能导致其他线程获取到锁并同时执行被锁定的代码块。因此,在使用 @RedisLock 和 @Transactional 注解时需要特别注意这个问题,并确保在代码中正确处理分布式锁和事务的顺序。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【RedisRedis分布式锁的10个坑](https://blog.csdn.net/u011397981/article/details/130490197)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [java中锁与@Transactional同时使用导致锁失效的问题](https://blog.csdn.net/weixin_42134685/article/details/127090804)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值