学习视频链接,示以尊重:https://space.bilibili.com/95256449/video
Redis 事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行。(一次性、顺序性、排他性)
注意:
Redis 事务执行阶段:
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
discard 命令,会取消掉事务,事务队列中的命令都不会被执行。
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD #取消事务
OK
127.0.0.1:6379> get k4
(nil)
悲观锁:很悲观,认为什么时候都会出问题,无论做什么都会加锁
乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据
Redis 监视测试:(使用watch来当做redis的乐观锁操作)
正常执行成功:
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> WATCH money # 监视money对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 事务正常结束,数据期间没有发生变动,这个时候就可以正常执行成功
1) (integer) 80
2) (integer) 20
多线程操作值,可能会执行失败:
127.0.0.1:6379> watch money # 监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec # 执行事务之前,另外一个线程,修改了值,这个时候,就会导致事务执行失败!
(nil)
127.0.0.1:6379> unwatch money # 解除监视(解锁)
OK
如果事务执行失败,Redis会这么处理(自旋锁):
Jedis 是 Redis 官方推荐的 java 连接开发工具,是使用 Java 操作 Redis 的中间件。
对应依赖:
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.3.0version>
dependency>
连接数据库:
Jedis jedis = new Jedis("127.0.0.1", 6379);
进行事务操作:
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "xingyu");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
multi.watch("user1","user2");
try {
multi.set("user1", result);
multi.set("user2", result);
int i = 1 / 0; // 代码抛出异常事务,执行失败!
multi.exec(); // 执行事务!
} catch (Exception e) {
multi.discard(); // 放弃事务 e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 关闭连接
}
}
}
在 SpringBoot2.x 之后,原来使用的 jedis 被替换为了 lettuce
1、导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2、配置连接
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、测试
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
// redisConnection.flushAll();
redisTemplate.opsForValue().set("mykey","星宇");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 为了自己开发方便,一般直接使用
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om); // String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}