SpringBoot整合Redis缓存

SpringBoot整合Redis缓存

如果我们每次需要数据都要从数据库访问数据的话,会给数据库带来极大的压力,这时候,就需要一个地点暂时居住起来,这样就不会因为大量的IO导致效率低下。:happy:

Redis

简介

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

简而言之就是一个高速的缓存器,我使用这个缓存器使在数据库中传入的数据,放入到一个缓存库中。这样,就不用每次需要数据,都连接一次数据库了。

加入依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

redis默认使用lettuce去连接,使用exclusions去排除,再加入jedis连接。当然,只加redis依赖也可以,只不过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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
server:
port: 8888

mybatis:
mapper-locations: classpath:/mapper/*Mapper.xml
type-aliases-package: com.example.redis.pojo

spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/springtest?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
tomcat:
max-idle: 10
max-wait: 10000
max-active: 50
initial-size: 5

cache:
type: redis
redis:
database: 0
host: localhost
port: 6379
# 有密码填密码,没有密码不填
password:
# 连接超时时间(ms)
timeout: 1000ms
# 高版本springboot中使用jedis或者lettuce
jedis:
pool:
# 连接池最大连接数(负值表示无限制)
max-active: 8
# 连接池最大阻塞等待时间(负值无限制)
max-wait: 5000ms
# 最大空闲链接数
max-idle: 8
# 最小空闲链接数
min-idle: 0

实体类

1
2
3
4
5
6
7
8
@Data
@Alias("user")
public class User implements Serializable {
private static final long serialVersionUID = 7760614561073458247L;
private Long id;
private String userName;
private String note;
}

Dao层

1
2
3
4
5
6
7
8
9
10
11
12
public interface UserDao {

User getUser(Long id);

int insertUser(User user);

int updateUser(User user);

List<User> findUsers(@Param("userName") String userName,@Param("note") String note);

int deleteUser(Long id);
}

XML:

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.redis.dao.UserDao">

<select id="getUser" parameterType="long" resultType="user">
select id, user_name as userName, note from t_user
where id = #{id}
</select>

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"
parameterType="user">
insert into t_user(user_name, note)
values(#{userName}, #{note})
</insert>

<update id="updateUser">
update t_user
<set>
<if test="userName != null">user_name =#{userName},</if>
<if test="note != null">note =#{note}</if>
</set>
where id = #{id}
</update>

<select id="findUsers" resultType="user">
select id, user_name as userName, note from t_user
<where>
<if test="userName != null">
and user_name = #{userName}
</if>
<if test="note != null">
and note = #{note}
</if>
</where>
</select>

<delete id="deleteUser" parameterType="long">
delete from t_user where id = #{id}
</delete>
</mapper>

熟练的定义一个接口,同时也记得要在启动器中加入:

1
2
@EnableCaching
@MapperScan("com.example.redis.dao")

@EnableCaching 记得一定要加,表示开启缓存。

Service层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface UserService {
// 获取单个用户
User getUser(Long id);

// 保存用户
User insertUser(User user);

// 修改用户,指定MyBatis的参数名称
User updateUserName(Long id, String userName);

// 查询用户,指定MyBatis的参数名称
List<User> findUsers(String userName, String note);

// 删除用户
int deleteUser(Long id);
}
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
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

// 插入用户,最后MyBatis会回填id,取结果id缓存用户
@Override
@Transactional
@CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
public User insertUser(User user) {
userDao.insertUser(user);
return user;
}

// 获取id,取参数id缓存用户
@Override
@Transactional
@Cacheable(value = "redisCache", key = "'redis_user_'+#id")
public User getUser(Long id) {
System.out.println("service");
return userDao.getUser(id);
}

// 更新数据后,充值缓存,使用condition配置项使得结果返回为null,不缓存
@Override
@Transactional
@CachePut(value = "redisCache", condition = "#result != 'null'", key = "'redis_user_'+#id")
public User updateUserName(Long id, String userName) {
// 此处调用getUser方法,该方法缓存注解失效,
// 所以这里还会执行SQL,将查询到数据库最新数据
User user = this.getUser(id);
if (user == null) {
return null;
}
user.setUserName(userName);
userDao.updateUser(user);
return user;

}

// 命中率低,所以不采用缓存机制
@Override
@Transactional
public List<User> findUsers(String userName, String note) {
return userDao.findUsers(userName, note);
}

// 移除缓存
@Override
@Transactional
@CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
public int deleteUser(Long id) {
return userDao.deleteUser(id);
}
}

@Transactional表示是一个关于数据库的事务,能够控制数据库隔离级别和事务类型,这里先不详谈。

@CachePut表示将方法的结果放入缓存,比如你插入了一条数据后,就会返回一个布尔值,表示成功或是失败,如果成功,则放这条数据放入redis中。

value表示缓存库的名字是什么,要放入到哪个缓存区域去,key表示缓存的值,类似hashmap的形式,# 表示传入的参数。

@Cacheable能从缓存中通过定义的键进行查询,如果查询到数据,则返回,否则执行该方法,返回数据,并且将返回结果保持到缓存中,是使用最多的方法。

@CacheEvic 言简意赅,移除缓存。

Controller层

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
@Controller
@RequestMapping("/user")
public class UserController {

@Autowired
private UserService userService = null;

@RequestMapping("/getUser")
@ResponseBody
public User getUser(Long id) {
System.out.println("controller");
return userService.getUser(id);
}

@RequestMapping("/insertUser")
@ResponseBody
public User insertUser(String userName, String note) {
User user = new User();
user.setUserName(userName);
user.setNote(note);
userService.insertUser(user);
return user;
}

@RequestMapping("/findUsers")
@ResponseBody
public List<User> findUsers(String userName, String note) {
return userService.findUsers(userName, note);
}

@RequestMapping("/updateUserName")
@ResponseBody
public Map<String, Object> updateUserName(Long id, String userName) {
User user = userService.updateUserName(id, userName);
boolean flag = user != null;
String message = flag? "更新成功" : "更新失败";
return resultMap(flag, message);
}

@RequestMapping("/deleteUser")
@ResponseBody
public Map<String, Object> deleteUser(Long id) {
int result = userService.deleteUser(id);
boolean flag = result == 1;
String message = flag? "删除成功" : "删除失败";
return resultMap(flag, message);
}

private Map<String, Object> resultMap(boolean success, String message) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", success);
result.put("message", message);
return result;
}
}

好了,来测试一下吧。

测试

输入地址:

回去查看idea:

我们可以看到的是,在Controller层进行了访问,然后在Service层进行了访问,这表示是第一次读取这个值,需要访问数据库,并且在放入缓存中。

那么再多访问几次看看吧。

连续的页面刷新后,再去idea

我们发现,这只执行到Controller层,而不进行Service层,这表示了我们读取到的是缓存,而不是数据库,因为只执行Controller层便结束了。

Redis拓展

我们放入Redis的缓存,在不设置之前,是永久存在的。,如果缓存只会被放入,而不设置过期时间的话,就算有办法取消掉一些缓存,但随着时间的推移,总会有些缓存会被遗忘,一直作为内存的存在Redis当中。这时,就需要深入的配置Redis,让其能够主动的销毁。

设置缓存过期时间

加入configuration:

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
@Configuration
public class RedisConfig{

@Autowired
private RedisConnectionFactory redisConnectionFactory;

//自定义Redis缓存管理器
@Bean
public RedisCacheManager redisCacheManager(){
//Redis加锁的写入器
RedisCacheWriter writer=RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
//这里注入了Redis连接工厂
//启动Redis缓存默认设置
RedisCacheConfiguration config=RedisCacheConfiguration.defaultCacheConfig();
//设置JDK序列化器
//设置10秒超时
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
//禁用前缀
config=config.disableKeyPrefix();
//设置10秒超时
config=config.entryTtl(Duration.ofSeconds(10));
//创建缓存管理器
RedisCacheManager redisCacheManager=new RedisCacheManager( writer,config);
return redisCacheManager;
}
}

configuration会全局识别为SpringBoot的配置文件,使用@Bean注解,去把这个容器注入到SpringBoot中。

再去测试一下:

我这里是每15秒进行一次访问,可以发现,缓存都在10后会自动过期,都需要重新从数据库读取。

我们可以使用RedisDesktop去查看Redis到底存放了些什么:

项目地址:https://github.com/Antarctica000/SpringBoot/tree/master/redis