Jedis

Jedis是官网推荐的Java连接工具,类似就是在Java和Redis中增加一层辅助层,辅助你用Java使用Redis。

简单的测试一下使用。

首先创建一个maven项目,导入相关的依赖。

1
2
3
4
5
6
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.1</version>
</dependency>

连接使用Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yww;
import redis.clients.jedis.Jedis;

public class config {
public static void main(String[] args) {
// 创建Jedis对象,连接Redis,jedis对象相当于客户端
// Redis命令就是使用该对象的方法
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 测试连接
System.out.println(jedis.ping());
// 关闭连接
jedis.close();
}
}

事务的使用。

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
package com.yww;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class config {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);

// 创建事务
Transaction multi = jedis.multi();
try{

// 组成事务的命令

// 执行事务
multi.exec();
} catch(Exception e) {
// 放弃事务
multi.discard();
e.printStackTrace();
} finally {
// 关闭连接
jedis.close();
}
}
}

Lettuce

整合就先创建一个SpringBoot的项目,导入SpringData项目的redis依赖。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.2</version>
</dependency>

在比较新的SpringBoot版本中,放弃了Jedis对redis的直接连接,因为多个线程会出现线程不安全的情况,所以官方已经替换成了lettuce。
lettuce采用netty,实例可以在多个线程中进行共享,减少线程数量,不存在线程不安全的情况。

在配置文件中,配置Redis的连接。

1
2
3
# 配置Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

简单的测试。

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
34
35
package com.yww;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisTestApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
/*
* 数据类型的操作
* 1. opsForValue 操作字符串
* 2. opsForList 操作list
* 3. opsForSet 操作set
* 4. opsForHash 操作hash
* 5. opsForZSet 操作Zest
* 6. opsForGeo 操作geospatial
* 7. opsForHyperLogLog 操作Hyperloglog
*
*/
redisTemplate.opsForValue().set("name","yww");
System.out.println(redisTemplate.opsForValue().get("name"));

// 获取Redis的连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
}

}

上述的测试其实是没有经过序列化的,这是一个很不安全的操作,特别是传输对象的时候,没有序列化就会报错。

先简单的创建一个对象来测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.yww;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
// 实现Serializable序列化
public class User implements Serializable {
private String name;
private int age;
}
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
package com.yww;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisTestApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() throws JsonProcessingException {
User user = new User("yww", 20);
// 转换为JSON字符串,传输的对象要经过序列化,不然会报错
String json = new ObjectMapper().writeValueAsString(user);

redisTemplate.opsForValue().set("user",json);
System.out.println(redisTemplate.opsForValue().get("user"));

}
}

默认是会使用jdk的序列化方式,在后期使用会出现一些麻烦,所以开发中一般会自己定义RedisTemplate的序列化方式,所以就需要自己定义一个RedisTemplate来使用。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* <p>
* Redis的配置类
* </p>
* @ClassName RedisConfig
* @Author yww
**/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurationSelector {

/**
* 自定义RedisTemplate
* @return RedisTemplate
*/
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes", "deprecation" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

/**
* 缓存管理
* @param factory Redis连接工厂
* @return CacheManager
*/
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}

}
测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class RedisTestApplicationTests {

// 注意要注入的对象是配置类的对象,要是使用了原生的就配置无效了,或者使用resource
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;

@Test
void contextLoads() throws JsonProcessingException {
User user = new User("yww", 20);
redisTemplate.opsForValue().set("user",json);
System.out.println(redisTemplate.opsForValue().get("user"));
}

}

使用注解

注解的使用会更加方便的为接口返回的数据添加到缓存里。

Cacheable

这个注解有几个参数。

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
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
// 缓存名(注意这个不是键,类似一个文件夹名)
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 缓存的自定义键
String key() default "";

String keyGenerator() default "";

String cacheManager() default "";

String cacheResolver() default "";
// 缓存的条件
String condition() default "";

String unless() default "";

boolean sync() default false;

}

首先在这个注解中,value或者是cacheNames这个参数是必须的。

1
2
3
4
5
6
@Cacheable("yww")	// 这样就是value的属性
@GetMapping("/")
public Result getInfo() {
List<User> list = service.list();
return Result.ok().data("list",list);
}

最经常用的组合。

1
2
3
4
5
6
@Cacheable(value = "yww",key = "111")
@GetMapping("/")
public Result getInfo() {
List<User> list = service.list();
return Result.ok().data("list",list);
}

condition这个参数是用来定义缓存的条件的,比如下面这个例子。

1
2
3
4
5
   // 这样表示id大于50的时候取出的数据才会放入缓存
@Cacheable(value = "yww",condition = "#id > 50")
@GetMapping("/{id}")
public Result getInfo(@PathVariable("id") int id) {
}

CachePut

这个注解和Cacheable作用是一样的,参数也是一样的,不一样的是这个注解是只负责将返回的结果存入缓存,再次查询的时候,是不走缓存的。

CacheEvict

这个注解主要是用来清除缓存的。

主要使用的参数是allEntries,让它为true会将缓存清空

1
2
3
4
// value,key,condition和Cacheable一样
@Cacheable(value = "yww",key = "111",condition = "#id > 50",allEntries = true)
public void clear() {
}

Zset实现一个排行榜

Zset这个数据结构是一个有序的集合,它会根据score来自动帮你进行排序。以下为Zset的一些操作进行一个封装。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* @ClassName RedisUtil
* @Descriprtion Redis的封装工具类
* @Author yww
**/
@Component
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public class RedisUtil {

@Resource
private RedisTemplate redisTemplate;

/**
* 新增数据
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param member 排序的对象
* @param score 排序的对象的分数
*/
public void addZset(String sortedSetKey, Object member, double score) {
redisTemplate.opsForZSet().add(sortedSetKey,member,score);
}

/**
* 获取排行榜前N的数据
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param number 获取前number的数据
* @return 排行榜前N的数据
*/
public Set<Object> getTop(String sortedSetKey, long number) {
return redisTemplate.opsForZSet().reverseRange(sortedSetKey, 0L, number);
}

/**
* 获取指定Zset中指定member的score值
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param member 排序的对象
* @return 返回指定对象的score值,若该member不存在,则返回null
*/
public Double getScore(String sortedSetKey, Object member) {
return redisTemplate.opsForZSet().score(sortedSetKey, member);
}

/**
* 获取排行榜前N的数据
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param number 获取前number的数据
* @return 排行榜前N的数据
*/
public Set<ZSetOperations.TypedTuple<Object>> getTop(String sortedSetKey, long top) {
return redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetKey, 0, top);
}

/**
* 用于点击后为指定member的score增加值
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param member 排序的对象
*/
public void incrementScore(String sortedSetKey, Object member, double score) {
redisTemplate.opsForZSet().incrementScore(sortedSetKey,member,score);
}

/**
* 返回该Zset中存在的对象数,即member的个数
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @return 返回存在对象的个数
*/
public Long getTotal(String sortedSetKey) {
return redisTemplate.opsForZSet().size(sortedSetKey);
}

/**
* 判断指定Zset中指定member的score值是否存在
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param member 排序的对象
* @return 存在就返回true,不存在就返回false
*/
public boolean containsZsetKey(String sortedSetKey, Object member) {
return redisTemplate.opsForZSet().score(sortedSetKey, member) != null;
}

/**
* 用于点击后为指定member的score增加值
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @param member 排序的对象
*/
public void incrementScore(String sortedSetKey, Object member, double score) {
redisTemplate.opsForZSet().incrementScore(sortedSetKey,member,score);
}

/**
* 返回该Zset中存在的对象数,即member的个数
* @param sortedSetKey Zset的Key,即用来区分不同的Zset
* @return 返回存在对象的个数
*/
public Long getTotal(String sortedSetKey) {
return redisTemplate.opsForZSet().size(sortedSetKey);
}

}

然后使用这个封装类的方法,定义出一个Zset,在点击等操作的逻辑中增加一些操作,将需要排行的数据加入到指定的Zset中,就可以实现一个简单排行榜了。

测试。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
class MyprojectApplicationTests {

@Resource
private RedisUtil util;

@Test
void contextLoads() {
System.out.println(util.getScore("video","e720a1f03858488b854564954dde7047"));
}

}