Redis学习笔记第二部分,关于事务、乐观锁、图形化界面和Java客户端( Jedis、JedisPool、SpringDataRedis )的操作
1.Redis事务
Redis的事务是按顺序执行一组命令集合,与MySQL不同,Redis的事务不存在隔离级别的概念,也没有原子性
其过程大致为:
- 开启事务 ( multi )
- 命令入队 ( 输入n条命令… )
- 执行事务 ( exec ) 或 放弃事务 ( discard )
以下是几种事务执行的不同情况
正常执行
放弃事务
若放弃事务,事务中的所有命令都不会被执行
编译型异常
比如下图中,命令写错,sett k3 v3 多写了一个t,或者是其他语法上的错误,这种就属于编译型异常
如果事务中出现编译型异常,那么事务中的所有命令都不会被执行
运行时异常
比如下图中,incr k2 并没有语法上的错误,但是运行时发现 incr 需要用在数值上,但k2却是字符串
像这种运行时才会报错的异常,只会影响出现错误的那条命令,错误会抛出,而其他命令可以正常执行
2.乐观锁
正常执行
使用watch命令监视money. 在事务的执行过程中,money的值没有被其他用户修改,则事务可以正常执行
出现异常
当我们使用watch监视money时,开启事务而未执行事务期间,如果另一个用户修改了money的值,那么这个事务中的所有命令都不会被执行。
此时出现异常后我们需要使用unwatch结束监视,然后再使用watch重新获取新值上锁,再执行事务
3.Redis图形化界面
下载地址:Releases · lework/RedisDesktopManager-Windows — Lework/RedisDesktopManager-Windows (github.com)
注意连接前,需要打开对应端口,默认为 6379 ,在使用云服务器时,通过管理安全组可能还是没法连接成功,可以这两条命令
firewall-cmd –add-port=6379/tcp –permanent
firewall-cmd –reload
4.Jedis及JedisPool
Jedis
导入依赖
1 2 3 4 5 6
| <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version> </dependency>
|
创建对象并测试
这里在springboot的测试类中进行,创建Jedis对象后,建立连接、设置密码、选择库后即可使用
通过Jedis对象,可以进行对Redis数据库的操作,操作的方法名基本与linux命令行中的命令一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @SpringBootTest class JedisDemoApplicationTests { private Jedis jedis;
@BeforeEach void setUp() { jedis = new Jedis("xx.xxx.xxx.xx",6379); jedis.auth("123456"); jedis.select(0); System.out.println(jedis.ping()); }
@Test public void test(){ String k1 = jedis.get("k1"); System.out.println("k1的值为"+k1); } @AfterEach void tearDown() { if(jedis!=null){ jedis.close(); } } }
|
JedisPool
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此推荐使用JedisPool连接池代替Jedis的
直连方式。
编写工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.example.jedis_demo.util; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig;
public class JedisConnectionFactory { private static final JedisPool jedisPool;
static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(100); config.setMaxIdle(10); config.setMinIdle(0); config.setMaxWaitMillis(1000); jedisPool = new JedisPool(config,"xx.xxx.xxx.xx",6379,1000,"123456"); } public static Jedis getJedis(){ return jedisPool.getResource(); }
}
|
通过工具类获取对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @SpringBootTest class JedisPooltest { private Jedis jedis;
@BeforeEach void setUp() { jedis = JedisConnectionFactory.getJedis(); }
@Test public void test(){ String k1 = jedis.get("k1"); System.out.println("k1的值为"+k1); }
@AfterEach void tearDown() { if(jedis!=null){ jedis.close(); } } }
|
5.SpringDataRedis
简介
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
使用方法
导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
|
在application.yaml中配置redis
1 2 3 4 5 6 7 8 9 10 11 12
| spring: redis: host: 47.113.202.66 port: 6379 password: 123456 database: 0 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: 1000ms
|
注入RedisTemplate并测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @SpringBootTest class RedisTemplateApplicationTests { @Resource @Autowired private RedisTemplate redisTemplate;
@Test void testString() { redisTemplate.opsForValue().set("name","李四"); Object name = redisTemplate.opsForValue().get("name"); System.out.println("name = " + name); }
}
|
最后虽然能够成功插入或查询,但在redis中查看时,却会看到乱码,这是由于RedisTemplate默认采用 JDK 序列化
因此需要在配置类中自定义序列化
自定义序列化
创建RedisConfig类进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.example.redisdemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import javax.annotation.Resource;
@Configuration public class RedisConfig { @Resource @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){ RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(RedisSerializer.string()); template.setHashKeySerializer(RedisSerializer.string()); template.setValueSerializer(jsonRedisSerializer); template.setHashValueSerializer(jsonRedisSerializer); return template; } }
|
完成自定义序列化后,在注入RedisTemplate时应加上泛型,即
1 2 3
| @Resource @Autowired private RedisTemplate<String,Object> redisTemplate;
|
再次测试
现在就不会乱码了
下面测试插入一个对象
1 2 3 4 5 6
| @Test public void testSaveUser(){ redisTemplate.opsForValue().set("user:001",new User("王五",18)); User o = (User) redisTemplate.opsForValue().get("user:001"); System.out.println(o); }
|
“@class”:”com.example.redisdemo.pojo.User”使得可以自动实现反序列化
最终方案-StringRedisTemplate
由于使用RedisTemplate在进行对象的序列化时,会自动在JSON中存入一个@class,这是一个很浪费资源的方式,而且RedisTemplate需要额外的自定义序列化,比较麻烦
所以改用StringRedisTemplate,结合ObjectMapper,我们手动完成序列化和反序列化
依赖和application配置还是和原来一样,只是RedisTemplate改为StringRedisTemplate
1 2 3 4
| @Autowired @Resource private StringRedisTemplate stringRedisTemplate; private static final ObjectMapper mapper = new ObjectMapper();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void testSaveUser2() throws JsonProcessingException { User user = new User("赵六",19); String json = mapper.writeValueAsString(user); stringRedisTemplate.opsForValue().set("user:002",json); String val = stringRedisTemplate.opsForValue().get("user:002"); User user1 = mapper.readValue(val, User.class); System.out.println("user1:"+user1); }
|
可以看到现在已经没有@class了