美文网首页来到JavaEE
springboot集成ehcache或Redis作为缓存

springboot集成ehcache或Redis作为缓存

作者: mihope | 来源:发表于2018-12-27 17:18 被阅读0次

    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

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

    • value :
      缓存的名称,在 spring 配置文件中定义,必须指定至少一个
      @Cacheable(value="account") 或者@Cacheable(value={"cache1","cache2"}
    • key :
      缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则按照方法的所有参数进行组合
      @Cacheable(value="account",key="#userName")
    • condition:
      缓存的条件,可以为空,使用 SpEL。返回 true 或者 false,只有为 true 才进行缓存
      @Cacheable(value="account",condition="#userName.length()>2")
    • unless
      缓存的条件,非必需,使用 SpEL。该条件是在函数被调用之后才做判断的,它可以对 result 进行判断。

    @CachePut

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

    @CacheEvict

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

    • value
      缓存的名称,在 spring 配置文件中定义,必须指定至少一个
      @CachEvict(value="mycache") 或者 @CachEvict(value={"cache1","cache2"}
    • key
      缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
      @CachEvict(value="account",key="#userName")
    • condition
      缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存
      @CachEvict(value="account",condition="#userName.length()>2")
    • allEntries
      是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
      @CachEvict(value="account",allEntries=true)
    • beforeInvocation
      非必需,默认为 false,会在调用方法之后移除数据,如果方法执行中抛出异常,则不会清空缓存。为 true 时,会在调用方法之前移除数据。
      @CachEvict(value="account",beforeInvocation=true)

    @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 安装

    • mac

      1. 执行 brew install redis
      2. 启动 redis,可以使用后台服务启动 brew services start redis。或者直接启动:redis-server /usr/local/etc/redis.conf
      3. $ redis-cli
    • ubuntu

      1. $ sudo apt-get update
      2. $ sudo apt-get install redis-server
      3. $ redis-server
      4. $ redis-cli
    • linux

      1. $ wget http://download.redis.io/releases/redis-4.0.6.tar.gz
      2. $ tar xzf redis-4.0.6.tar.gz
      3. $ cd redis-4.0.6
      4. $ make
      5. $ cd src
      6. $ ./redis-server
      7. $ ./redis-cli

    知识索引:centos install redis

    Redis 配置

    • $ redis-cli:该命令会连接本地的 redis 服务
    • 连接远程:$ redis-cli -h host -p port -a password
    • 查看所有配置:config get *
    • 查看某个配置:config get requirepass
    • 修改某个配置:config set config_setting_name new_config_value

    知识索引:

    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 缓存

    相关文章

      网友评论

        本文标题:springboot集成ehcache或Redis作为缓存

        本文链接:https://www.haomeiwen.com/subject/qqldlqtx.html