SpringBoot杂碎知识 (十)缓存的使用

目录

RedisCacheManager

ehcache3.x、2.x


Spring Framework支持透明地向应用程序添加缓存。从本质上讲,抽象将缓存应用于方法,从而根据缓存中可用的信息减少执行次数。缓存逻辑应用透明,不会对调用者造成任何干扰。只要通过@EnableCaching 注释启用了缓存支持,Spring Boot就会自动配置缓存基础结构。下面我就介绍两个我比较常用的缓存。

啥也不管记得依赖依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

RedisCacheManager

首先呢先看下yml 依赖我就不贴了

spring:
  cache:
    type: redis
    redis:
      time-to-live: 1000s  #缓存1000s的生存时间 m分 d天  h小时
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    jedis:
      pool:
        max-active: 9
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
    database: 0

如果不想使用提供的默认配置希望改变 key或者value的序列化或者其他可以自定义cacheManager 注意这个注入的方式为springboot2.x以上的

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        template.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }


    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

如果只想定义缓存的序列化可以这样

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1));
        // 设置缓存有效期一小时
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

然后呢我们测试下怎么使用 首先呢写一个实体类

package com.maoxs.pojo;

import lombok.Data;

import java.io.Serializable;

@Data
public class User implements Serializable {
    private Long id;
    private String name;

    public User() {
    }

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("User{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

这里呢用到了一个自定义的key   wiselyKeyGenerator 这里贴出代码

  /**
     * 设置统一的生成key的方式
     * @return
     */
    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append("-");
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

然后建一个service

package com.maoxs.service;

import com.maoxs.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
@CacheConfig(cacheNames = "myuser")
@Slf4j
public class UserService {

    public static final Map<Long, User> users = new HashMap<>();

    static {
        users.put(1L, new User(1L, "我是快乐鱼"));
        users.put(2L, new User(1L, "我是忧郁猫"));
        users.put(3L, new User(1L, "我是昴先生"));
    }

    /**
     * 使用自定义的key
     *
     * @param id
     * @return
     */
    @Cacheable(keyGenerator = "wiselyKeyGenerator")
    public User getUserKeyGen(Long id) {
        log.info("这是自定义key,redis里没有从map中读取");
        return users.get(id);
    }

    /**
     * 从缓存中读取否则读map
     *
     * @param id
     * @return
     */
    @Cacheable(key = "#id")
    public User getUser(Long id) {
        log.info("redis中没有,从map中读取");
        return users.get(id);
    }

    /**
     * 将方法的返回值放到缓存中
     * 在方法的调用前并不会检查缓存,方法始终都会被调用。
     *
     * @param user
     */
    @CachePut(key = "#id")
    public User saveOrUpdate(Long id, User user) {
        log.info("进入 saveOrUpdate 方法");
        users.put(id, user);
        return users.get(id);
    }

    /**
     * 清除缓存
     *
     * @param id
     */
    @CacheEvict(key = "#id")
    public void delete(Long id) {
        users.remove(id);
        log.info("进入 delete 方法");
    }


}

测试一下

package com.maoxs;

import com.maoxs.pojo.User;
import com.maoxs.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.cache.RedisCacheManager;

public class RedisCacheTest extends SpringbootCacheApplicationTests {
    @Autowired
    private UserService userService;
    @Autowired
    private RedisCacheManager redisCacheManager;

    @Test
    public void getUserKeyGen() {
        User userKeyGen = userService.getUserKeyGen(1L);
        System.out.println(userKeyGen);
    }

    @Test
    public void getUser() {
        User user = userService.getUser(1L);
        System.out.println(user);
    }

    @Test
    public void saveOrUpdate() {
        User user = new User(3L, "我的的 的的的");
        userService.saveOrUpdate(1L, user);
    }

    @Test
    public void delete() {
        userService.delete(1L);
    }

}

首先介绍下service中几个注解是什么意思

注解 描述
@Cacheable 在调用方法之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,就会返回缓存的值。否则,这个方法就会被调用,返回值会放到缓存之中。
@CachePut 将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用。
@CacheEvict 在缓存中清除一个或多个条目。
@Caching 分组的注解,能够同时应用多个其他的缓存注解。这里不做介绍有兴趣可以研究下

@Cacheable(根据方法的请求参数对其结果进行缓存)

  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:@Cacheable(value="user",key="#userName")
  • value: 缓存的名称,必须指定至少一个(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"})
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")

@CachePut(根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用)

  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:@Cacheable(value="user",key="#userName")
  • value: 缓存的名称,必须指定至少一个(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"})
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")

@CachEvict(根据条件对缓存进行清空)

  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:@Cacheable(value="user",key="#userName")
  • value: 缓存的名称,必须指定至少一个(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"})
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")
  • allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如:@CacheEvict(value = "user", key = "#id", allEntries = true)
  • beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如:@CacheEvict(value = "user", key = "#id", beforeInvocation = true)

启动测试类,结果和我们期望的一致,可以看到第一次调用getUser,会在redis中插入缓存,因为缓存中不存在,此时,是会打印日志的。第二次调用呢是没有日志输出的,因为他是直接从缓存中获取数据,刷新缓存呢都是会进入方法内执行具体的业务代码,然后通过切面去除掉redis中缓存的数据,这里面的那个#号代表是一个spel 表达式,具体呢可以去spring官网查看详细用法。

这里说明一下 如果注解不能满足你的需求可以使用redisCacheManager 具体用法如下例子

package com.maoxs;

import com.maoxs.pojo.User;
import com.maoxs.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;

public class RedisCacheTest extends SpringbootCacheApplicationTests {
 
    @Autowired
    private RedisCacheManager redisCacheManager;
     
    @Test
    public void managerTest() {
        Cache cache = redisCacheManager.getCache("fulin");
        cache.put("1", "我看不清楚");
        Cache.ValueWrapper valueWrapper = cache.get("1");
        System.out.println(valueWrapper.get());

    }
}

这里为了方便大家给大家一个封装的抽象类

package com.maoxs.controller;

import org.springframework.cache.Cache;

/**
 * fulin缓存抽象类
 */
public abstract class AbstractCacheSupport {

    /**
     * 获取缓存内容
     * @param cache
     * @param key
     * @return
     */
    protected Object getFromCache(Cache cache, String key) {
        final Cache.ValueWrapper valueWrapper = cache.get(key);
        return null == valueWrapper ? null : valueWrapper.get();
    }

    /**
     * 设置缓存数据
     * @param cache
     * @param key
     * @param value
     * @return
     */
    protected boolean putCache(Cache cache, String key, Object value) {
        if (null == value) {
            return false;
        }
        cache.put(key, value);

        return true;
    }

    /**
     * 删除缓存数据
     * @param cache
     * @param key
     * @return
     */
    protected boolean evictFromCache(Cache cache, Object key) {
        if (null == key) {
            return false;
        }
        cache.evict(key);

        return true;
    }
}

ehcache3.x、2.x

ehcache算是比较老牌的缓存了,随着时代的进步ehcache已经更新到了3.x版本,虽然今天本贴只是介绍整合spingboot的使用,但是其功能远不如此,值得大家去细细学习一波//TODO会一起分享的 ehcache学习手册

这里呢我采用的ehcache加 jsr107(jcache)的整合首先呢看下yml

spring:
  cache:
    type: jcache
    jcache:
      config: classpath:cache/ehcache.xml
logging:
  level:
    root: info

依赖依赖

        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- JSR107 API -->
        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
        </dependency>

这里使用的是xml配置 在此贴出xml

<?xml version="1.0" encoding="UTF-8"?>
<config
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
    xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

    <cache-template name="heap-cache">
        <resources>
            <heap unit="entries">2000</heap>
            <offheap unit="MB">100</offheap>
        </resources>
    </cache-template>

    <cache alias="myuser" uses-template="heap-cache">
        <expiry>
            <ttl unit="seconds">40</ttl>
        </expiry>
    </cache>

</config>

然后呢之前做redis缓存时候的类接着拿来用  切记CacheConfig中的缓存名称要跟xml文件中的cacahe 别名保持一致

package com.maoxs.service;

import com.maoxs.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
@CacheConfig(cacheNames = "myuser")
@Slf4j
public class UserService {

    public static final Map<Long, User> users = new HashMap<>();

    static {
        users.put(1L, new User(1L, "我是快乐鱼"));
        users.put(2L, new User(1L, "我是忧郁猫"));
        users.put(3L, new User(1L, "我是昴先生"));
    }

    /**
     * 使用自定义的key
     *
     * @param id
     * @return
     */
    @Cacheable(keyGenerator = "wiselyKeyGenerator")
    public User getUserKeyGen(Long id) {
        log.info("这是自定义key,ehcahe里没有从map中读取");
        return users.get(id);
    }

    /**
     * 从缓存中读取否则读map
     *
     * @param id
     * @return
     */
    @Cacheable(key = "#id")
    public User getUser(Long id) {
        log.info("ehcahe中没有,从map中读取");
        return users.get(id);
    }

    /**
     * 将方法的返回值放到缓存中
     * 在方法的调用前并不会检查缓存,方法始终都会被调用。
     *
     * @param user
     */
    @CachePut(key = "#id")
    public User saveOrUpdate(Long id, User user) {
        log.info("进入 saveOrUpdate 方法");
        users.put(id, user);
        return users.get(id);
    }

    /**
     * 清除缓存
     *
     * @param id
     */
    @CacheEvict(key = "#id")
    public void delete(Long id) {
        users.remove(id);
        log.info("进入 delete 方法");
    }


}

由于ehcache存在内存中所以这里测试改为接口测试

package com.maoxs.controller;

import com.maoxs.pojo.User;
import com.maoxs.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/getUserKeyGen/{id}")
    public void getUserKeyGen(@PathVariable("id") Long id) {
        User userKeyGen = userService.getUserKeyGen(id);
        System.out.println(userKeyGen);
    }

    @RequestMapping("/getUser/{id}")
    public void getUser(@PathVariable("id") Long id) {
        User user = userService.getUser(id);
        System.out.println(user);
    }

    @RequestMapping("/saveOrUpdate/{id}")
    public void saveOrUpdate(@PathVariable("id") Long id) {
        User user = new User(3L, "我的的 的的的");
        userService.saveOrUpdate(id, user);
    }

    @RequestMapping("/delete/{id}")
    public void delete(@PathVariable("id") Long id) {
        userService.delete(id);
    }

}

然后呢访问对应的url看对应的日志即可访问。 注解呢在上面有提到怎么使用的

注意,有时候spring cache的注解可能没有满足我们的需求,注入一下代码即可


    @Bean
    public JCacheCacheManager jCacheCacheManager() throws URISyntaxException {
        CachingProvider provider = Caching.getCachingProvider();
        JCacheCacheManager jCacheCacheManager = new JCacheCacheManager();
        javax.cache.CacheManager eh107CacheManager = provider.getCacheManager(getClass().getResource("/cache/ehcache.xml").toURI(), getClass().getClassLoader());
        jCacheCacheManager.setCacheManager(eh107CacheManager);
        return jCacheCacheManager;
    }

怎么使用呢 这样就行

    @Autowired
    private JCacheCacheManager jCacheCacheManager;

    public void getUserByManager() {
        Cache cache = jCacheCacheManager.getCache("myuser");
        User user = new User(1L, "我是快乐鱼");
        cache.put(1L, user);
    }

顺便提一下2.x的使用 yml要改为

    
spring:
  cache:
    ehcache:
      config: classpath:cache/ehcache.xml

manager配置呢应该改为这样

    /**
     * ehcache 主要的管理器
     * @param bean
     * @return
     */
    @Bean
    public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean){
        return new EhCacheCacheManager(bean.getObject());
    }
    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean(){
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("cache/ehcache.xml"));
        factoryBean.setShared(true);
        return factoryBean;
    }

注:如果不对联系本宝宝及时改正委屈~

猜你喜欢

转载自blog.csdn.net/qq_32867467/article/details/81738758