简体   繁体   English

Spring 引导应用程序的 Hibernate 缓存(Infinispan,Hazelcast,理想可扩展)

[英]Hibernate Cache for Spring Boot Application (Infinispan, Hazelcast, ideally scalable)

I have a Spring Boot application which retrieves (and stores) data via Hibernate.我有一个 Spring 引导应用程序,它通过 Hibernate 检索(和存储)数据。 Hibernate is connected to a MySQL database. Hibernate 连接到 MySQL 数据库。 I optimised the database requests slightly with some entity graphs to join some tables before retrieving them from the database.我用一些实体图稍微优化了数据库请求,以便在从数据库中检索它们之前加入一些表。 Now, I want the most common objects (which are not changed too often) to be stored in a cache.现在,我希望将最常见的对象(不经常更改)存储在缓存中。

I tried caching via Infinispan and Hazelcast.我尝试通过 Infinispan 和 Hazelcast 进行缓存。 I changed configurations here and there but somehow the entities are ALWAYS retrieved from the database.我在这里和那里更改了配置,但以某种方式总是从数据库中检索实体。

For Halzelcast, I added the following three dependencies in my pom.xml .对于 Halzelcast,我在pom.xml中添加了以下三个依赖项。 Even though hazelcast-hibernate seems to be integrated in hazelcast-spring , I need the third dependency because otherwise the region factory would not be available.尽管hazelcast-hibernate似乎集成在hazelcast-spring中,但我需要第三个依赖项,否则区域工厂将不可用。

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
    <version>3.12.2</version>
</dependency>
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-spring</artifactId>
    <version>3.12.2</version>
</dependency>
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-hibernate53</artifactId>
    <version>1.3.2</version>
</dependency>

The following settings should be sufficient for using Hazelcast as a Hibernate cache.以下设置足以将 Hazelcast 用作 Hibernate 缓存。 But it does not work.但它不起作用。 I still see the same SQL queries in the logs like before.我仍然像以前一样在日志中看到相同的 SQL 查询。

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=com.hazelcast.hibernate.HazelcastLocalCacheRegionFactory

The HazelcastLocalCacheRegionFactory should be used because the performance should be slightly better and ram is (currently) not such a huge issue.应该使用HazelcastLocalCacheRegionFactory ,因为性能应该稍微好一些,并且 ram(当前)不是一个大问题。 Nevertheless, I also welcome different approaches.尽管如此,我也欢迎不同的方法。

The relevant entities get both annotations ( @Cacheable and @Cache ).相关实体获得两个注释( @Cacheable@Cache )。

@Entity(name = "business_units")
@Getter @Setter
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class BusinessUnit extends Auditable<String> implements Serializable {

    private static final long serialVersionUID = -6994142588281279518L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name = "native", strategy = "native")
    @Column(nullable = false, name = "id")
    private Integer id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "allocated_responsibility_id", referencedColumnName = "id")
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private ProductManager allocatedResponsibility;

    // Some other attributes, getters and setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BusinessUnit)) return false;
        BusinessUnit that = (BusinessUnit) o;
        return id != null && id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return 31;
    }
}

The same was also tried with Infinispan (different dependencies but same non-existing result). Infinispan 也进行了同样的尝试(不同的依赖项但相同的不存在结果)。

I intend to use an external provider because the app should be scalable.我打算使用外部提供商,因为该应用程序应该是可扩展的。 Without paying attention to this, the cache would not mirror the database correctly anymore.如果不注意这一点,缓存将不再正确地镜像数据库。

An embedded cache should be used because an extra server for Hazelcast or Infinispan would be too complicated to maintain.应该使用嵌入式缓存,因为 Hazelcast 或 Infinispan 的额外服务器太复杂而无法维护。

I have no idea why nothing changes (okay, i see that Hazelcast starts).我不知道为什么没有任何变化(好吧,我看到 Hazelcast 开始了)。 Another idea I had is using a query cache instead of a Hibernate cache.我的另一个想法是使用查询缓存而不是 Hibernate 缓存。 But this would require more attention to ensure synchronous database and cache.但这需要更多的注意来确保数据库和缓存的同步。 Furthermore, the app could benefit less from this.此外,该应用程序可能从中受益较少。

Could you please tell me why it does not work and what to change that it works?您能否告诉我为什么它不起作用以及如何改变它起作用?

Update: Add Statistics更新:添加统计信息

I tried below suggestions and added the statistics like @Nicolas suggested.我尝试了以下建议并添加了@Nicolas 建议的统计信息。

spring.jpa.properties.hibernate.generate_statistics=true

When I first load such a page, the data gets requested from the database and put into the cache (L2C).当我第一次加载这样的页面时,从数据库中请求数据并放入缓存(L2C)。

1519200 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
1759800 nanoseconds spent preparing 1 JDBC statements;
4144000 nanoseconds spent executing 1 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
161900 nanoseconds spent performing 3 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)

But when I access the site again (or perform any other action which should hit the cache), the data gets requested from the database again (and put into the cache).但是当我再次访问该站点(或执行任何其他应该命中缓存的操作)时,数据会再次从数据库中请求(并放入缓存中)。

The logs when accessing the page again:再次访问页面时的日志:

2019-10-08 12:51:38.399  INFO 17028 --- [nio-8080-exec-8] i.StatisticalLoggingSessionEventListener : Session Metrics {
    0 nanoseconds spent acquiring 0 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    0 nanoseconds spent preparing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
2019-10-08 12:51:38.404 DEBUG 17028 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        businessun0_.id as id1_0_0_,
        productman1_.id as id2_9_1_,
        employee2_.id as id1_4_2_,
        businessun0_.created_by as created_2_0_0_,
        businessun0_.created_date as created_3_0_0_,
        businessun0_.last_modified_by as last_mod4_0_0_,
        businessun0_.last_modified_date_time as last_mod5_0_0_,
        businessun0_.allocated_responsibility_id as allocate7_0_0_,
        businessun0_.name as name6_0_0_,
        productman1_.employee_id as employee3_9_1_,
        employee2_.created_by as created_2_4_2_,
        employee2_.created_date as created_3_4_2_,
        employee2_.last_modified_by as last_mod4_4_2_,
        employee2_.last_modified_date_time as last_mod5_4_2_,
        employee2_.email_address as email_ad6_4_2_,
        employee2_.location as location7_4_2_,
        employee2_.name as name8_4_2_,
        employee2_.team as team9_4_2_ 
    from
        business_units businessun0_ 
    left outer join
        responsibilities productman1_ 
            on businessun0_.allocated_responsibility_id=productman1_.id 
    left outer join
        employees employee2_ 
            on productman1_.employee_id=employee2_.id
2019-10-08 12:51:38.412  INFO 17028 --- [nio-8080-exec-8] i.StatisticalLoggingSessionEventListener : Session Metrics {
    901000 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    1075000 nanoseconds spent preparing 1 JDBC statements;
    1766400 nanoseconds spent executing 1 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    166800 nanoseconds spent performing 3 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

I think the SQL will always be output by Hibernate, regardless whether the cache activates or not.我认为 SQL 将永远是 output 由 Hibernate ,无论缓存是否激活。 If you want to make sure that your cache is used, please activate Hibernate statistics:如果你想确保你的缓存被使用,请激活 Hibernate 统计:

spring.jpa.properties.hibernate.generate_statistics=true

On the firs request, it should output something like that:在第一个请求中,它应该 output 是这样的:

Session Metrics {
    388816 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    2436908 nanoseconds spent preparing 1 JDBC statements;
    2585533 nanoseconds spent executing 1 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    10276363 nanoseconds spent performing 1 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    54012289 nanoseconds spent performing 1 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

And on later requests, something like:在以后的请求中,类似:

Session Metrics {
    79940 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    0 nanoseconds spent preparing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    1665675 nanoseconds spent performing 1 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

Note the cache hit on the second request.注意第二个请求的缓存命中。

If you see a Hazelcast instance has started there should not be a problem on the caching provider side and L2C must be ready for service.如果您看到 Hazelcast 实例已启动,则缓存提供者端应该没有问题,并且 L2C 必须准备好服务。 I think the problem is about your entity object.我认为问题在于您的实体 object。 An entity will not be kept in the second level cache unless you use @Cachable annotation for the entity class or define <cache... > in the entity_name.hbm.xml .除非您对实体 class 使用@Cachable注释或在entity_name.hbm.xml中定义<cache... > ,否则实体不会保留在二级缓存中。

Also,还,

... in this case, does Hibernate cache only the annotated entities? ...在这种情况下,Hibernate 是否仅缓存带注释的实体?

For the second level cache, yes.对于二级缓存,是的。

Do you have a recommendation where I should call the statistics?你有什么建议我应该在哪里调用统计数据?

If you enable the statistics, detailed session stats (seen on Nicolas' answer) will be printed out right after the session is closed.如果启用统计信息,则会在 session 关闭后立即打印出详细的 session 统计信息(见 Nicolas 的回答)。

I think the SQL will always be output by Hibernate, regardless whether the cache activates or not.我认为 SQL 将永远是 output 由 Hibernate ,无论缓存是否激活。

No, SQL queries should not show up on the log unless they hit the DB.不,SQL 查询不应显示在日志中,除非它们命中数据库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM