SpringBoot集成Redis、SpringCache
1 Redis介绍
1.1 Redis作为缓存
由于Redis的存取效率非常高,在开发实践中,通常会将一些数据从关系型数据库(例如MySQL)中读取出来,并写入到Redis中,后续当需要访问相关数据时,将优先从Redis中读取所需的数据,以此,可以提高数据的读取效率,并且对一定程度的保护关系型数据库。
一旦使用Redis后,相关的数据就会同时存在于关系型数据和Redis中,即同一个数据有2份或更多(如果你使用了更多的Redis服务或其它数据处理技术),则可能出现数据不同步的问题!
1.2 数据一致性问题
如果最终出现了关系型数据库和Redis中的数据不同的问题,则称之为“数据一致性问题
”
-
更新数据库成功 -> 更新缓存失败 -> 数据不一致
-
更新缓存成功 -> 更新数据库失败 -> 数据不一致
1.3 redis缓存使用场景
-
高频率访问的数据
- 例如热门榜单
-
修改频率非常低的数
- 例如商品的类别
-
对于数据的“准确性”要求不高的
- 例如商品的库存余量(因为不管余量为多少,只有真正付款的时候才会去判断是否买到)
1.4 redis使用缓存的时机
关于在项目中应用Redis,首先考虑何时将MySQL中的数据读取出来并写入到Redis中!常见的策略有:
1 直接尝试从Redis中读取数据,如果Redis中无此数据,则从MySQL中读取并写入到Redis
从运行机制上,类似于单例模式中的懒汉式
2 当项目启动时,就直接从MySQL中读取数据并写入到Redis
从运行机制上,类似于单例模式中的饿汉式
这种做法通常称之为“缓存预热”当使用缓存预热的处理机制时,需要使得某段代码是项目启动时就自动执行的,可以自定义组件类实现AppliacationRunner接口,重写其中的run( )方法,此方法将在项目启动完成之后自动调用。
2 SpringBoot集成Redis
2.1 安装redis
地址Releases · microsoftarchive/redis · GitHub
2.2 在springboot中引入依赖
<!--集成redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.7.18</version>
</dependency>
2.3 添加redis配置信息
# spring配置
spring:redis:database: 0 # redis数据库索引(默认为0)host: localhostport: 6379timeout: 5000 # mspassword: 123456
2.4 编写redis配置类、工具类
/*** redis配置*/
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport
{@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());template.afterPropertiesSet();return template;}
}
工具类,后面引入即可使用
/*** spring redis 工具类* **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisService
{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @return true=设置成功;false=设置失败*/public <T> boolean setCacheObjectIfAbsent(final String key, final T value){return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value));}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获取有效时间** @param key Redis键* @return 有效时间*/public long getExpire(final String key){return redisTemplate.getExpire(key);}/*** 判断 key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key){return redisTemplate.hasKey(key);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public boolean deleteObject(final Collection collection){return redisTemplate.delete(collection) > 0;}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的 key* @param hasKey 缓存的 hasKey* @param hasValue 缓存的 hasValue* @return true=设置成功;false=设置失败*/public <T> boolean setCacheMapIfAbsent(final String key,final String hasKey, final T hasValue){return Boolean.TRUE.equals(redisTemplate.opsForHash().putIfAbsent(key,hasKey,hasValue));}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 删除Hash中的某条数据** @param key Redis键* @param hKey Hash键* @return 是否成功*/public boolean deleteCacheMapValue(final String key, final String hKey){return redisTemplate.opsForHash().delete(key, hKey) > 0;}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}
3 SpringCache整合redis
3.1 添加依赖项,添加@EnableCaching
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.7.18</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.7.18</version>
</dependency>
3.2 配置yml文件
重点是 spring.cache.type 这个配置,SpringCache是提供了多种缓存的实现方式,redis只是其中一种。
3.3 Redis配置类编写
在刚才的配置类,添加缓存的配置信息
@Beanpublic RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {CacheProperties.Redis redisProperties = cacheProperties.getRedis();RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();// 序列化key、valueconfig= config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));if (redisProperties.getTimeToLive() != null) {config = config.entryTtl(redisProperties.getTimeToLive());}if (redisProperties.getKeyPrefix() != null) {config = config.prefixKeysWith(redisProperties.getKeyPrefix());}if (!redisProperties.isCacheNullValues()) {config = config.disableCachingNullValues();}if (!redisProperties.isUseKeyPrefix()) {config = config.disableKeyPrefix();}return config;}
3.4 在方法上面使用缓存
这里代表的是:根据Id查询品牌数据,然后开启缓存;缓存的名称的头部是brands开头,例如:brands:xx:xx。缓存的key使用传入进来的id进行设置;开启条件缓存,只有id是2的倍数,redis才进行缓存
这里第一次走数据库,第二次就没有数据库输出,redis里面正常保存数据
@Cacheable(value = "brands",key = "#id",condition = "#id%2==0")
public Brand getById(Integer id) {Brand brand = brandMapper.selectById(id);return brand;
}
可以看到实现效果是只有2,4,6这种偶数才进行了缓存
3.5 注解的介绍
3.5.1 Cacheable注解
将方法返回值加入缓存。同时在查询时,会先从缓存中取,若不存在才再发起对数据库的访问。在后续调用时(使用相同的参数),返回缓存中的值,而不必实际调用该方法。
注解参数:
1)value:指定缓存的名称,也可以说是缓存的prefix
例如:@Cacheable(value=”mycache”)
2)key:缓存的 key,可以为空,如果指定要按照 SpringEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
例如:@Cacheable(value=”mycache”,key=”#user.id”);表示方法传入的参数User对象的id作为Key
3)condition:缓存的条件,可以为空,使用 SpringEL 编写,返回 true 或者 false,只有为 true 才进行缓存
例如:@Cacheable(value=”mycache”,condition=”#user.age>18”);表示方法传入的参数User对象,如果user.age>18才缓存
3.5.2 CachePut注解
就是你在更新数据库的时候,要同步更新缓存数据,就用这个注解。
3.5.3 CacheEvict注解
能够根据一定的条件对缓存进行清空,对于从缓存中删除过时或未使用的数据非常有用。与@Cacheable相反,@CacheEvict 定义了执行缓存逐出的方法(即充当从缓存中删除数据的触发器的方法)。
注解参数:跟Cacheable一样,拥有那三属性,同时还有一个allEntries属性,该属性默认为false,当为true时,删除该value所有缓存。
3.5.4 Caching注解
有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id–>user;username—>user;email—>user的缓存;此时就需要@Caching组合多个注解标签了。
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) {
}
3.5.5 CacheConfig注解
CacheConfig是一个类级别的注解。
所有的@Cacheable里面都有一个value的属性,这个注解能够指定该类下面所有@Cacheable的value值。
假设我在UserService类上面添加了这个注解,并且指定了value值,那么UserService类下的所有需要缓存的方法(例如:getById( )方法)都不需要添加value值了。