侧边栏壁纸
博主头像
术业有道之编程博主等级

亦是三月纷飞雨,亦是人间惊鸿客。亦是秋霜去叶多,亦是风华正当时。

  • 累计撰写 99 篇文章
  • 累计创建 50 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

springframework cache的使用

Administrator
2020-10-24 / 0 评论 / 0 点赞 / 166 阅读 / 13488 字

写在前面

  • 同类文章非常多了,而且也不是什么新功能,写它目的仅仅是记录 cache 的使用过程,这里我只介绍我用到的,更全的介绍请自行百度。
  • 我是使用redis作为缓存工具而非应用程序内存

一、准备工作

  • redis 服务
  • springBoot框架
  • 这里我使用的是kotlin,使用java也是一样的

二、配置redis 缓存

  • 需要的maven坐标
<!--springboot的redis操作 starter-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--阿里巴巴的 fastjson,用来序列化存储(非必须)-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.68</version>
</dependency>
  • 缓存的数据结构(故意放到前面说,方便理解后面的配置)
    • 缓存由 缓存名、缓存key、缓存值 3部分组成
    • 对应理解为一张数据表、一条数据的主键+索引、实际的数据内容
  • 使用springBoot的方式正常配置redis

  • 创建一个 redis的配置器
    RedisCacheConfig

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer
import com.ayouran.crawler.common.bo.DetectionBO
import com.ayouran.crawler.common.bo.MilkBO
import com.ayouran.crawler.common.vo.FilterConditionVO
import com.ayouran.crawler.common.vo.MilkDetailsVO
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.context.annotation.Primary
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager
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 org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.RedisSerializer
import java.lang.reflect.Method
import java.net.UnknownHostException
import java.time.Duration


/**
 * Redis 缓存配置类
 */
@Configuration
@EnableCaching
class RedisCacheConfig : CachingConfigurerSupport() {
    /**
     * 缓存对象集合中,缓存是以 key-value 形式保存的。
     * 当不指定缓存的 key 时,SpringBoot 会使用 SimpleKeyGenerator 生成 key。
     */
    @Bean
    fun wiselyKeyGenerator(): KeyGenerator { // key前缀,用于区分不同项目的缓存,建议每个项目单独设置
        val PRE_KEY = "milk_cache"
        val sp = ':'
        return KeyGenerator { target: Any, method: Method, params: Array<Any> ->
            val sb = StringBuilder()
            sb.append(PRE_KEY)
            sb.append(sp)
            sb.append(target.javaClass.simpleName)
            sb.append(sp)
            sb.append(method.name)
            for (obj in params) {
                if (null == obj) continue
                sb.append(sp)
                sb.append(obj.toString())
            }
            sb.toString()
        }
    }

    /**
     * 定义 spring-boot-data-redis的 redisTemplate对象 主要是序列化方式
     */
    @Bean
    @Throws(UnknownHostException::class)
    fun redisTemplate(
            redisConnectionFactory: RedisConnectionFactory): RedisTemplate<Any, Any> {
        val template = RedisTemplate<Any, Any>()
        template.connectionFactory = redisConnectionFactory
        //fastjson的序列化器
        //FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
        val serializer: Jackson2JsonRedisSerializer<*> = Jackson2JsonRedisSerializer<Any>(Any::class.java)
        //设置序列化器
        template.defaultSerializer = serializer
        return template
    }

    /**
     * 定义 spring-boot-data-redis的 stringRedisTemplate对象 主要是序列化方式
     */
    @Bean
    @Throws(UnknownHostException::class)
    fun stringRedisTemplate(
            redisConnectionFactory: RedisConnectionFactory): StringRedisTemplate {
        val template = StringRedisTemplate()
        //fastjson的序列化器
        val serializer: FastJsonRedisSerializer<*> = FastJsonRedisSerializer(Any::class.java)
        //Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        template.valueSerializer = serializer
        //设置序列化器
        template.connectionFactory = redisConnectionFactory
        return template
    }

    /**
     * 启用@Cacheable等注解时,redis里面用到的key--value的序列化
     * key = new StringRedisSerializer()
     * value = new JdkSerializationRedisSerializer()
     * 以及缓存的时效
     */
    fun redisCacheConfiguration(serializer: RedisSerializer<*>?, duration: Duration): RedisCacheConfiguration {
        var configuration = RedisCacheConfiguration.defaultCacheConfig()
        if (null != serializer) {
            configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer<Any>(serializer as RedisSerializer<Any>))
        }
        configuration = configuration.computePrefixWith { cacheName: String -> "milk:$cacheName:" }
        configuration = configuration.entryTtl(duration)//设置缓存的时效
        return configuration
    }

    /**
     * @Primary  配置默认的缓存管理器
     * valueSerializationPair:使用Jackson2JsonRedisSerializer()
     * 使用方式:如果不指定cacheManagers属性,就会使用默认的CacheManager
     * @Cacheable(value = "cache_1_minutes",keyGenerator = "myKeyGenerator")
     * @param redisConnectionFactory
     */
    @Primary
    @Bean
    fun defaultCacheManager(redisConnectionFactory: RedisConnectionFactory): RedisCacheManager {
        val serializer: Jackson2JsonRedisSerializer<*> = Jackson2JsonRedisSerializer<Any>(Any::class.java)
        val builder = RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration(serializer, Duration.ofDays(30)))
        //可以抽取的公共配置
        val map = mutableMapOf<String, RedisCacheConfiguration>()
//        map["cache_1_minutes"] = redisCacheConfiguration(serializer, Duration.ofMinutes(1))
//        map["cache_10_minutes"] = redisCacheConfiguration(serializer, Duration.ofMinutes(10))
//        map["cache_1_hour"] = redisCacheConfiguration(serializer, Duration.ofHours(1))
//        map["cache_10_hour"] = redisCacheConfiguration(serializer, Duration.ofHours(10))
//        map["cache_1_day"] = redisCacheConfiguration(serializer, Duration.ofDays(1))
//        map["cache_7_days"] = redisCacheConfiguration(serializer, Duration.ofDays(7))
        map["milk:best:milk_cache:MilkServiceImpl:best"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        map["milk:best:milk_cache:MilkServiceImpl:query"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        map["milk:best:milk_cache:MilkServiceImpl:queryContrastOptions"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        map["milk:hotWords:milk_cache:UserServiceImpl:hotWords"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        map["milk:hotWords:milk_cache:UserServiceImpl:nutritions"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        builder.withInitialCacheConfigurations(map)
        return builder.build()
    }

    /**
     * 为PageResult配置一个单独的RedisCacheManager
     * 同理,如果Department也想把数据以json的形式放入缓存,再添加一个RedisCacheManager即可
     *
     *
     * 使用方式:@Cacheable(value = "cache_1_minutes",keyGenerator = "myKeyGenerator",cacheManager = "pageResultCacheManager")
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    fun pageResultCacheManager(redisConnectionFactory: RedisConnectionFactory): RedisCacheManager { //employee的使用FastJSON的序列化器
        val serializer: FastJsonRedisSerializer<*> = FastJsonRedisSerializer(PageResult::class.java)
        val builder = RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration(serializer, Duration.ofDays(30)))
        val map = mutableMapOf<String, RedisCacheConfiguration>()
//        map["cache_1_minutes"] = redisCacheConfiguration(serializer, Duration.ofMinutes(1))
//        map["cache_10_minutes"] = redisCacheConfiguration(serializer, Duration.ofMinutes(10))
//        map["cache_1_hour"] = redisCacheConfiguration(serializer, Duration.ofHours(1))
//        map["cache_10_hour"] = redisCacheConfiguration(serializer, Duration.ofHours(10))
//        map["cache_7_days"] = redisCacheConfiguration(serializer, Duration.ofDays(7))
        map["milk:best:milk_cache:MilkServiceImpl:pageEvaluate"] = redisCacheConfiguration(serializer, Duration.ofDays(1))
        map["milk:best:milk_cache:MilkServiceImpl:homeSearch"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        map["milk:best:milk_cache:MilkServiceImpl:search"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        map["milk:hotWords:milk_cache:UserServiceImpl:queryCollection"] = redisCacheConfiguration(serializer, Duration.ofDays(30))
        builder.withInitialCacheConfigurations(map)
        return builder.build()
    }

  • 解释一下代码
    • 类上的 @Configuration @EnableCaching 这两个注解是必须的
    • wiselyKeyGenerator() 用于创建一个默认的key生成器,会在redis存储``key```上体现。
    • redisTemplate()、stringRedisTemplate() 用于创建spring-boot-data-redis的默认操作对象模板,主要是指定了序列化方式。
    • redisCacheConfiguration() 将cache的具体操作抽象成一个方法,其中 configuration = configuration.computePrefixWith { cacheName: String -> "milk:$cacheName:" } 仅仅是设置前缀,可以不需要。
    • defaultCacheManager() 这是一个默认的缓存管理器,主要是适用于于实现了Collection<E>的方法出参(即这个方法的返回参数是一个 Collection<E>实现类 ),原因在于本方法使用的Jackson2JsonRedisSerializer 默认class是一个Object对象。
    • pageResultCacheManager() 单独为分页方法返回pageResult对象配置的一个缓存管理器,主要是是使用了FastJsonRedisSerializer来指定class是一个PageResult对象。
    • 如果你有非常多的自定义Object对象返回,建议抽象成一个base对象,那就只需要写一个缓存管理器,或者像我样,每出现一个新的Object对象返回就定义一个新的缓存管理器
    • 需要注意缓存管理器中通过缓存名做了不同的缓存过期时间设置,这个缓存名需要根据你定义的来

三、使用缓存

  • 在要使用缓存的类上加上 @CacheConfig(keyGenerator = "wiselyKeyGenerator") 配置默认的缓存键生成方式(即整个类都会默认应用这个生成方式),这个不是必须的,也可以在缓存的地方手动指定
  • 下面是一个缓存使用的示例代码
 @Cacheable(value = ["pageEvaluate"], key = "#pageRequest.toString()", cacheManager = "pageResultCacheManager")
    override fun pageEvaluate(pageRequest: com.ayouran.crawler.common.PageRequest): PageResult<ScoreVO> {
        val nmilk = nmilkRepository.findByMilkNo(pageRequest.query!!)
        val qTmpUser = QTmpUser.tmpUser
        val qScoreRecord = QScoreRecord.scoreRecord
        val rredicate = qScoreRecord.brand.eq(nmilk.brand)
                .and(qScoreRecord.series.eq(nmilk.series))
                .and(qScoreRecord.edition.eq(nmilk.edition))
        val order = if (null != pageRequest.sort) qScoreRecord.createAt.desc() else qScoreRecord.praise.desc()
        val exprs = arrayOf<Expression<*>>(qTmpUser.head, qTmpUser.nick, qScoreRecord.remark, qScoreRecord.score,
                qScoreRecord.praise, qScoreRecord.tags, qScoreRecord.id, qScoreRecord.createAt)
        val results = querydslUtlis.getQueryFactory()
                .select(*exprs)
                .from(qScoreRecord)
                .leftJoin(qTmpUser)
                .on(qTmpUser.userNo.eq(qScoreRecord.userNo))
                .where(ExpressionUtils.allOf(rredicate))
                .orderBy(order)
                .offset((pageRequest.getPageIndex()) * pageRequest.getPageSize())
                .limit(pageRequest.getPageSize())
                .fetchResults()
        return PageUtlis.retPage(results, querydslUtlis.getList(results.results, exprs, ScoreVO::class.java))
    }
  • 代码说明
    • @Cacheable 表示使用缓存注解,会先根据注解的配置来生成redis 缓存键通过序列化器来尝试获取缓存数据,如果没有命中,就会执行方法体,当方法体正常返回时,将返回数据缓存到redis
    • @Cacheable#value = ["pageEvaluate"] 通过这个指定缓存的名称,相当于一个表名。
    • @Cacheable#key = "#pageRequest.toString()" 通过这个自己指定缓存的值,相当于数据表中的 索引&主键,如果不指定这个key属性,就会使用当前类上指定的@CacheConfig(keyGenerator = "wiselyKeyGenerator")
    • @Cacheable#cacheManager = "pageResultCacheManager") 指定缓存管理器,这里就是上面一步针对返回不同的Object对象定制的缓存管理器。

到此缓存管理器完成,文章篇幅不少,其实没多少东西

个人公众号

0

评论区