Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Caching and lazy loaded associations

Tags:

I got a typical entity association of order and items. To make it possible to read only orders, the items set is default FetchType.LAZY. 2nd level and query cache is enabled. To read an order with associated items, I'm using a JPQL query. The query and the entities are cached by EHCache. But on the second call when accessing items, a LazyInitializationException was thrown, because items are not initialized (not restored from cache). Why? What's the best way to implement this requirement?

Order:

@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>();
  ...
}

Item:

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

DAO:

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:

@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 and Hibernate 5.2.9.Final.

As you can see, I've tried to use Hibernate entity annotations and cache hints instead of JPA caching. I've also tried JPA entity graphs instead of JOIN FETCH. Always the same: Items are not initialized/restored on the second call of the order query.

like image 826
Titus Avatar asked Apr 18 '17 08:04

Titus


2 Answers

The LazyInitializationException is thrown because of the HHH-12430 issue.

However, there are some issues with your code as well, and there is a workaround that you can use until the Hibernate HHH-12430 issue is fixed.

When you're using Hibernate, it's not enough to annotate your entity with @Cacheable.

You need to provide a CacheConcurrencyStrategy as well:

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

The Query Cache only stores entity identifiers, and since you are only selecting the Order, the items association will not get cached as well.

What you can do is to change your query to this:

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

Then, activate the cache for collection:

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

And both the Order and the Item should use this:

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

since they need to be cacheable.

And make sure you initialize the items collection prior to returning the result set:

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

This way, the items will always be initialized and the collection will be fetched from the cache, not from the database.

like image 109
Vlad Mihalcea Avatar answered Sep 25 '22 09:09

Vlad Mihalcea


I think the @Cacheable is not Hibernate cache, but Spring cache. @Cache annotation is for Hibernate cache. Besides of this, when I have troubles with this stuff, to get the JOIN FETCH result also usable with cache, I had to add Hibernate.initialize(...) to the Dao method to avoid LazyInitializationException.

like image 41
dominik Avatar answered Sep 23 '22 09:09

dominik