来到JavaEE

springboot集成ehcache或Redis作为缓存

2018-12-27  本文已影响0人  mihope

springboot 缓存

基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案,而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache 集成。Spring boot 默认使用的是 SimpleCacheConfiguration,即使用 ConcurrentMapCacheManager 来实现缓存。

相关配置

application.properties

server.port=9090
spring.datasource.url=jdbc:mysql://localhost:3306/test_cache?autoReconnect=true&useUnicode=true&allowMultiQueries=true&useSSL=false&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.platform=mysql
spring.messages.encoding=UTF-8
#jpa
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.open-in-view=true
spring.jpa.hibernate.use-new-id-generator-mappings=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
# logs
spring.output.ansi.enabled=detect
logging.file=logs/log-v1.log
logging.level.com.welooky.cache.demo.*=INFO
logging.level.org.springframework.web=INFO
logging.level.org.hibernate=INFO
#ehcache的配置
spring.cache.ehcache.config=classpath:config/ehcache.xml
dependencies {
    compile('org.springframework.boot:spring-boot-starter-cache')
    //compile('org.springframework.boot:spring-boot-starter-data-redis')
    compile('net.sf.ehcache:ehcache')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    runtime('mysql:mysql-connector-java')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache
        updateCheck="true"
        monitoring="autodetect"
        dynamicConfig="true">

    <diskStore path="java.io.tmpdir"/>

    <defaultCache
            maxElementsInMemory="500"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="1200"
            overflowToDisk="true"
            diskSpoolBufferSizeMB="100"/>

    <cache name="account"
           maxElementsInMemory="1000"
           memoryStoreEvictionPolicy="LRU"
           timeToIdleSeconds="30000"
           timeToLiveSeconds="10000"
           diskSpoolBufferSizeMB="400"
           overflowToDisk="true"/>
    <!--
    maxElementsInMemory : 缓存最大个数、
    eternal : 对象是否永久有效,一但设置了,timeout将不起作用
    timeToIdleSeconds : 设置对象在失效前的允许闲置时间(单位:秒)。
                        仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
    timeToLiveSeconds : 设置对象在失效前允许存活时间(单位:秒)。
                        最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.也就是对象存活时间无穷大。
    overflowToDisk : 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
    diskSpoolBufferSizeMB : 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
    maxElementsOnDisk : 硬盘最大缓存个数。
    diskPersistent : 是否缓存虚拟机重启期数据
    diskExpiryThreadIntervalSeconds : 磁盘失效线程运行时间间隔,默认是120秒。
    memoryStoreEvictionPolicy : 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。
                                默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
    clearOnFlush: 内存数量最大时是否清除。
    -->
</ehcache>
  1. 使用@EnableCaching 启用 Cache 注解支持;
  2. 实现 CachingConfigurer,然后注入需要的 cacheManager 和 keyGenerator;从 spring4 开始默认的 keyGenerator 是 SimpleKeyGenerator;
  3. Spring cache 利用了 Spring AOP 的动态代理技术

注解说明

@Cacheable

主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。

@CachePut

配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable 不同的是,它可以既保证方法被调用,
又可以实现结果被缓存。所以主要用于数据新增和修改操作上,同时能够起到更新缓存的作用。它的参数与@Cacheable 类似,具体功能可参考上面对@Cacheable 参数的说明。

@CacheEvict

配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。

@CacheConfig

是类级别的注解,表示该类中方法定义的所有缓存操作默认使用的缓存 name。如果方法上有@Cacheable 注解,会覆盖它。

  1. spring cache 是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效,缓存不起作用。
  2. 和内部调用问题类似,非 public 方法如果想实现基于注释的缓存,必须采用基于 AspectJ 的 AOP 机制
@Service
public class AccountService {
    @Resource
    private AccountRepository accountRepository;
    private Logger logger = LoggerFactory.getLogger(AccountService.class);
    private static final String CACHE_ACCOUNT = "account";

    public void saveUser(String username, String password) {
        Account account = new Account(username, password);
        accountRepository.save(account);
    }

    @Cacheable(value = CACHE_ACCOUNT, key = "#root.caches[0].name + ':' +#username")
    public Account findByUsername(String username) {
        logger.info("select info from db");
        return accountRepository.findByUsername(username);
    }

    @CachePut(value = CACHE_ACCOUNT, key = "#root.caches[0].name + ':' +#username")
    public Account updateUserInfo(String username, String password) {
        Account account = accountRepository.findByUsername(username);
        account.setPassword(password);
        logger.info("update info inside db");
        return accountRepository.save(account);
    }

    @CacheEvict(value = CACHE_ACCOUNT, key = "#scope+':'+#username")
    public void clearCache(String username, String scope) {
        logger.info("clear cache");
    }

    @Cacheable(value = CACHE_ACCOUNT, keyGenerator = "aKeyGenerator")
    public List<Account> findAll() {
        return accountRepository.findAll();
    }

    @Cacheable(value = CACHE_ACCOUNT, keyGenerator = "bKeyGenerator")
    public Account getAccountById(Long id) {
        return accountRepository.getOne(id);
    }

    @CacheEvict(value = CACHE_ACCOUNT, keyGenerator = "bKeyGenerator")
    public void deleteAccountById(Long id) {
        accountRepository.deleteById(id);
    }
}

知识索引:

  1. Spring Cache 抽象详解
  2. 注释驱动的 Spring cache 缓存介绍

Redis

Redis是一个开源,先进的key-value存储,并用于构建高性能,可扩展的Web应用程序的完美解决方案。
Redis从它的许多竞争继承来的三个主要特点:
Redis数据库完全在内存中,使用磁盘仅用于持久性。
相比许多键值数据存储,Redis拥有一套较为丰富的数据类型。
Redis可以将数据复制到任意数量的从服务器。

Redis 优势
异常快速:Redis的速度非常快,每秒能执行约11万集合,每秒约81000+条记录。
支持丰富的数据类型:Redis支持最大多数开发人员已经知道像列表,集合,有序集合,散列数据类型。这使得它非常容易解决各种各样的问题,因为我们知道哪些问题是可以处理通过它的数据类型更好。
操作都是原子性:所有Redis操作是原子的,这保证了如果两个客户端同时访问的Redis服务器将获得更新后的值。
多功能实用工具:Redis是一个多实用的工具,可以在多个用例如缓存,消息,队列使用(Redis原生支持发布/订阅),任何短暂的数据,应用程序,如Web应用程序会话,网页命中计数等

Redis 安装

知识索引:centos install redis

Redis 配置

知识索引:

  1. redis 教程
  2. redis 中国

Redis 命令

  1. 删除当前数据库中的所有 Key:flushdb
  2. 删除所有数据库中的 key:flushall
  3. 删除指定 key:delkey

项目配置

去掉 Elcache 的相关配置,使项目的缓存切换到 Redis

application.properties

# redis
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000ms

修改 gradle

dependencies {
    compile('org.springframework.boot:spring-boot-starter-cache')
    compile('org.springframework.boot:spring-boot-starter-data-redis')
  //compile('net.sf.ehcache:ehcache')
}

RedisConfig

package com.welooky.cache.demo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.welooky.cache.demo.entity.Account;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
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.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;

@Configuration
public class RedisConfig {

    @Bean("aKeyGenerator")
    public KeyGenerator aKeyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(".").append(method.getName());
            StringBuilder paramsSb = new StringBuilder();
            for (Object param : params) {
                if (param != null) {
                    paramsSb.append("_").append(param.toString());
                }
            }
            if (paramsSb.length() > 0) {
                sb.append("_").append(paramsSb);
            }
            return sb.toString();
        };
    }

    @Bean("bKeyGenerator")
    public KeyGenerator bKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                String[] value = new String[1];
                Cacheable cacheable = method.getAnnotation(Cacheable.class);
                if (cacheable != null) {
                    value = cacheable.value();
                }
                CachePut cachePut = method.getAnnotation(CachePut.class);
                if (cachePut != null) {
                    value = cachePut.value();
                }
                CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
                if (cacheEvict != null) {
                    value = cacheEvict.value();
                }
                sb.append(value[0]);
                for (Object obj : params) {
                    sb.append(":").append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    @Bean
    public JedisConnectionFactory connectionFactory() {
        return new JedisConnectionFactory();
    }

    @Bean
    public CacheManager cacheManager(JedisConnectionFactory connectionFactory) {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }

    @Bean
    public RedisTemplate<String, Account> accountTemplate(JedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Account> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Account.class));
        return template;
    }
}

使用 keyGenerator

  @Cacheable(value = "account", keyGenerator = "aKeyGenerator")
  public List<Account> findAll() {
      return accountRepository.findAll();
  }

test

    @Resource
    private RedisTemplate<String, Account> redisTemplate;

    @Test
    public void testRedisTemplate() {
        redisTemplate.opsForValue().set("andy", new Account("mingXi", "mingxi"));
        Assert.assertEquals("mingXi", redisTemplate.opsForValue().get("andy").getUsername());
    }

生成的缓存 key 为:account::com.welooky.cache.demo.service.AccountService.findAll

使用命令keys *查看所有的缓存 key.

知识索引:

Spring Boot 使用 Redis 缓存

上一篇下一篇

猜你喜欢

热点阅读