【问题标题】:Hibernate Caching and lazy loaded associations休眠缓存和延迟加载关联
【发布时间】:2026-01-06 05:25:01
【问题描述】:

我得到了一个典型的订单和项目实体关联。为了使只读订单成为可能,项目集默认为 FetchType.LAZY。二级和查询缓存已启用。要读取包含相关商品的订单,我使用的是 JPQL 查询。查询和实体由 EHCache 缓存。但是在访问项目的第二次调用中,抛出了 LazyInitializationException,因为项目没有被初始化(没有从缓存中恢复)。为什么?实现此要求的最佳方式是什么?

订单:

@Entity
@Cacheable
@NamedQueries({
  @NamedQuery(name = Order.NQ_FIND_BY_ID_FETCH_ITEMS, query = "SELECT DISTINCT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
})
@Table(...)
public class Order extends ... {
  ...
  @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
  // @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
  private Set<Item> items = new HashSet<Item>();
  ...
}

项目:

@Entity
@Cacheable
@Table(...)
public class Item extends ... {
  @ManyToOne
  @JoinColumn(name = "order_id", nullable = false)
  private Order order;
  ...
}

道:

public class OrderDaoJpaImpl extends ... {

  @Override
  public Catalog findByIdFetchItems(Long id) {
    TypedQuery<Order> query = entityManager.createNamedQuery(Order.NQ_FIND_BY_ID_FETCH_ITEMS, Order.class);
    query.setParameter("id", id);
    // query.setHint(QueryHints.HINT_CACHEABLE, Boolean.TRUE);
    Order order = JPAUtil.singleResultOrNull(query);
    return order;
}

服务:

@Service("orderService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class OrderServiceImpl implements OrderService {

  @Override
  public Order getOrderWithItems(Long orderId) {
    return orderDao.findByIdFetchItems(orderId);
  }
}

persistence.xml:

<persistence ...>
  <persistence-unit name="shop-persistence" transaction-type="RESOURCE_LOCAL">
    <jar-file>shop-persistence.jar</jar-file>
    <!-- Enable JPA 2 second level cache -->
    <shared-cache-mode>ALL</shared-cache-mode>
    <properties>
      ...
      <property name="hibernate.cache.use_second_level_cache" value="true" />
      <property name="hibernate.cache.use_query_cache" value="true" />
      <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
    </properties>
  </persistence-unit>
</persistence>

Spring Framework 4.3.7.RELEASE 和 Hibernate 5.2.9.Final。

如您所见,我尝试使用 Hibernate 实体注释和缓存提示而不是 JPA 缓存。我也尝试过 JPA 实体图而不是 JOIN FETCH。始终相同:在订单查询的第二次调用中未初始化/恢复项目。

【问题讨论】:

  • 我是唯一一个使用 Hibernate/JPA 缓存和 On-To-Many 关系的人吗?

标签: java hibernate jpa caching lazy-initialization


【解决方案1】:

LazyInitializationExceptionHHH-12430 问题而被抛出。

但是,您的代码也存在一些问题,在修复 Hibernate HHH-12430 问题之前,您可以使用一种解决方法。

当您使用 Hibernate 时,仅使用 @Cacheable 注释您的实体是不够的。

您还需要提供CacheConcurrencyStrategy

@Entity
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)

查询缓存only stores entity identifiers,由于您只选择Orderitems 关联也不会被缓存。

您可以做的是将您的查询更改为:

@NamedQuery(name = Order.NQ_FIND_BY_ID_FETCH_ITEMS, query = "SELECT DISTINCT o FROM Order o WHERE o.id = :id")

然后,激活缓存进行收集:

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Item> items = new HashSet<Item>();

OrderItem 都应该使用这个:

@Entity
@org.hibernate.annotations.Cache(usage = 
    CacheConcurrencyStrategy.READ_WRITE)

因为它们需要可缓存。

并确保在返回结果集之前初始化 items 集合:

Order order = JPAUtil.singleResultOrNull(query);
if(order != null) {
    order.getItems().size();
}
return order;

这样,items 将始终被初始化,并且集合将从缓存中获取,而不是从数据库中获取。

【讨论】:

  • 是否可以将 dto 与 jpa 投影查询一起使用,而不是 order.getItems().size() ?
  • 我不明白这个问题。如果您使用投影查询,结果将按原样缓存,但您将获得表格 DTO 表示,而不是实体。但这不是最初的问题想要达到的目标。
【解决方案2】:

我认为@Cacheable 不是Hibernate 缓存,而是Spring 缓存。 @Cache 注解用于 Hibernate 缓存。 除此之外,当我遇到问题时,为了让 JOIN FETCH 结果也可用于缓存,我必须将 Hibernate.initialize(...) 添加到 Dao 方法以避免 LazyInitializationException。

【讨论】: