Maven 整合 Jedis

在 Maven 中引入 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
25
class DemoTest {
private Jedis jedis;

@BeforeEach // 建立 Redis 链接
public void getReids() {
jedis = new Jedis("server.xorex.space", 6379);
jedis.auth("tempestxorex");
}

@Test // 按照使用 Redis 命令名字来调用方法
public void test() {
String result = jedis.set("tempest", "xorex");
System.out.println(result);

result = jedis.get("tempest");
System.out.println(result);
}

@AfterEach // 关闭 Redis 链接
public void closeRedis() {
if (jedis != null) {
jedis.close();
}
}
}

Redis 虽然是原子性线程安全的,但是 Jedis 在获取和修改 Redis 的时候的代码,是线程不安全的。也就是如果多个 Java 线程共享一个 jedis 链接实例,就会出现线程不安全问题。为了解决这个问题,可以建立多个 jedis 的连接池,为每个 Java 线程分配不同的 jedis 连接实例。

创建连接池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JedisConnectionPoolFactory {
private static final JedisPool jedisPool;

// 设置为静态的,一被加载就创建连接池
static {
JedisPoolConfig config = new JedisPoolConfig(); // 获取连接池配置
config.setMaxTotal(8); // 设置最大连接数量
config.setMinIdle(8); // 设置最小连接数量
config.setMaxWait(Duration.ZERO); // 设置最大等待时间

// 通过 JedisPoolConfig配置信息 host地址 端口 最大连接时间 密码 来创建连接池
jedisPool = new JedisPool(config, "server.xorex.space", 6379, 1000, "tempestxorex");
}

public Jedis getJedis() {
return jedisPool.getResource(); // 返回池子的 Jedis 连接
}
}

使用连接池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DemoTest {
private Jedis jedis;

@BeforeEach
public void getReids() {
//从连接池获取 jedis 连接
jedis = JedisConnectionPoolFactory.getJedis();
}

@AfterEach
public void closeRedis() {
if (jedis != null) {
// 如果存在连接池,close 的时候会将连接归还而不是关闭
jedis.close();
}
}
}

SpringBoot 整合 Jedis

前置操作

这里是使用 Spring 全家桶中的 SpringData 系列(专注于数据处理)的 SpringDataRedis,它封装了第三方的 Redis Java 客户端,提供了统一的操作接口。

使用依赖:

1
2
3
4
5
6
7
8
9
10
<!--Redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖 lettuce 和 jedis 都用它-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

引入完成之后,就是在 SpringBoot 的配置文件里面去配置 Redis 的信息。需要注意的是,SpringDataRedis 默认只下载了 lettuce 客户端,如果需要用 Jedis,那么就要引入 Jedis 的 Maven 了。版本号 SpringBoot 会仲裁。

1
2
3
4
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

完成配置:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
redis:
host: xxxxxxxx
port: 6379
password: xxxxxxx
database: 0
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: 100ms
min-idle: 0

SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作 API 封装到了不同的类型中:

API 返回值类型 说明
redisTemplate.opsForValue() ValueOperations 操作String类型数据
redisTemplate.opsForHash() HashOperations 操作Hash类型数据
redisTemplate.opsForList() ListOperations 操作List类型数据
redisTemplate.opsForSet() SetOperations 操作Set类型数据
redisTemplate.opsForZSet() ZSetOperations 操作SortedSet类型数据
redisTemplate 通用的命令

操作 Redis:

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest
public class JedisTest {
@Autowired // 自动注入 RedisTemplate
private RedisTemplate redisTemplate;

@Test
public void getJedis() {
// 获取 ValueOperations 来操作 String 类型数据
redisTemplate.opsForValue().set("testKey", "testValue");
}
}

对象序列化存储问

RedisTemplate 的两种序列化实践方案:

方案一:

  • 自定义 RedisTemplate
  • 修改 RedisTemplate 的序列化器为 GenericJackson2JsonRedisSerializer

方案二:

  • 使用 StringRedisTemplate
  • 写入 Redis 时,手动把对象序列化为 JSON
  • 读取 Redis 时,手动把读取到的 JSON 反序列化为对象

方案一:

需要注意的是, RedisTemplate 操作的时候,输入的 Key 和 Value 都是 Object 对象,那么就需要有专门的工具类将对象转换为 Redis 需要的 Key 和 Value 字符串。

这个专门工具类的设置,是作用在 RedisTemplate 上面的,需要我们去修改他的参数。那么在 SpringBoot 中很好解决,只需要我们自定义一个 RedisTemplate,然后注入到 Spring 的 IOC 容器就可以了。这样我们 @AutoWired 拿到的就是我们自定义的 RedisTemplate 而不是默认创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class RedisConfig {
@Bean // 向 IOC 容器中注入我们自己创建的 RedisTemplate
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { // 方法参数 connectionFactory 会从 IOC 容器中自己找
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// 设置连接工厂,使得 RedisTemplate 可以获取连接池中的 Redis 连接
redisTemplate.setConnectionFactory(connectionFactory);

// 设置 Key 的序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());

// 设置 Value 的序列化
redisTemplate.setValueSerializer(RedisSerializer.json());
redisTemplate.setHashValueSerializer(RedisSerializer.json());

return redisTemplate;
}
}

这样以后 @AutoWired 获取的 RedisTemplate 就是我们自己设置的了,中文编码也就不会出问题了。

  • 自动完成对象和 Json 字符串的序列化和反序列化:
1
2
3
4
5
6
7
8
public void test() {
redisTemplate.opsForValue().set("user:100",new User(100,"Xorex",20));

// 从 Redis 中取出 Json 字符串,然后序列化成一个对象
User user = (User) redisTemplate.opsForValue().get("user:100");
// 反序列化过程是,先创建对象,然后使用 setter 方法进行填充
System.out.println(user);
}
  • 自动反序列化的结果
1
2
3
4
5
6
{
"@class": "space.xorex.boot.pojo.User", // 因为保存了 Class 信息,所以可以自动反序列化为对象
"id": 101,
"userName": "你好",
"age": 20
}

方案二:

因为自动反序列化的结果多了一个 @Class,比较占用内存,我们可以通过将 Value 也设置为 String 序列化,这样就没有 @Class 了,但是这样做的坏处就是 get() 获取的 Redis 数据没有办法自动反序列化为对象了,需要我们手动完成。

Spring 默认提供了一个 StringRedisTemplate 类,它的 key 和 value 的序列化方式默认就是 String 方式。省去了我们自定义 RedisTemplate 的过程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void test() throws JsonProcessingException {
// jackson 提供的 json 字符串和对象互相转换的工具
ObjectMapper mapper = new ObjectMapper();

User user = new User(101,"你好",20);

// 获取 user 的 json
String json = mapper.writeValueAsString(user);

// 写入 key value
stringRedisTemplate.opsForValue().set("user:101",json);

// 读取 key value
String value = stringRedisTemplate.opsForValue().get("user:101");

// 将读取的字符串结果转换为对象
User newUser = mapper.readValue(json, User.class);

System.out.println(newUser);
}
  • 这样的序列化结果:
1
2
3
4
5
{
"id": 101,
"userName": "你好",
"age": 20
}