这一节,我们将整合redis,并用将session交由redis保存。为之后的集群和分布式开发做准备。

整合redis

配置

这里介绍redis的单机配置,集群配置之后再说。默认你已安装了redis。

  • web模块下的pom.xml加入依赖
1
2
3
4
5
<!-- 整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • application.yml添加配置
1
2
3
4
5
spring:
redis:
host: 127.0.0.1
port: 6380
password: 123456

没错,只要这两步操作,就可以在项目中使用redis啦。

用junit测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class Test1 {
@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
public void test1() {
String value1 = "hahaha";
stringRedisTemplate.opsForValue().set("test", value1);
Assert.assertEquals(value1, stringRedisTemplate.opsForValue().get("test"));
}
}

运行一下test1,看看是否测试通过了。再在redis里看看是否键和值都有并对应上了。

使用注解

上面这种手动操作存储、读取数据的用法,一般我们在系统中很少用到。我们可以在方法上加@CacheConfig@Cacheable@CachePut@CacheEvict这几个缓存注解(关于这几个注解的作用,可以自己查一下,比较简单这里就不介绍了),自动进行缓存操作。不过在使用这几个注解之前,需要在DemoApplication类加上@EnableCaching这个注解来使缓存注解生效。

加上@EnableCaching注解后测试一下,在UserService中加入以下方法:

1
2
3
4
5
6
7
8
@Cacheable(cacheNames = "User", key = "#id")
public RespInfo<User> getUser(Long id) {
if (id == null) {
return RespInfo.error("请输入用户ID");
}
User user = userMapper.selectByPrimaryKey(id);
return RespInfo.success(user);
}

test类:

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
public class Test1 {
@Autowired
private UserService userService;

@Test
public void test1() {
RespInfo respInfo = userService.getUser(1L);
}
}

先在getUser()中打个断点,再Debug两次test1。第一次运行时,断点生效,运行成功后,redis里生成了key为User::1的数据。说明注解已生效,并把结果存入redis。第二次运行时,断点没有生效,运行成功,返回值和第一次运行的一样。说明我们是从redis中读取的数据,并没有经过数据库查询。

RedisCacheConfig

上面已经基本完成了缓存的使用。但是还有两个小问题:1.我们给方法加缓存,每次都要定义一个缓存key,比较麻烦。2.我们存在redis里的数据默认使用jdk的序列化方法,该方法效率低、序列化后需要的内存较大、缓存的类必须要实现Serializable接口,而且数据可读性不高。所以我们需要定义一个通用的key生成方法,并用Jackson序列化方法代替默认的序列化方法。

我们创建RedisCacheConfig类继承CachingConfigurerSupport

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
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Bean//这个注解必须要
public RedisTemplate redisTemplate() {
StringRedisTemplate redisTemplate = new StringRedisTemplate(redisConnectionFactory);
//将序列化方式改为GenericJackson2JsonRedisSerializer
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

@Bean//这个注解必须要
@Override
public CacheManager cacheManager() {
//新建一个默认的RedisCacheConfiguration
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
//在默认的配置上修改key过期时间为6小时//修改值的序列化方式为GenericJackson2JsonRedisSerializer
configuration = configuration.entryTtl(Duration.ofHours(6)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//设置RedisCacheManager
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(configuration).build();
return cacheManager;
}

@Bean//这个注解必须要
@Override
public KeyGenerator keyGenerator() {
//使用包名+类名+方法名+参数的格式生成key,防止key冲突导致数据异常
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(":").append(method.getName());
sb.append(":").append(getParamsString(params));
return sb.toString();
};
}

private String getParamsString(Object params) {
try {
//用gson把参数转换为字符串
String s = new Gson().toJson(params);
//使用MD5加密 具体加密方法网上都有的查,也可以到本项目git里找
return MD5Util.MD5(s);
} catch (Exception e) {
e.printStackTrace();
}
return "null";
}
}

UserService修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private RedisTemplate redisTemplate;

//这里之前的key省略了 但是cacheNames不能省略,可以所有的地方都用一样的cacheNames
@Cacheable(cacheNames = "Demo")
public RespInfo<User> getUser(Long id) {
if (id == null) {
return RespInfo.error("请输入用户ID");
}
User user = userMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set("test", user);
return RespInfo.success(user);
}

再按照之前上面的测试操作,测试一下是否成功。如果成功的话,发现redis多了两个key:

1
2
Demo::com.tt.study.demo.service.UserService:getUser:35DBA5D75538A9BBE0B4DA4422759A0E
test

使用get key查看一下,发现都是json格式的数据,可读性完爆之前的。

整合spring session(使用redis存session)

  • web模块下的pom.xml加入依赖
1
2
3
4
5
<!-- 整合session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
  • DemoApplication类加上注解
1
2
3
//redisNamespace:redis中key的命名会加上这个
//maxInactiveIntervalInSeconds:不活跃多长时间自动过期key
@EnableRedisHttpSession(redisNamespace = "Demo", maxInactiveIntervalInSeconds = 3600)

没错,又是只要两步操作,就可以了。

测试一下,添加一个controller:

1
2
3
4
5
6
7
8
9
@RequestMapping("/login")
@ResponseBody
public Object login(User user, HttpSession session) {
RespInfo<User> respInfo = userService.login(user);
if (respInfo.isSuccess()) {
session.setAttribute("user_session", respInfo.getData());
}
return respInfo;
}

请求一次,查看redis,发现多了3个key:

1
2
3
Demo:sessions:expires:8165ce0b-f18e-4184-8b6f-10774dd46c67
Demo:sessions:8165ce0b-f18e-4184-8b6f-10774dd46c67
Demo:expirations:1521549420000

Ok,现在session已经交给redis保存了,我们之后做集群也不用担心session的问题了。