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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With