Redis-笔记2

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
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<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() {
//1.建立连接
jedis = new Jedis("xx.xxx.xxx.xx",6379);
//2.设置密码
jedis.auth("123456");
//3.选择库
jedis.select(0);
//4.测试连接
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");
}
//获取Jedis对象
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对象
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>

<!--Jackson依赖 spring-boot-starter-web中已包含,若已存在无需重复导入-->
<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 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建 JSON 序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer
= new GenericJackson2JsonRedisSerializer();
// 设置 key 的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置 value 的序列化
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);
//写入到redis
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了