Redis之事务

1.基础了解

Redis事务的本质是一组命令的集合。
事务支持一次性执行多个命令,一个事务中所有命令都会被序列化。
在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务是没有隔离级别的概念的:
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,事务内可以查询到数据事务外则不行

Redis事务不保证原子性:
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务的三个阶段:
开始事务
命令入队
执行事务

Redis事务相关命令:
watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控

2.环境

        <!-- RedisTemplate -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring:
  redis:
    host: 192.168.8.128
    port: 6379
    password: 1234
    database: 0
    timeout: 3000
    jedis:
      pool:
        max-wait: -1
        max-active: -1
        max-idle: 20
        min-idle: 10

在这里插入图片描述

3.Redis配置

package com.yzm.redis03.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class ObjectMapperConfig {

    private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";

    @Bean(name = "myObjectMapper")
    public ObjectMapper objectMapper() {
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
        return new ObjectMapper()
                // 转换为格式化的json(控制台打印时,自动格式化规范)
                //.enable(SerializationFeature.INDENT_OUTPUT)
                // Include.ALWAYS  是序列化对像所有属性(默认)
                // Include.NON_NULL 只有不为null的字段才被序列化,属性为NULL 不序列化
                // Include.NON_EMPTY 如果为null或者 空字符串和空集合都不会被序列化
                // Include.NON_DEFAULT 属性为默认值不序列化
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                // 如果是空对象的时候,不抛异常
                .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
                // 反序列化的时候如果多了其他属性,不抛出异常
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                // 取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
                .setDateFormat(new SimpleDateFormat(PATTERN))
                // 对LocalDateTime序列化跟反序列化
                .registerModule(javaTimeModule)

                .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
                // 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
                .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
                ;
    }

    static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(DateTimeFormatter.ofPattern(PATTERN)));
        }
    }

    static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
            return LocalDateTime.parse(p.getValueAsString(), DateTimeFormatter.ofPattern(PATTERN));
        }
    }
}
package com.yzm.redis03.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.time.Duration;

@Configuration
public class RedisConfig {

    @Resource(name = "myObjectMapper")
    private ObjectMapper objectMapper;

    /**
     * redisTemplate配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer<Object> jacksonSerializer = jackson2JsonRedisSerializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 使用StringRedisSerializer来序列化和反序列化redis的key,value采用json序列化
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jacksonSerializer);

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        template.afterPropertiesSet();

        //支持事务
        //template.setEnableTransactionSupport(true);

        return template;
    }

    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        jacksonSerializer.setObjectMapper(objectMapper);
        return jacksonSerializer;
    }
}

4.事务支持

首先看下开启事务支持的示例

    public void test01() {
    	// 开启事务支持
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.multi();
        redisTemplate.opsForValue().set("k1", "v1");
        redisTemplate.opsForValue().set("k2", "v2");
        redisTemplate.opsForValue().get("k2");
        redisTemplate.opsForValue().set("k3", "v3");
        List<Object> exec = redisTemplate.exec();
        System.out.println("exec = " + exec);
    }
    @GetMapping("/test01")
    public void test01() {
        transactionUtils.test01();
    }

http://localhost:8080/test01
在这里插入图片描述
直接报错:ERR EXEC without MULTI
意思是说,没有启动事务,但这里启动了的redisTemplate.multi();
问题也就在redisTemplate.multi();这个方法上,通过底层源码可以发现该方法在最后会释放连接
在这里插入图片描述
导致multi()方法跟exec()方法不在同一连接上。

要想执行事务命令,必须在同一个连接上才行。
SessionCallback & RedisCallback 的作用是:让RedisTemplate进行回调,通过它们可以在同一条连接下执行多个Redis命令。
这里的Redis事务支持是基于SessionCallback实现的,RedisCallback这里不作演示。

5.事务提交

    /**
     * 事务提交
     */
    public void test02() {
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set("k1", 1);
                operations.opsForValue().set("k2", 2);
                operations.opsForValue().get("k2");
                operations.opsForValue().set("k3", 3);
                List exec = operations.exec();
                System.out.println("exec = " + exec);
                return "事务提交";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k2"));
    }

    @GetMapping("/test02")
    public void test02() {
        transactionUtils.test02();
    }

http://localhost:8080/test02
在这里插入图片描述
对应的redis操作
在这里插入图片描述

6.事务回滚

    /**
     * 事务回滚
     */
    public void test03() {
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set("k4", 4);
                operations.opsForValue().set("k5", 5);
                operations.discard();
                return "事务回滚";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k4"));
    }

http://localhost:8080/test03
在这里插入图片描述
redis操作
在这里插入图片描述

7.命令性事务回滚

错误的redis命令,导致事务回滚
在整合springboot中不会出现这样的错误,仅作了解即可
在这里插入图片描述

8.语法性事务提交

    /**
     * 语法错误
     */
    public void test04() {
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set("k6", 6);
                operations.opsForValue().set("k7", "7");
                // 字符串不能自增
                operations.opsForValue().increment("k7");
                operations.opsForValue().set("k8", 8);
                List exec = operations.exec();
                System.out.println("exec = " + exec);
                return "语法错误";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k6"));
    }

http://localhost:8080/test04
在这里插入图片描述
语法性的错误并不会导致事务回滚,只是对应的命令不会执行而已,k7的值仍是"7"
redis操作在这里插入图片描述
发现incr命令对数字字符串有效
在这里插入图片描述

9.程序异常事务回滚

    public void test04() {
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set("k6", 6);
                operations.opsForValue().set("k7", "7");
//                operations.opsForValue().increment("k7");
                int i = 1 / 0;
                operations.opsForValue().set("k8", 8);
                List exec = operations.exec();
                System.out.println("exec = " + exec);
                return "语法错误";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k6"));
    }

http://localhost:8080/test04
在这里插入图片描述

10.watch监控

正常事务提交

    public void test05() {
        redisTemplate.opsForValue().set("watch", "ok");
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch("watch");
                operations.multi();
                operations.opsForValue().set("k8", 8);
                operations.opsForValue().set("k9", 9);
                List exec = operations.exec();
                System.out.println("exec = " + exec);
                return "监控";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k8"));
    }

http://localhost:8080/test05
在这里插入图片描述
在这里插入图片描述

同一连接修改监控数据也能事务提交

    public void test06() {
        redisTemplate.opsForValue().set("watch2", "ok");
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch("watch2");
                operations.multi();
                operations.opsForValue().set("k10", 10);
                // 修改
                operations.opsForValue().set("watch2", "no");
                operations.opsForValue().set("k11", 11);
                List exec = operations.exec();
                System.out.println("exec = " + exec);
                return "监控";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k10"));
    }

http://localhost:8080/test06
在这里插入图片描述
在这里插入图片描述

其他连接修改监控数据导致事务回滚

    public void test07() {
        redisTemplate.opsForValue().set("watch3", "ok");
        SessionCallback<Object> callback = new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch("watch3");
                operations.multi();
                operations.opsForValue().set("k12", 10);
				// 休眠10s,让其他连接来修改watch3值
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                operations.opsForValue().set("k13", 11);
                List exec = operations.exec();
                System.out.println("exec = " + exec);
                return "监控";
            }
        };
        System.out.println(redisTemplate.execute(callback));
        System.out.println(redisTemplate.opsForValue().get("k12"));
    }
    @GetMapping("/test07")
    public void test07() {
        transactionUtils.test07();
    }

    @GetMapping("/test071")
    public void test071() {
    	// 修改watch3值
        redisTemplate.opsForValue().set("watch3", "no");
    }

先访问
http://localhost:8080/test07
10s访问
http://localhost:8080/test071
在这里插入图片描述
在这里插入图片描述

相关链接

首页
上一篇:方法封装
下一篇:jedis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值