何时以及如何使用Hibernate二级缓存?

95

我有困难理解hibernate何时使用二级缓存以及何时使缓存失效。

这是我目前的理解:

  • 二级缓存在会话之间存储实体,其作用域是SessionFactory
  • 您必须告诉Hibernate要缓存哪些实体,默认情况下不会缓存任何实体
  • 查询缓存将查询结果存储在缓存中。

我不明白的是

  • Hibernate何时使用这个缓存?
  • 假设我设置了二级缓存但没有设置查询缓存。 我想缓存我的客户,有50000个客户。 有哪些方法可以从缓存中检索客户?
  • 我假设可以通过id从缓存中获取它们。这很容易,但也不值得缓存。但是,如果我想对所有客户进行一些计算怎么办。 假设我想显示客户列表,那么如何访问它们?
  • 如果禁用查询缓存,如何获取所有客户?
  • 如果有人更新了其中一个客户会发生什么?
    • 该客户会在缓存中失效还是所有客户都会失效?

或者我完全错误地思考缓存?在这种情况下,二级缓存的更合适的用途是什么?Hibernate文档并没有很清楚地说明缓存的实际工作方式。只有关于如何设置它的说明。

更新: 因此,我已经了解到如果禁用查询缓存,则二级缓存(不含查询缓存)对于按ID加载数据非常有用。例如,我有要在Web应用程序的每个请求中检查权限的用户对象。将用户缓存在二级缓存中以减少数据库访问是否是一个好的案例?就像我会将用户id存储在会话或其他地方,当需要检查权限时,我会通过id加载用户并检查权限。


可能是什么是Hibernate中的二级缓存?的重复问题。 - Don Roby
5个回答

110
首先,让我们谈一谈进程级缓存(或者在Hibernate中称为第二级缓存)。要使其工作,您应该:
  1. 配置缓存提供程序
  2. 告诉Hibernate要缓存哪些实体(如果使用此类映射,则可以在hbm.xml文件中直接进行设置)。
您告诉缓存提供程序它应该存储多少对象以及何时/为什么它们应该失效。因此,假设您有一个Book和一个Author实体,每次从数据库获取它们时,只会从实际的数据库中选择未在缓存中的实体。这极大地提高了性能。以下情况下很有用:
  • 您仅通过Hibernate向数据库写入数据(因为它需要一种方法来知道何时更改或使缓存中的实体无效)
  • 您经常读取对象
  • 您只有一个节点,并且没有复制。否则,您需要复制缓存本身(使用JGroups等分布式缓存),这增加了更多的复杂性,并且不像共享-nothing应用程序那样好扩展。
所以缓存什么时候起作用?
  • 当您使用session.get()session.load()检索先前已选择并保存在缓存中的对象时。缓存是一个存储器,其中ID是键,属性是值。因此,只有在可以通过ID搜索时才能消除对数据库的访问。
  • 当您的关联是延迟加载时(或者使用选择而不是连接进行急切加载)
但是,在以下情况下它无法工作:
  • 如果您不通过ID选择。再次说明-第二级缓存存储了实体ID到其他属性的映射(它实际上并未存储对象,而是数据本身),因此,如果您的查找看起来像这样:from Authors where name = :name,则不会命中缓存。
  • 当您使用HQL时(即使使用where id=?也是如此)。
  • 如果您在映射中设置了fetch="join",这意味着加载关联时将使用连接而不是单独的选择语句。只有在使用fetch="select"时,处理级别缓存才适用于子对象。
  • 即使您使用fetch="select",但在HQL中使用连接来选择关联 - 这些连接将立即发出,并覆盖您在hbm.xml或注释中指定的任何内容。
  • 现在,关于查询缓存。您应该注意它不是一个单独的缓存,而是过程级别缓存的补充。假设您有一个Country实体。它是静态的,因此每次您说from Country时将产生相同的结果集。这是查询缓存的完美候选人,它将在自身中存储一组ID,并且下一次选择所有国家时,它将将此列表返回到过程级别缓存中,后者将为每个ID返回对象,因为这些对象已经存储在第二级缓存中。 每当与实体相关的任何内容更改时,查询缓存都会失效。因此,假设您配置了from Authors以放置到查询缓存中。由于作者经常更改,因此将无效。因此,您应仅将查询缓存用于相对静态的数据。


    查询 "from Author a fetch join a.books" 是否需要使用查询缓存从缓存中获取作者? - palto
    1
    不,查询缓存仅适用于静态数据,并且仅存储ID。作者将从第二级缓存中获取。 - Stanislav Bashkyrtsev
    @ctapobep:你说的不正确!如果实体Author的books字段被注释(fetch EAGER),那么“from Author a fetch join a.books”就可以正常工作...我想现在为时已晚。 - Bilal BBB
    这样的好答案!我会一直记得!:d - Mohammadreza Khatami
    启用“查询缓存”后,如果您选择的属性不是id,则它是否从缓存中获取数据? - Arun Raaj

    44
    • 第二级缓存是一个键值存储库。只有通过id获取实体时才会起作用。
    • 当使用Hibernate更新/删除实体时,第二级缓存将按实体进行失效/更新。如果以不同的方式更新数据库,则不会使其失效。
    • 对于查询(例如客户列表),请使用查询缓存。

    实际上,拥有一个键值分布式缓存是很有用的 - 这就是Memcached的功能,它为Facebook、Twitter等提供支持。但是,如果您没有按id查找,则它将不会非常有用。


    查询缓存(与DTO模式的投影、结果转换器一起使用),如果您更新了用于缓存查询的实体,则会使其失效。正如您所说,第二级缓存仅适用于按ID获取实体的查询(不适用于条件限制)或具有投影的Criteria(仅选择某些属性)。顺便说一句,“Hibernate缓存如何工作”的最佳答案是...... - ics_mauricio

    14

    虽然晚了一步,但我想系统地回答这些许多开发者的问题。

    逐一回答您的问题:

    Q. Hibernate 何时会命中缓存?

    A. 一级缓存 关联于 Session 对象。而 二级缓存 则关联于 Session Factory 对象。如果对象不在第一个缓存中,则检查第二个级别。

    Q. 假设我已设置了第二级缓存但没有查询缓存。我想缓存我的客户,有50000个客户。有哪些方法可以从缓存中检索客户?

    A. 您已经在更新中得到了答案。查询缓存仅存储对象的 ID 列表,并且这些对象与其 ID 相关联的缓存都存在于 同一 二级缓存中。因此,如果启用了查询缓存,则将利用相同的资源。很整洁,对吧?

    Q. 我假设可以通过 id 从缓存中获取它们。那很容易,但也不值得缓存。但是,如果我想对所有客户进行一些计算。例如,我想显示客户列表,那么我该如何访问它们?

    A. 上面已经回答了。

    Q. 如果禁用查询缓存,我怎么能获取所有我的客户?

    A. 上面已经回答了。

    Q. 如果有人更新其中一个客户会发生什么?该客户是否将在缓存中失效,还是所有客户都将失效?

    A. Hibernate并不知道,但您可以使用其他第三方IMDG /分布式缓存来实现作为Hibernate二级缓存并使其失效。例如,TayzGrid就是其中之一,还有更多。


    0

    我的工作二级缓存代码。

    hibernate.cfg.xml

    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
            <property name="connection.url">jdbc:mysql://localhost:3306/practice</property>
            <property name="connection.user">root</property>
            <property name="connection.password">Welcome123#</property>
            <property name="show_sql">true</property>
            <property name="format_sql">true</property>
            <property name="hbm2ddl.auto">update</property>
    
            <property name="cache.use_second_level_cache">true</property>
            <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
            <property name="cache.ehcache.missing_cache_strategy">create</property>
    
            <mapping class="com.oracle.dto.BankAccount"/>
            <mapping class="com.oracle.dto.Citizen"/>
    
    
    
        </session-factory>
    </hibernate-configuration>
    

    package com.oracle.dto;
    
    import lombok.AccessLevel;
    import lombok.Data;
    import lombok.Setter;
    import org.hibernate.annotations.CacheConcurrencyStrategy;
    
    import javax.persistence.*;
    import java.util.Date;
    
    @Entity
    @Data
    @Cacheable
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public class BankAccount {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO,generator = "bid")
        @SequenceGenerator(name = "bid",sequenceName = "b_id",initialValue = 2000,allocationSize = 1)
        @Setter(AccessLevel.NONE)
        @Column(name="bank_id",updatable = false,nullable = false)
        private Long bId;
        private Integer branchCode;
        private Double accountBalance;
        @Temporal(TemporalType.DATE)
        @Column(name="account_opening_date",updatable = false,nullable = false)
        private Date accountOpeningDate;
    }
    
    

    主类

    package com.oracle.main;
    
    import com.oracle.dto.BankAccount;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.cfg.Configuration;
    
    public class CacheDriver {
        public static void main(String[] args) {
            SessionFactory sf=new Configuration().configure().buildSessionFactory();
            Session session1 = sf.openSession();
            BankAccount account1 = session1.get(BankAccount.class,2000l);
            System.out.println(account1);
            session1.close();
            Session session2 = sf.openSession();
            BankAccount account2 = session2.get(BankAccount.class,2000l);
            System.out.println(account2);
            session1.close();
            sf.close();
        }
    }
    
    

    0

    Hibernate的二级缓存有点难以理解和实现。根据您的问题,我们可以说:

    Hibernate何时使用此缓存?

    正如您所建议的那样,只有在启用Hibernate L2缓存(默认情况下未启用)后,才会查询L1缓存之后的缓存。这是一个键值缓存,其数据在多个会话中保留。

    假设我已经设置了第二级缓存但没有查询缓存。我想缓存我的客户,有50000个客户。有哪些方法可以从缓存中检索客户?

    对于这种情况,查询缓存是最好的选择,因为客户数据是静态的,并且从关系数据库中检索。

    如果有人更新其中一个客户会发生什么?该客户会在缓存中失效还是所有客户都会失效?

    这取决于您正在使用的特定Hibernate缓存策略。Hibernate实际上有四种不同的缓存策略:

    READ_ONLY:对象一旦进入缓存就不会更改。

    NONSTRICT_READ_WRITE:对象在相应的数据库条目更新后会更改(最终保证一致性)。

    READ_WRITE:在相应的数据库条目更新后,对象会立即更改;这通过使用“软”锁来保证强一致性。

    TRANSACTIONAL:使用分布式XA事务更改对象,确保数据完整性;这保证了全部成功或回滚所有更改。然而,在这四种情况下,更新单个数据库条目不会使缓存中的所有客户列表无效。Hibernate比那要聪明一些 :)

    要了解Hibernate中L2缓存的工作原理,请查看文章“What is the Hibernate L2 cache”,或深入阅读文章 Caching in Hibernate with Redis


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接