避免n + 1选择与缓存Hibernate关联或缓存集合作为一个整体

时间:2010-09-24 01:46:58

标签: hibernate collections caching

我有一对多关系:包含n个子记录的父记录。这些记录经常使用和只读,是缓存的理想选择。

这是我的Hibernate映射的近似值:

`<class name="Parent" table="Parent>
   <cache usage="read-only"/>
   <id name="primary_key"/>
   <property name="natural_key"/>

   <set name="children" lazy="false" fetch="join">
      <cache usage="read-only"/>
      <key-column name="parent_id"/>
      <one-to-many class="Child"/>
   </set>
</class>

<class name="Child" table="Child">
   <cache usage="read-only"/>
   <id name="primary_key"/>
   <property name="parent_id"/>
</class>`

我经常通过自然键而不是主键来获取Parent,所以我需要启用查询缓存才能利用二级缓存(我使用ehcache)。

这是问题:当我获取父级并在查询缓存中获得命中时,它变为“按主要提取”查询。对于我的一对多的“一”结尾来说这很好。如果在缓存中找不到Parent,则从DB中获取它。如果在缓存中找不到我的n个子记录,Hibernate会使用n个后续选择查询来获取它们。 N + 1选择问题。

我想要的是是一种缓存由parent_id键入的Child对象集合的方法。我希望Hibernate在整个缓存中查找我的集合,而不是作为一堆单独的记录。如果找不到集合,我希望Hibernate使用1 select语句获取集合 - 使用parent_id = x获取所有Child。

从Hibernate + ehcache那里问这个太多了吗?

1 个答案:

答案 0 :(得分:5)

我找到了自己的答案 - 可以配置Hibernate + ehcache来执行我上面描述的操作。

通过将我的Child声明为值类型而不是实体类型(我相信这些是Hibernate社区使用的术语),我基本上可以将我的Child视为Parent的一个组件,而不是一个单独的实体。这是我修改后的映射的一个例子:

<class name="Parent" table="Parent">
   <cache usage="read-only"/>
   <id name="primary_key"/>
   <property name="natural_key"/>

   <set name="children" lazy="false" fetch="join" table="Child">
      <cache usage="read-only"/>
      <key-column name="parent_id"/>
      <composite-element class="Child">
         <property name="property1" column="PROP1" type="string">
         <property name="property2" column="PROP2" type="string">
      </composite-element>
   </set>
</class>

我的Child对象的行为在此配置下与以前略有不同 - 现在没有为Child定义单独的主键,没有共享引用,也没有可空字段/列。有关详细信息,请参阅Hibernate docs

我的父母和孩子都是只读的,我真的只想通过父母访问Child的实例 - 我不使用Child独立于Parent,因此值类型处理非常适合我的用例

对我而言,最大的胜利是如何在我的新配置下缓存集合。集合缓存现在将我的集合整体缓存,由parent_id键入。我的集合中的某些(但不是全部)不再可能位于缓存中。该集合被缓存并作为一个整体逐出。更重要的是,如果Hibernate在二级缓存中寻找我的集合并得到一个未命中,它会通过一个选择查询从数据库中提取整个集合。

这是我的ehcache配置:

 <ehcache>
    <cache name="query.Parent"
        maxElementsInMemory="10"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="43200" 
    </cache>
    <cache name="Parent"
        maxElementsInMemory="10"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="43200" 
    </cache>
    <cache name="Parent.children"
        maxElementsInMemory="10"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="43200" 
    </cache>
<ehcache>

希望这个例子可以帮助别人。