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.
Related
happy new year:)
I have a Spring MVC project using Hibernate and DataJPA.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
As you can see, here is two fields with eager fetch. I want to make both a lazy. I need to user #NamedEntityGraph annotation asI made here:
#NamedQueries({
#NamedQuery(name = Restaurant.GET_BY_ID, query = "SELECT r FROM Restaurant r WHERE r.id=?1"),
})
#Entity
#NamedEntityGraph(name = Restaurant.GRAPH_WITH_MENU_HISTORY, attributeNodes = {#NamedAttributeNode("menuHistory")})
#Table(name = "restaurants")
public class Restaurant extends NamedEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "restaurant")
#OrderBy(value = "date DESC")
private List<Dish> menuHistory;
public static final String GRAPH_WITH_MENU_HISTORY = "Restaurant.withMenuHistory";
I want to know, if I'll write
#NamedEntityGraph(name = "G_NAME", attributeNodes = {#NamedAttributeNode("user", "restaurant")})
and if I'll request one of them, will the second load anyway or it will load only by request to him? May be, I need to user two graphs?
According to JPA 2.1 Spec 3.7.4:
The persistence provider is permitted to fetch additional entity state
beyond that specified by a fetch graph or load graph. It is required,
however, that the persistence provider fetch all state specified by
the fetch or load graph.
So actually the #NamedEntityGraph just guarantees what fields should be eagerly loaded, but not what fields should not be loaded.
So, if you make #NamedEntityGraph with user, your persistence provider (Hibernate for example) can load only user field or both user and restaurant fields eagerly. This is dependent on implementation and not guaranteed.
See this hibernate's issue.
But as far as I know, the Hibernate loads only simple fields in addition to specified in #NamedEntityGraph, and not loads lazy associations.
So if you use hibernate, it should work.
But of course you need two separate #NamedEntityGraphs for user and restaurant fields.
Or you can use ad-hoc spring-data-jpa's feature:
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
With this code you don't need explicitly declare #NamedEntityGraph anymore. You can just specify fields inline.
I'm having an issue where a Validation instance is added to a Collection on a Step instance.
Declaration is as follows:
Step class:
#Entity
#Table
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Step extends AbstractEntity implements ValidatableStep {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "step_id", nullable = false)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Validation> validations = new HashSet<>();
#Override
public void addValidation(Validation validation) {
// do some stuff
...
// add validation instance to collection
getValidations().add(validation);
}
}
Validation class:
#Entity
#Table
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Validation extends AbstractEntity {
//some properties
}
Both classes are Cacheable with a READ_WRITE strategy applied. The unidirectional Collection of Validations are also cached with same strategy.
One would expect when a read-write transaction that invokes addValidation(new Validation('userName')); commits, the new Validation would be visible in a subsequent read-only transaction. The weird thing is that sometimes it does work and sometimes it doesn't work...
The first transaction always succeeds; we see the new validation being persisted in database and Step's version property (for optimistic locking puposes) getting incremented. But sometimes, the 2nd read transaction contains a Step instance with an empty Validation Collection...
Our Hibernate caching config is as follows:
hibernate.cache.use_second_level_cache = true
hibernate.cache.use_query_cache = true
hibernate.cache.region.factory_class = org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
hibernate.cache.provider_configuration_file_resource_path = classpath:ehcache.xml
net.sf.ehcache.hibernate.cache_lock_timeout = 10000
Any idea what's causing this weird (and random) behavior?
The Hibernate Collection Cache always invalidates existing entries and both the Entity and the Collection caches are sharing the same AbstractReadWriteEhcacheAccessStrategy, so a soft-lock is acquired when updating data.
Because you are using a unidirectional one-to-many association, you will end up with a Validation table and a Step_validation link table too. Whenever you add/remove a Validation you have to hit two tables and that's less efficient.
I suggest you adding the #ManyToOne side in the Validation entity and turn the #OneToMany side into a mapped-by collection:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "step")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Validation> validations = new HashSet<>();
I have read and reread everything here on SO and many other sites but can't seem to figure out why my updated objects are not updating.
The basic overview of what I'm doing:
Service layer asks DAO for some People
Return an ArrayList of People from the DB (DAO / #Repository)
Service Layer manipulates object and adds them to a new arraylist
Service Layers passes new list back to DAO to update
NOTHING GETS Updated
If I throw a log message in my object has the new values, the children are correctly hydrated. I get no errors in the code is just doesn't commit my changes.
Here is some code:
PersonDAO annotated as #Repository
public void updatePeople(List<Person> people) {
log.info("Updating " + people.size() + " people");
try {
Transaction tx = getCurrentSession().beginTransaction();
for (Person person : people){
getCurrentSession().saveOrUpdate(person);
}
getCurrentSession().flush();
tx.commit();
getCurrentSession().close();
} catch (Exception e){
log.error("Exception Updating all people " + e.toString());
e.printStackTrace();
}
}
public List<Person> getAssociatesByStoreId(String storeId) {
try {
List<Person> usersInStore = (List<Person>) getCurrentSession()
.createCriteria(Person.class).createCriteria("store")
.add(Restrictions.eq("storeId", storeId)).list();
return usersInStore;
} catch (Exception e) {
log.error("exception in getAssociatesByStoreId ", e);
}
return null;
}
PersonService annotated as #Service - relevant method
/* I put the people into a map to do some other logic on them */
for (Person person : personDAO.getAllPeople()){
personMap.put(person.getEmployeeId() + "-" + person.getStore().getStoreId(), person);
}
/*
I iterate a list creating new Person objects (based on some random stuff),
including saving any children entities (managementcodes and stores) that need to be created
After I have created a new Person I attempt to find it in the map from above.
If I find it pull it out of the map and put it into an array list
that is eventually passed back into the DAO
*/
Person annotated as #Entity
private int personid;
private String firstName;
private String lastName;
private Store store;
private ManagementCode managementCode;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "personid", unique = true, nullable = false)
public int getPersonid() {
return this.personid;
}
/*a bunch of getters and setters*/
#ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#org.hibernate.annotations.Cascade( {org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.PERSIST})
#LazyCollection(LazyCollectionOption.EXTRA)
#JoinColumn(name = "managementlevel", nullable = true)
public ManagementCode getManagementCode() {
return this.managementCode;
}
#ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
// #org.hibernate.annotations.Cascade( {org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.PERSIST})
#JoinColumn(name = "storeid", nullable = false)
public Store getStore() {
return this.store;
}
Store annotated as entity (Managementcode is the same)
/*fields + getters and setter */
#OneToMany(fetch = FetchType.LAZY, mappedBy = "store", cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#LazyCollection(LazyCollectionOption.EXTRA)
#JsonIgnore
public Set<Person> getPersons() {
return this.persons;
}
EDIT I added both sets of Cascade type annotations above and still no luck
EDIT 2 Updated the to show the primary key definition
Someone please help me before I kick a puppy.
Thanks
There are three main types of entity objects with JPA:
Managed. These are the objects that are cached by JPA-provider, so the JPA-provider keeps track of their states, and commits any changes made to them when the transaction commits. These are the objects you get when you query. They have a database id.
New. These are entity objects that are not yet persisted in the database. The JPA-provider knows nothing about them. They do not have a database id.
Detached. These are objects that used to be managed, but no longer are. Typically, objects that are returned to a method that is outside the transaction. These object have a database id, but they are not part of the cache, so the JPA-provider does not know them.
In order to persist a relationship between an entity and another, the referenced entity has to managed. There are multiple ways of achieving this.
Cascade. Cascade from entity A to entity B means that when a cascading operation is performed on entity A by you in your code, the JPA-provider will perform the same operation on entity B. If you add CascadeType.MERGE to the relationship, and have entity A reference a detached entity B, then doing a merge() on entity A will first to a merge() on entity B, making it managed. When entity A is then stored, it has a reference to a managed B, and the relationship is persisted. (If you also want to persist references to objects that are not already in the database, add CascadeType.PERSIST as well.)
Making sure you only reference managed objects. Inside your transaction, load the referenced objects (the managed ones) and replace the references with managed objects, before storing.
Widen your transaction. That way, the referenced objects will never become detached.
It could be the case that you need to have "cascade = {CascadeType.MERGE, CascadeType.PERSIST}" on the ManyToOne annotation. The default behavior does not cascade anything to the associated objects.
/**
* (Optional) The operations that must be cascaded to
* the target of the association.
*
* <p> By default no operations are cascaded.
*/
CascadeType[] cascade() default {};
Eg.
#ManyToOne(optional = true, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = EAGER)
UPDATE:
In case you are using spring managed transaction manager and have the liberty to use annotation driven transaction demarcation, you can try the spring #Transactional annotation instead of manually starting transaction and committing/flushing.
I have following setup:
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="persistenceUnitName" value="persistenceUnit"/>
<property name="dataSource" ref="local-dataSource"/>
<property name="packagesToScan" value="X"/>
<property name="persistenceXmlLocation" value="classpath:persistence.xml"/>
</bean>
DAO class:
#Repository
public class EventRepository {
#PersistenceContext
EntityManager entityManager;
#Transactional
public void persist(Event event) {
entityManager.persist(event);
}
}
First of all, make sure that you use the fitting CascadeType on those ManyToOne annotations.
Secondly, a merge Operation in the entityManager/session does not remain object equality by reference. That means, in an update case where a merge is performed, check if a different object is returned. You are supposed to always use the return values of save operations.
Given the following entity one-to-many model:
One Repository can be linked to many AuditRecords.
Many AuditRecords can all link to the same Repository
#Entity
class AuditRecordEntity {
private AuditRepositoryEntity auditRepository;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = AUDIT_REPOSITORY_DB_COLUMN_NAME, nullable = false, updatable = false)
public AuditRepositoryEntity getAuditRepository() {
return auditRepository;
}
...
}
#Entity
class AuditRepositoryEntity {
private List<AuditRecordEntity> auditRecords = new ArrayList<AuditRecordEntity>();
#OneToMany(mappedBy = "auditRepository")
public List<AuditRecordEntity> getAuditRecords() {
return auditRecords;
}
...
}
I have the following HQL query to get the latest (by accessTime) AuditRecord for each distinct Repository:
select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)
I would like to know the equivalent SQL for the above HQL?
(the reason for this is I'l like to add the query as an sql restriction using the criteria API, as I am having trouble translating the HQL above to use the criteria API - see Hibernate criteria implementation for this entity model (subquery, self-join))
There is one hibernate property named
hibernate.show_sql
You can set it true in your hibernate configuration file or property file. It will show you the equivalent sql query of your hql/criteria query.
If I have this method in object class:
#OneToMany( fetch = FetchType.EAGER,
cascade = { CascadeType.ALL },
mappedBy = "object" )
#org.hibernate.annotations.Cascade(
{org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#Column( nullable = false )
public Set<ObjectEntry> getObjectEntries() {
return this.objectEntries;
}
and I put #cache both on ObjectEntry and on Object
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Object extends HashCodeValidator {
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class ObjectEntry extends HashCodeValidator
Do I still need to put #cache on getObjectEntries like this:
#org.hibernate.annotations.Cascade(
{org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#Column( nullable = false )
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public Set<ObjectEntry> getObjectEntries() {
return this.objectEntries;
}
Do I need to define cache for each query if I specifically add
hibernate.cache.use_query_cache = true
?
(...) Do I still need to put #cache on getObjectEntries like this:
Yes, you still have to cache a collection if you want to (that will be cached in a specific cache region).
Do I need to define cache for each query if I specifically add hibernate.cache.use_query_cache = true
From the reference documentation about the hibernate.cache.use_query_cache property (section 3.4. Optional configuration properties):
Enables the query cache. Individual queries still have to be set cachable. e.g. true|false
So, yes, you still have to set a query cachable (by calling setCacheable(true) on a query or criteria object) if you want to - which is IMO a good thing.