springboot集成ehcache或Redis作为缓存

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




    

    

    
    

  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 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
    临时设置:config set
    永久设置:config rewrite,将目前服务器的参数配置写入redis.conf

知识索引:

  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 accountTemplate(JedisConnectionFactory connectionFactory) {
        RedisTemplate 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 findAll() {
      return accountRepository.findAll();
  }

test

    @Resource
    private RedisTemplate 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.

@Configuration
public class RedisConfig {
    @Resource
    private LettuceConnectionFactory lettuceConnectionFactory;


    @Bean
    public CacheManager cacheManager() {
        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()
                .entryTtl(Duration.ofDays(30))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
        return RedisCacheManager.builder(lettuceConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }

}

implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:2.9.8'


@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List> converters) {
        List supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(new HibernateAwareObjectMapper());
        converter.setPrettyPrint(true);
        converter.setSupportedMediaTypes(supportedMediaTypes);
        converters.add(converter);
        super.configureMessageConverters(converters);
    }

}

public class HibernateAwareObjectMapper extends ObjectMapper {
    public HibernateAwareObjectMapper() {
        registerModule(new Hibernate5Module());
    }
}

知识索引:

Spring Boot 使用 Redis 缓存

你可能感兴趣的:(springboot集成ehcache或Redis作为缓存)