spring data rest 缓存 /spring boot Jpa 二级缓存 ehcache 与 redis

本贴最后更新于 1839 天前,其中的信息可能已经东海扬尘

这两天都在为学校的项目做准备,期间也遇到了很多问题,比如自己写代码生成器,比如授权这些,发现自己还是有点太弱了。但是最为棘手的莫过于缓存的问题。主要原因还是自己太想用 spring data rest 了,这个在假期就没有解决缓存的问题,现在还是要来再次面对,不过相隔几个月,今非昔比。

问题分析

项目直接使用 spring data rest 进行资源暴露,repository 完全接手,所以我舍去了 service 层,controller 也基本没有方法,就算有也被抽象出来了。那么没有 service 如何做缓存呢?那就是用他 Jpa 底层实现的 hibernate 了,一级缓存我们是默认开启的,那么我们就要使用 二级缓存 来提高性能。

使用 ehcache

第一种方式就是使用非常快速的 ehcache 来提高性能,配合 hibernate-jcache 使用,就可以达到很好的效果,具体如下:

1、 引入依赖,没有加上版本号是因为 spring boot 的版本管理会自动下载合适的版本。

        <!--ehcache-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jcache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>

2、 添加 ehcache.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<eh:config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns:eh='http://www.ehcache.org/v3'
        xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.3.xsd">
    <eh:persistence directory="${java.io.tmpdir}/lesson-cloud-cache-data"/>

    <eh:cache-template name="default">
        <eh:expiry>
            <eh:ttl unit="seconds">600</eh:ttl>
        </eh:expiry>
        <eh:resources>
            <!--堆内内存可以放2000个条目,超出部分堆外100MB-->
            <eh:heap unit="entries">2000</eh:heap>
            <eh:offheap unit="MB">100</eh:offheap>
        </eh:resources>
    </eh:cache-template>
</eh:config>

3、 添加 hibernate.properties 配置文件

hibernate.format_sql=true
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region_prefix=gzmu_lesson_cloud_
hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
hibernate.cache.provider_configuration_file_resource_path=ehcache.xml
hibernate.cache.use_structured_entries=true
hibernate.generate_statistics=false
hibernate.javax.cache.missing_cache_strategy=create

如果你不喜欢使用 properties 文件,可以在 application.yml 里面配置

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        generate_statistics: true
        javax:
          cache:
            missing_cache_strategy: create
        cache:
          format_sql: true
          use_second_level_cache: true
          use_query_cache: true
          region:
            factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
          region_prefix: lesson_cloud_
          use_structured_entries: true
          provider_configuration_file_resource_path: ehcache.xml

4、 不需要在 service 层添加注解,而是在实体类添加注解,如下:

@Data
@Cacheable // 缓存
@Table(name = "sys_log")
@Entity(name = "sys_log")
@Where(clause = "is_enable = 1")
@EqualsAndHashCode(callSuper = true)
@Cache(region = "sys_log", usage = CacheConcurrencyStrategy.READ_WRITE ) // 缓存名字以及策略
public class SysLog extends BaseEntity {
	// ...
}

默认对查询的列表不缓存,一方面是因为命中率低,另一方面查询的列表会因为其中的一个改变就要销毁,操作频繁,没有缓存的必要。

使用 redis

这个是真的搞了好久,因为系统涉及到分布式,所以使用 ehcache 肯定是不太好的,所以要把它缓存 redis。一开始自己尝试了很多办法,自己尝试实现一个缓存机制(效率太低并且代码不忍直视)。所以就想到去找别人的,所以在 github 上一搜真的有,找到一个 hibernate-redis,但是一使用发现很多很多问题,然后自己一个一个的排除,最后遇到一个无法解决的问题

java.lang.IncompatibleClassChangeError: Expected static method org.hibernate.cache.internal.DefaultCacheKeysFactory.createEntityKey(Ljava/lang/Object;Lorg/hibernate/persister/entity/EntityPersister;Lorg/hibernate/engine/spi/SessionFactoryI

然后发现这个问题是个 bug,已经在未来的版本解决,见 issue, 由于没有上传到 maven 仓库,需要自己切换到 2.4 版本的分支自己构建自己打包,并且还要用本地 jar 导入的方式导入,实在太过麻烦,暂时放弃。

然后找资料,网上乱七八糟的一大堆,没一个有用的,重复的还非常多。最后又回到 hibernate-redis,然后查看的时候发现了他引用了 redisson 的依赖,就很好奇这个是什么,然后去 官网 看了下,发现他是一个 java 的 redis client,可以通过它来操作 redis,并且观望上给出的标语

Redis based cache implementations for Java like JCache API, Hibernate 2nd Level Cache, Spring Cache and application level caching.

他也可以作为 JCache API, Hibernate, Spring Cache 的二级缓存,那么是不是意味着我可以用它呢?于是去他的 github wiki 看了下,果然有!而且还有 spring cache 的!不过我们不用,因为我们不用那几个注解来进行缓存控制,而是通过 hibernate,所以我尝试一下,完美整合!

1、 导入依赖,不同的是,我们导入的不是 redisson 的依赖,而是 redisson-hibernate ,这里算是坑到我了,导入 redisson 没用,然后去 maven 中央仓库逛了一波才发现 redisson-hibernate,然后尝试了一波才发现居然可以。

<!-- 注意先导入你的 hibernate-core -->
<dependency>
    <groupId>org.redisson</groupId>
    <!-- 对于 Hibernate v4.x -->
    <artifactId>redisson-hibernate-4</artifactId>
    <!-- 对于 Hibernate v5.0.x - v5.1.x -->
    <artifactId>redisson-hibernate-5</artifactId>
    <!-- 对于 Hibernate v5.2.x -->
    <artifactId>redisson-hibernate-52</artifactId>
    <!-- 对于 Hibernate v5.3.x - v5.4.x -->
    <artifactId>redisson-hibernate-53</artifactId>
    <version>3.10.6</version>
</dependency>

2、 添加 hibernate.properties 如下

hibernate.format_sql=true
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region_prefix=gzmu_lesson_cloud_
hibernate.cache.region.factory_class=org.redisson.hibernate.RedissonRegionFactory
hibernate.cache.redisson.config=redisson.yaml
hibernate.cache.use_structured_entries=true
hibernate.generate_statistics=false
hibernate.javax.cache.missing_cache_strategy=create

3、 添加 redisson.yaml 如下

# 配置参见 https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95

# 单节点设置,其余模式待测试
singleServerConfig:
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  connectTimeout: 1000
  timeout: 1000
  retryAttempts: 1
  retryInterval: 1000
  reconnectionTimeout: 3000
  failedAttempts: 5
  password: null
  subscriptionsPerConnection: 5
  clientName: null
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 25
  connectionMinimumIdleSize: 5
  connectionPoolSize: 100
  database: 0
threads: 0
# Codec
codec: !<org.redisson.codec.SnappyCodec> {}
eventLoopGroup: null

4、 同样,实体类添加注解

@Data
@Cacheable // 缓存
@Table(name = "sys_log")
@Entity(name = "sys_log")
@Where(clause = "is_enable = 1")
@EqualsAndHashCode(callSuper = true)
@Cache(region = "sys_log", usage = CacheConcurrencyStrategy.READ_WRITE ) // 缓存名字以及策略
public class SysLog extends BaseEntity {
	// ...
}

5、 测试即可

不过在使用的时候发现 redisson-hibernate-53redisson-hibernate-52 少了一些包,其查看 jar 结构的时候会很奇怪

redisson-hibernate-52

redisson-hibernate-53

使用的时候优势会出问题有时候不会,一开始提示找不到类,试了很多次,后来莫名其妙又可以了。。。。

更细致化配置

使用 yaml 进行配置

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        cache:
          redisson:
            config: redisson.yaml
          region:
            factory_class: org.redisson.hibernate.RedissonRegionFactory
          region_prefix: gzmu_lesson_cloud_
          use_query_cache: true
          use_second_level_cache: true
          use_structured_entries: true
          provider_configuration_file_resource_path: classpath:conf/hibernate-redis.properties
        format_sql: true
        generate_statistics: false
        javax:
          cache:
            missing_cache_strategy: create

添加 hibernate-redis.properties

##########################################################2
#
# properities for hibernate-redis 可以在这里进行更加细致化的 redis 配置
#
##########################################################

# Redisson configuration file
redisson-config=classpath:conf/redisson.yaml

# Cache Expiry settings
# 'hibernate' is second cache prefix
# 'common', 'account' is actual region name
redis.expiryInSeconds.default=120
redis.expiryInSeconds.hibernate.common=0
redis.expiryInSeconds.hibernate.account=1200

redisson 配置同上

值得注意的是,redis 同样不会缓存查询的列表,理由同上。

总结

搞这个搞了一天多才成功,集群还没尝试,不过应该不难了,还是需要多多熟悉才行啊。

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • watler-peng

    ehcache 3 配置的过期时间无效怎么解决的