一、本次案例
Springboot 中Spring-cache与redis 整合。这也是一个不错的框架,与 spring 的事务使用类似,只要添加一些注解方法,就可以动态的去操作缓存了,减少代码的操作。如果这些注解不满足项目的需求,我们也可以参考 spring-cache 的实现思想,使用 AOP 代理+缓存操作来管理缓存的使用。
在这个例子中我使用的是 redis,当然,因为 spring-cache 的存在,我们可以整合多样的缓存技术,例如 Ecache、Mamercache 等。
二、创建 springBoot 项目 king-cache
在 pom.xml 中添加以下依赖
org.springframework.boot
spring-boot-starter-cache
org.springframework.boot
spring-boot-starter-data-redis
三、redis 客户端配置
无论使用 spring-boot 的哪个版本,我们都需要先配置 redis 连接,两个版本的 redis 客户端连接池使用有所不同。
spring-boot 版本 默认客户端类型
1.5.x jedis
2.x lettuce
在 1.5.x 中,我们配置 jedis 连接池只需要配置 spring.redis.pool.* 开始的配置即可,如下配置
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.min-idle=0
spring.redis.pool.max-idle=8
但在 2.x 版本中由于引入了不同的客户端,需要指定配置哪种连接池,如下配置
在 application.properties 中添加如下代码:
jedis 客户端
server.port=8080
spring.cache.type=redis
spring.redis.host=192.168.140.130 # server host
spring.redis.password= 123456
spring.redis.port= 6379
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=8
lettuce 客户端
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.shutdown-timeout=100ms
除此之外还可以看到时间配置上还需要带上对应的单位。
四、JavaConfig 方式配置
通用配置方式只能满足整个程序所有缓存都采用相同公共配置的方式,如果需要特殊处理,如我们的案列,则需要自己采用代码的方式来配置。
采用代码的方式,只要需要配置的是 CacheMananger,采用 Redis 时具体实现我们需要使用其子类 RedisCacheMananger 来做配置
创建 redis 的配置类 RedisConfig.java
注意一定要在类上加上以下两个注解:
@Configuration 可理解为用 spring 的时候 xml 里面的标签
@EnableCaching 注解是 spring framework 中的注解驱动的缓存管理功能。
package com.answer.config;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
- redis 缓存配置
- Created by zhezhiyong@163.com on 2017/9/21.
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport { @Bean
public KeyGenerator KeyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object… params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
} /* @Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager manager = new RedisCacheManager(redisTemplate);
manager.setUsePrefix(true);
RedisCachePrefix cachePrefix = new RedisPrefix(“prefix”);
manager.setCachePrefix(cachePrefix);
// 整体缓存过期时间
manager.setDefaultExpiration(3600L);
// 设置缓存过期时间。key 和缓存过期时间,单位秒
Map expiresMap = new HashMap<>();
expiresMap.put(“user”, 1000L);
manager.setExpires(expiresMap);
return manager;
}*/ @Bean
CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化一个 RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//设置 CacheManager 的值序列化方式为 JdkSerializationRedisSerializer,但其实 RedisCacheConfiguration 默认就是使用 StringRedisSerializer 序列化 key,JdkSerializationRedisSerializer 序列化 value,所以以下注释代码为默认实现
//ClassLoader loader = this.getClass().getClassLoader();
//JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader);
//RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);
//RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//设置默认超过期时间是 30 秒
defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
//初始化 RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
return cacheManager;
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
以上代码中 RedisCacheConfiguration 类为 2.x 新增的配置类,增加了几个配置项。这里比较奇怪的是调用它的配置方法每一次都会重新生成一个配置对象,而不是在原对象上直接修改参数值,这一点本人没搞懂作者为何要选择这种方式。 到这里基本的配置就已经完成了 五、具体使用
如果我们想在其他项目中添加该微服务想进行缓存的操作,需依赖 king-cache 模块: com.answer king-cache 0.0.1-SNAPSHOT 六、测试 Demo——新建项目:king-cache-1 添加以下依赖: com.answer king-cache 0.0.1-SNAPSHOT com.google.guava guava 19.0 org.projectlombok lombok compile
依上图所示分别创建以下实例: Info.java package com.answer.entity; import java.io.Serializable; import lombok.AllArgsConstructor;
import lombok.Data; /**- Created by zhezhiyong@163.com on 2017/9/22.
/
@Data
@AllArgsConstructor
public class Info implements Serializable{
/*
- @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 5457507957150215600L;
public Info(String string, String string2) {
// TODO Auto-generated constructor stub
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
private String phone;
private String address;
- @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
User.java package com.answer.entity; import java.io.Serializable; import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; /**- Created by zhezhiyong@163.com on 2017/9/21.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{ /**- @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 1L;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private Long id;
private String name;
private String password;
- @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
IndexController.java package com.answer.web; import java.util.List;
import java.util.Map; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import com.answer.entity.User;
import com.answer.service.UserService;
import com.google.common.collect.ImmutableMap; /**- Created by zhezhiyong@163.com on 2017/9/21.
*/
@RestController
public class IndexController { @Autowired
private UserService userService; @GetMapping(“/users”)
@ResponseBody
public List users() {
return userService.list();
} @GetMapping(“/user/{id}”)
@ResponseBody
public User findUserById(@PathVariable(“id”) Long id) {
return userService.findUserById(id);
} @GetMapping(“/upuser/{id}”)
@ResponseBody
public User upuser(@PathVariable(“id”) Long id) {
return userService.upuser(id);
} @GetMapping(“/info/{id}”)
@ResponseBody
public User findInfoById(@PathVariable(“id”) Long id) {
return userService.findInfoById(id);
} @GetMapping(“/user/{id}/{name}”)
@ResponseBody
public Map update(@PathVariable(“id”) Long id, @PathVariable(“name”) String name) {
User user = userService.findUserById(id);
user.setName(name);
userService.update(user);
return ImmutableMap.of(“ret”, 0, “msg”, “ok”);
}
UserService.java package com.answer.service; import java.util.List; import com.answer.entity.User; /**- Created by zhezhiyong@163.com on 2017/9/21.
*/
public interface UserService { List list(); User findUserById(Long id); User findInfoById(Long id); void update(User user); void remove(Long id); User upuser(Long id);
UserServiceImpl.java package com.answer.service.impl;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import com.answer.entity.Info;
import com.answer.entity.User;
import com.answer.service.UserService; import lombok.extern.slf4j.Slf4j; /**- Created by zhezhiyong@163.com on 2017/9/21.
*/
@Service
@Slf4j
public class UserServiceImpl implements UserService { private Map userMap = new HashMap<>();
private Map infoMap = new HashMap<>(); public UserServiceImpl() {
User u1=new User();
u1.setId(1L);
u1.setName(“1111”);
u1.setPassword(“11223434”);
User u2=new User();
u2.setId(1L);
u2.setName(“1111”);
u2.setPassword(“11223434”);
User u3=new User();
u3.setId(1L);
u3.setName(“1111”);
u3.setPassword(“11223434”);userMap.put(1L,u1); userMap.put(2L,u2); userMap.put(3L,u3); infoMap.put(1L, new Info("18559198715", "福州市"));
} @Override
public List list() {
return Arrays.asList(userMap.values().toArray());
} @Override
@Cacheable(value = “user”, key = “‘user’.concat(#id.toString())”)
public User findUserById(Long id) {
//log.info(“findUserById query from db, id: {}”, id);
System.out.println(“findUserById query from db, id: {}======”+id);
return userMap.get(id);
} @Override
@Cacheable(value = “info”, key = “‘info’.concat(#id.toString())”)
public User findInfoById(Long id) {
// log.info(“findInfoById query from db, id: {}”, id);
System.out.println(“findUserById query from db, id: {}======”+id);
return userMap.get(id);
} @Override
@CachePut(value = “user”, key = “‘user’.concat(#user.id.toString())”)
public void update(User user) {
//log.info(“update db, user: {}”, user.toString());
System.out.println(“findUserById query from db, id: {}======”);
userMap.put(user.getId(), user);
} @Override
@CacheEvict(value = “user”, key = “‘user’.concat(#id.toString())”)
public void remove(Long id) {
//log.info(“remove from db, id: {}”, id);
System.out.println(“findUserById query from db, id: {}======”);
userMap.remove(id);
} @Override
@CacheEvict(value = “user”, key = “‘user’.concat(#id.toString())”)
public User upuser(Long id) {
User d= userMap.get(id);
d.setName(“000000000000000000000000000000000000000000000000”);
return d;
}
}
注意:启动项目前一定要对 king-cache 进行打包(右键项目:Run As —Maven Install)
@Cacheable 触发缓存入口 @CacheEvict 触发移除缓存 @CacahePut 更新缓存 @Caching 将多种缓存操作分组 @CacheConfig 类级别的缓存注解,允许共享缓存名称 @CacheConfig
该注解是可以将缓存分类,它是类级别的注解方式。我们可以这么使用它。
这样的话,UseCacheRedisService 的所有缓存注解例如@Cacheable 的 value 值就都为 user。 @CacheConfig(cacheNames = “user”)
@Service
public class UseCacheRedisService {}
在 redis 的缓存中显示如下 127.0.0.1:6379> keys *
1) “user~keys”
2) “user_1”
127.0.0.1:6379> get user~keys
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> type user~keys
zset
127.0.0.1:6379> zrange user~keys 0 10
1) “user_1”
我们注意到,生成的 user~keys,它是一个 zset 类型的 key,如果使用 get 会报 WRONGTYPE Operation against a key holding the wrong kind of value。这个问题坑了我很久 @Cacheable
一般用于查询操作,根据 key 查询缓存. 如果 key 不存在,查询 db,并将结果更新到缓存中。
如果 key 存在,直接查询缓存中的数据。 查询的例子,当第一查询的时候,redis 中不存在 key,会从 db 中查询数据,并将返回的结果插入到 redis 中。 @Cacheable
public List selectAllUser(){
return userMapper.selectAll();
}
调用方式。 @Test
public void selectTest(){
System.out.println(“===========第一次调用=======”);
List list = useCacheRedisService.selectAllUser();
System.out.println(“===========第二次调用=======”);
List list2 = useCacheRedisService.selectAllUser();
for (User u : list2){
System.out.println(“u = ” + u);
}
}
打印结果,大家也可以试一试
只输出一次 sql 查询的语句,说明第二次查询是从 redis 中查到的。 ===========第一次调用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
DEBUG [main] – ==> Preparing: SELECT id,name,sex,age,password,account FROM user
DEBUG [main] – ==> Parameters:
DEBUG [main] – <== Total: 1
===========第二次调用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
u = User{id=1, name=’fsdfds’, sex=’fdsfg’, age=24, password=’gfdsg’, account=’gfds’} redis 中的结果
我们可以看到 redis 中已经存在
com.lzl.redisService.UseCacheRedisService.selectAllUser.记录了。
这么长的一串字符 key 是根据自定义 key 值生成的。 127.0.0.1:6379> keys *
1) “user~keys”
2) “com.lzl.redisService.UseCacheRedisService.selectAllUser.”
3) “user_1”
127.0.0.1:6379> get com.lzl.redisService.UseCacheRedisService.selectAllUser.
“[\”java.util.ArrayList\”,[[\”com.lzl.bean.User\”,{\”id\”:1,\”name\”:\”fsdfds\”,\”sex\”:\”fdsfg\”,\”age\”:24,\”password\”:\”gfdsg\”,\”account\”:\”gfds\”}]]]”
@CachePut
一般用于更新和插入操作,每次都会请求 db
通过 key 去 redis 中进行操作。- 如果 key 存在,更新内容
- 如果 key 不存在,插入内容。
/**
- 单个 user 对象的插入操作,使用 user+id
- @param user
- @return
*/
@CachePut(key = “\”user_\” + #user.id”)
public User saveUser(User user){
userMapper.insert(user);
return user;
}
redis 中的结果
多了一条记录 user_2
1) “user~keys”
2) “user_2”
3) “com.lzl.redisService.UseCacheRedisService.selectAllUser.”
4) “user_1”
127.0.0.1:6379> get user_2
“[\”com.lzl.bean.User\”,{\”id\”:2,\”name\”:\”fsdfds\”,\”sex\”:\”fdsfg\”,\”age\”:24,\”password\”:\”gfdsg\”,\”account\”:\”gfds\”}]”
@CacheEvict
根据 key 删除缓存中的数据。allEntries=true 表示删除缓存中的所有数据。 @CacheEvict(key = “\”user_\” + #id”)
public void deleteById(Integer id){
userMapper.deleteByPrimaryKey(id);
}
测试方法 @Test
public void deleteTest(){
useCacheRedisService.deleteById(1);
}
redis 中的结果
user_1 已经移除掉。 127.0.0.1:6379> keys *
1) “user~keys”
2) “user_2”
3) “com.lzl.redisService.UseCacheRedisService.selectAllUser.”
测试 allEntries=true 时的情形。 @Test
public void deleteAllTest(){
useCacheRedisService.deleteAll();
}
@CacheEvict(allEntries = true)
public void deleteAll(){
userMapper.deleteAll();
}
redis 中的结果
redis 中的数据已经全部清空 127.0.0.1:6379> keys *
(empty list or set)
@Caching
通过注解的属性值可以看出来,这个注解将其他注解方式融合在一起了,我们可以根据需求来自定义注解,并将前面三个注解应用在一起 public @interface Caching {
Cacheable[] cacheable() default {};CachePut[] put() default {}; CacheEvict[] evict() default {};
}
使用例子如下 @Caching(
put = {
@CachePut(value = “user”, key = “\”user_\” + #user.id”),
@CachePut(value = “user”, key = “#user.name”),
@CachePut(value = “user”, key = “#user.account”)
}
)
public User saveUserByCaching(User user){
userMapper.insert(user);
return user;
} @Test
public void saveUserByCachingTest(){
User user = new User();
user.setName(“dkjd”);
user.setAccount(“dsjkf”);
useCacheRedisService.saveUserByCaching(user);
}
redis 中的执行结果
一次添加三个 key 127.0.0.1:6379> keys *
1) “user~keys”
2) “dsjkf”
3) “dkjd”
4) “user_3”
Spring Boot2.X 自定义 Redis 的 cacheManager,保存 Json 格式到 Redis
1231 在自义定 cacheManager 方面,SpringBoot2.X 与SpringBoot1.X 有很大不同,SpringBoot2 用的是 按照它的样子,你也可以自定义一个 cacheManager,@Co… 来自: caojidasabi 作者:果壳中 de 宇宙
来源:CSDN
原文:https://blog.csdn.net/guokezhongdeyuzhou/article/details/79789629
版权声明:本文为博主原创文章,转载请附上博文链接! - Created by zhezhiyong@163.com on 2017/9/22.
/
@Data
@AllArgsConstructor
public class Info implements Serializable{
/*