1. 在pom.xml文件中配置相关依赖:
<!-- 加载MyBatis整合SpringBoot -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
2. 在Springboot核心配置文件application.properties中配置redis连接信息:
# 配置 Redis 连接信息
spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=
3. 在需要操作redis的类中注入redisTemplate:
package com.sztxtech.springbootmybatis.service.impl;
import com.sztxtech.springbootmybatis.entity.User;
import com.sztxtech.springbootmybatis.mapper.UserMapper;
import com.sztxtech.springbootmybatis.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserMapper mapper;
/** 注入SpringBoot自动配置好的 RedisTemplate */
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/**
* 查询所有用户信息
* @return
*/
public List<User> findAllUserInfo(){
// 查询Redis缓存
List<User> userList = (List<User>) redisTemplate.opsForValue().get("allUser");
//如果缓存为空,查询数据库,并把查询结果存入缓存
if(userList == null){
userList = mapper.selectAllUser();
//将查询结果存入Redis
redisTemplate.opsForValue().set("allUser", userList);
}
return userList;
}
}
Remark:spring boot帮我们注入的redisTemplate类,泛型里面只能写 <String, String>、<Object, Object>:
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
以上就是通过IntelliJ IDEA实现SpringBoot集成Redis缓存的过程。在Redis中缓存的具体结果如下:
4. 由于Redis在存储数据时,Key和Value默认都是进行过序列化的,数据可读性不高。为解决此问题,可以在做Redis缓存之前对数据做相应的处理,即通过 org.springframework.data.redis.serializer.RedisSerializer 和 org.springframework.data.redis.serializer.StringRedisSerializer 创建序列化器,通过 RedisTemplate 的 setKeySerializer 方法,传入创建的序列化器,对存储缓存的序列化方式进行修改;具体实现如下:
public List<User> findAllUserInfo(){
// 字符串的序列化器
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
// 查询Redis缓存
List<User> userList = (List<User>) redisTemplate.opsForValue().get("allUser");
if(userList == null){
userList = mapper.selectAllUser();
redisTemplate.opsForValue().set("allUser", userList);
}
return userList;
}
清除Redis中之前存入的数据,再次进行缓存的结果如下:
可以看到,处理后的缓存结果,Key就是在Java代码中设置的“allUser”。
5. 针对以上的查询功能,假设同时有1W人同时执行此首次查询(高并发)功能,那么,Redis缓存中是没有数据的,这样这1W人的查询就都会查询数据库.....所以数据库的压力会剧增;同时,这种情况下,Redis缓存并没起到作用,这就出现了缓存穿透问题。
解决此问题可以使用多线程的线程同步来进行处理。
方式一:直接在查询方法上加 synchronized 进行线程同步处理;这样,当1W人执行查询时,一次只有1人能进入该查询方法,其余的查询请求在此方法处于等待状态,从第二个查询进入此方法开始,开始查询Redis缓存。但是这种处理方式,系统的效率会比较低,牺牲性能来降低数据库查询量!
public synchronized List<User> findAllUserInfo(){
// 字符串的序列化器
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
// 查询Redis缓存
List<User> userList = (List<User>) redisTemplate.opsForValue().get("allUser");
if(userList == null){
userList = mapper.selectAllUser();
redisTemplate.opsForValue().set("allUser", userList);
}
return userList;
}
方式二:由于Spring容器中的Bean默认都是单例的,可以使用线程锁来实现:
public List<User> findAllUserInfo(){
// 字符串的序列化器
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
// 查询Redis缓存
List<User> userList = (List<User>) redisTemplate.opsForValue().get("allUser");
// 双重检测锁
if(userList == null){
synchronized (this){
userList = (List<User>) redisTemplate.opsForValue().get("allUser");
if(userList == null){
userList = mapper.selectAllUser();
redisTemplate.opsForValue().set("allUser", userList);
}
}
}
return userList;
}
对方式二的实现进行测试:
在Controller中创建测试方法,通过线程池模拟多线程的方式进行测试:
@GetMapping("/boot/findAllUser")
public void findAllUserInfomation(){
//创建线程,调用Service层的查询方法
Runnable runnable = new Runnable() {
@Override
public void run() {
service.findAllUserInfo();
}
};
//创建线程池; 通过多线程测试缓存穿透问题
ExecutorService executorService = Executors.newFixedThreadPool(25);
for(int i=0; i<=10000; i++){
executorService.submit(runnable);
}
}
为了体现测试效果,得知程序是查询数据还是Redis缓存,对以上查询功能的Service实现类稍做修改(添加输出语句打印查询过程):
public List<User> findAllUserInfo(){
// 字符串的序列化器
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
// 查询Redis缓存
List<User> userList = (List<User>) redisTemplate.opsForValue().get("allUser");
// 双重检测锁
if(userList == null){
synchronized (this){
userList = (List<User>) redisTemplate.opsForValue().get("allUser");
if(userList == null){
userList = mapper.selectAllUser();
redisTemplate.opsForValue().set("allUser", userList);
System.out.println("synchronized代码块内 查询数据库!!!!!!");
}else {
System.out.println("synchronized代码块内 查询Redis缓存 ......");
}
}
}else {
System.out.println("查询Redis缓存 ......");
}
return userList;
}
浏览器访问该查询路径,控制台输出的查询过程如下【只有第一次查询的是数据库,之后全部查询Redis缓存】:
而未使用线程同步处理缓存穿透之前的查询过程如下,可以看到有大量查询数据的记录,这并不是使用Redis缓存想要的效果: