Springboot 2.X中Spring-cache与redis整合

一、本次案例

        Springboot中Spring-cache与redis整合。这也是一个不错的框架,与spring的事务使用类似,只要添加一些注解方法,就可以动态的去操作缓存了,减少代码的操作。如果这些注解不满足项目的需求,我们也可以参考spring-cache的实现思想,使用AOP代理+缓存操作来管理缓存的使用。 

        在这个例子中我使用的是redis,当然,因为spring-cache的存在,我们可以整合多样的缓存技术,例如Ecache、Mamercache等。 

二、创建springBoot 项目  king-cache

Springboot 2.X中Spring-cache与redis整合_第1张图片

  1. 在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来做配置

  1. 创建redis的配置类RedisConfig.java
  2. 注意一定要在类上加上以下两个注解:
  3.     @Configuration  可理解为用spring的时候xml里面的标签
  4.     @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 [email protected] 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

Springboot 2.X中Spring-cache与redis整合_第2张图片

添加以下依赖:



	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 [email protected] 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; }

User.java

package com.answer.entity;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 

* Created by [email protected] 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; }

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 [email protected] 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 [email protected] 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 [email protected] 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)

然后:king-cache-1 右键 maven ---Update Project

最后:以Debug 模式启动 项目:king-cache-1 

在以下方法出添加断点:

然后多次访问:http://localhost:8080/user/3

你会发现第一次进入断点,后面几次不再进入断点.

一定要添加上这个注解:@Cacheable


Springboot 2.X中Spring-cache与redis整合_第3张图片

项目源码下载地址: https://download.csdn.net/download/guokezhongdeyuzhou/10322780


注意以下注解的作用:

缓存的注解介绍

@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查询缓存.

  1. 如果key不存在,查询db,并将结果更新到缓存中。
  2. 如果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中进行操作。 
1. 如果key存在,更新内容 
2. 如果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

127.0.0.1:6379> keys *
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"

你可能感兴趣的:(Java开发)