目录
一、Redis缓存数据
1、pom.xml增加依赖
<!-- Redis缓存(1.4版本后多了个data) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce集成Redis服务,需要导入此包(Jedis直连模式不需要导入它) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、application*的配置
# 在application.properties文件中,加入配置
# 配置日志输出级别以观察SQL的执行情况
logging.level.org.springboot.springboot01=debug
# 在application-dev.properties文件中,加入配置
######################## Redis配置 ########################
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=123456
# 连接超时时间(毫秒)
spring.redis.timeout=5000
##### Jedis是直连模式,多个线程间共享一个Jedis实例时,线程不安全。
##### 可通过创建Jedis实例来解决,但连接数量增多时,物理连接成本较高,会影响性能
##### 较好的解决方法是:使用JedisPool
## 连接池中的最小空闲连接,默认值是0。
#spring.redis.jedis.pool.min-idle=50
## 连接池中的最大空闲连接,默认值是8。
#spring.redis.jedis.pool.max-idle=500
## 最大连接数,如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
#spring.redis.jedis.pool.max-active=1000
## 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
#spring.redis.jedis.pool.max-wait=2000
##### Lettuce的连接是基于Netty的,连接实例可以在多个线程间共享
##### Netty可以使多线程的应用使用同一个连接实例,而不用担心并发线程的数量
##### 通过异步的方式可以让我们更好地利用系统资源
# 连接池中的最小空闲连接,默认值是0。
spring.redis.lettuce.pool.min-idle=50
# 连接池中的最大空闲连接,默认值是8。
spring.redis.lettuce.pool.max-idle=500
# 最大连接数,如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
spring.redis.lettuce.pool.max-active=1000
# 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
spring.redis.lettuce.pool.max-wait=2000
3、Redis配置类与缓存类
3-1、创建Redis配置类
在org.springboot.springboot01下,新增config包,并创建配置类:RedisConfig
/**
* Redis配置
*
* Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,
* 应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,
* 当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例
*
* RedisTemplate和StringRedisTemplate的区别:
* 1. 两者的关系是StringRedisTemplate继承RedisTemplate。
* 2. 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
* 3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
* StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
* RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);
/**
* 模式一
* 配置自定义redisTemplate(用于org.springboot.springboot01.utils.RedisUtils自定义方法的使用)
*
* @param redisConnectionFactory redisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 配置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer(Object.class);
// 设置值(value)的序列化采用Jackson2JsonRedisSerializer
redisTemplate.setHashKeySerializer(serializer);
redisTemplate.setValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 模式二
* 使用注解配置形式:自定义缓存Key的生成策略
* @return 缓存Key
*/
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
for (Object obj : params) {
sb.append(obj.toString());
}
log.info("keyGenerator的结果值:{}", sb.toString());
return sb.toString();
};
}
/**
* 模式二
* 使用注解配置形式,如@Cacheable
*
* @param redisConnectionFactory redisConnectionFactory
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.setCacheConfigurationWithTtl(7200), this.initialCacheConfigurations());
}
/**
* 默认策略,未配置的 key,会使用这个
*
* @param time 设定的过期时间,秒为单位
*
* @return RedisCacheConfiguration
*/
private RedisCacheConfiguration setCacheConfigurationWithTtl(long time) {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
serializer.setObjectMapper(om);
return RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)).entryTtl(Duration.ofSeconds(time));
}
/**
* 指定的过期策略
* 失效间隔时间 0 秒 (表示永久缓存)
* 失效间隔时间 1800 秒 (0.5小时)
* 失效间隔时间 3600 秒 (1小时)
* 失效间隔时间 7200 秒 (2小时)
* 失效间隔时间 21600 秒 (6小时)
*
* @return Map<String, RedisCacheConfiguration>
*/
private Map<String, RedisCacheConfiguration> initialCacheConfigurations() {
Map<String, RedisCacheConfiguration> redisMap = new HashMap<>();
redisMap.put("StudentList", setCacheConfigurationWithTtl(0));
redisMap.put("UserList", setCacheConfigurationWithTtl(3600));
return redisMap;
}
}
3-2、Redis缓存类
3-2-1、在org.springboot.springboot01.utils下,创建工具缓存类:RedisUtils(模式一使用的)
@Component
public class RedisUtils {
private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
log.error("判断key={}是否存在异常", key, e);
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}
/**
* 放入缓存
*
* @param key 键
* @param value 值
* @return boolean
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
} catch (Exception e) {
log.error("key={}, value={}放入缓存失败", key, value, e);
return false;
}
return true;
}
/**
* 放入缓存,并设定有效期
*
* @param key 键
* @param value 值
* @param time 指失效时间(以秒为单位)
* @return boolean
*/
public boolean set(String key, Object value, long time) {
try {
// 放入缓存
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
} catch (Exception e) {
log.error("key={}, value={}放入缓存失败", key, value, e);
return false;
}
return true;
}
/**
* 读取缓存
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
}
3-2-2、在org.springboot.springboot01.service和org.springboot.springboot01.service.impl和下,创建注解缓存类:CacheService和CacheServiceImpl(模式二使用的)
// 缓存服务接口
public interface CacheService {
List<User> listUser();
List<Student> listStudent();
List<Student> cacheStudentList();
}
// 缓存实现接口类
/**
* unless="#result == null":不对结果为null的进行缓存(函数返回值符合条件的排除掉、只缓存其余不符合条件的)
* 缓存的key使用的是自定义方法keyGenerator,也可以自己设置
* 更新缓存:@CachePut
* 获取缓存(如果没有就去数据库找,找到就重新放入缓存):@Cacheable
*/
@Service
public class CacheServiceImpl implements CacheService {
private static final Logger log = LoggerFactory.getLogger(CacheServiceImpl.class);
@Resource
private MysqlStudentMapper mysqlStudentMapper;
@Override
@CachePut(value = "UserList", keyGenerator = "keyGenerator", unless = "#result == null")
public List<User> listUser() {
log.debug("进入listUser方法,获取用户信息,并缓存");
List<User> userList = new ArrayList<>();
userList.add(new User("wangwu", "王五", "111111", "超级管理员", "17777777777"));
userList.add(new User("zhangsan", "张三", "123456", "管理员", "17677777777"));
return userList;
}
@Override
@CachePut(value = "StudentList", keyGenerator = "keyGenerator", unless = "#result == null")
public List<Student> listStudent() {
log.debug("进入listStudent方法,获取学生信息,并缓存");
return mysqlStudentMapper.listAllStudent();
}
@Override
@Cacheable(value = "StudentList", keyGenerator = "keyGenerator", unless = "#result == null")
public List<Student> cacheStudentList() {
log.debug("未获得cacheStudentList方法的缓存");
return null;
}
}
4、测试类
在src/test/java下,新增org.springboot.springboot01包,并新增测试类ApplicationTest
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ApplicationTest {
private static final Logger log = LoggerFactory.getLogger(ApplicationTest.class);
@Resource
private RedisUtils redisUtils;
@Resource
private CacheService cacheService;
@Test
public void testMode1() {
redisUtils.set("k001", "v001");
log.debug("把key=k001,value=v001放入缓存");
Object value = redisUtils.get("k001");
log.debug("获取key=k001的缓存值:{}", value);
redisUtils.set("k002", "v002", 100);
log.debug("设置key=k002的缓存有效时间");
}
@Test
public void testMode2() {
log.debug("查询学生信息:{},并缓存", JSON.toJSONString(cacheService.listStudent()));
log.debug("查询用户信息:{},并缓存", JSON.toJSONString(cacheService.listUser()));
log.debug("获取学生缓存信息:{}", JSON.toJSONString(cacheService.cacheStudentList()));
}
}
5、测试结果
测试操作:选中要测试的方法,点击右键,出现如下图片所示(Run普通运行,Debug可打断点运行)
5-1、模式一测试结果
5-1-1、设置了两个key值(使用的是RedisDesktopManager客户端查看)
5-1-2、没有设置有效期的
5-1-3、设置了有效期的
5-2、模式二测试结果
5-2-1、无参方法(使用的是RedisDesktopManager客户端查看)
5-2-2、有参方法(使用的是RedisDesktopManager客户端查看)
// 1. 测试类方法testMode2中,增加下面的调用
log.debug("查询用户信息:{},并缓存", JSON.toJSONString(cacheService.listUser("wangwu", "17777777777")));
// 2. 增加接口方法
List<User> listUser(String userName, String phoneNo);
// 3. 实现类实现接口方法
@Override
@CachePut(value = "UserList", keyGenerator = "keyGenerator", unless = "#result == null")
public List<User> listUser(String userName, String phoneNo) {
log.debug("进入listUser方法,获取用户信息,并缓存");
List<User> userList = new ArrayList<>();
userList.add(new User("wangwu", "王五", "111111", "超级管理员", "17777777777"));
userList.add(new User("zhangsan", "张三", "123456", "管理员", "17677777777"));
return userList;
}
增加了上面三步程序代码,就可以测试有参数的方法缓存的情况,缓存的内容,如下所示(key值是【value = "UserList"】中的UserList加上::,然后再加上参数值组合);参数值组合可参照keyGenerator自定义生成策略。