Hibernate 5 Lazy Loading Issue - java

I have a class of User that has a list (collection) of Projects:
#OneToMany(mappedBy = "user_owner_id")
private Collection<Project> project = new ArrayList<>();
And at some point, I want to see the list of projects and I fetch them:
Session sessionF = sessionFactory.openSession();
sessionF.beginTransaction();
user = sessionF.get(User.class, user.getId());
sessionF.getTransaction().commit();
List<Project> projects = (List<Project>) user.getProject();
sessionF.close();
If I don't do something with projects it throws the error: org.hibernate.LazyInitializationException: could not initialize proxy – no Session
But if I am adding a int projectCount = projects.size(); it works just fine. Why does that happen and how can I resolve it without playing with projects here?
P.S. After I am closing the session I am just passing it through the HttpServletRequest and then going with a for loop over it in a jsp file.

Check Fetching Strategies section of Hibernate Reference Documentation
Lazy collection fetching - a collection is fetched when the application invokes an operation upon that collection. (This is the default for collections.)
Returning your collection from an entity and assigning it to a variable does not involve calling a method on that collection.

Related

HibernateException when updating a collection configured with delete orphan : can't save the parent object

I work on a Java project and I have to write a new module in order to copy some data from one database to another (same tables).
I have an entity Contrat containing several fields and the following field :
#OneToMany(mappedBy = "contrat", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
#Cascade( { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
#BatchSize(size = 50)
private Set<MonElement> elements = new HashSet<MonElement>();
I must read some "Contrat" objects from a database and write them in another database.
I hesitate between 2 solutions :
use jdbc to query the first database and get the objects and then write those objects into the second database (paying attention to the order and the different keys). It will be long.
as the project currently uses Hibernate and contains all hibernate mapping classes, I was thinking about opening a first session to the first database, reading the hibernate Contrat object, setting the ids to null in the children elements and writing the object to the destination database with a second session. It should be quicker.
I wrote a test class for the second use case and the process fails with the following exception :
org.hibernate.HibernateException: Don't change the reference to a
collection with cascade="all-delete-orphan"
I think the reference must change when I set the ids to null, but I am not sure : I don't understand how changing a field of a Collection member can change the Collection reference
Note that if I remove DELETE_ORPHAN from the configuration, everything works, all the objects and their dependencies are written in the database.
So I would like to use the hibernate solution which is faster but I have to keep the DELETE_ORPHAN feature because the application currently uses this feature to ensure that every MonElement removed from the elements Set will be deleted in the database.
I don't need this feature but cannot remove it.
Also, I need to set the MonElement ids to null in order to generate new ones because their id in the first database may exist in the target database.
Here is the code I wrote which works well when I remove the DELETE_ORPHAN option.
SessionFactory sessionFactory = new AnnotationConfiguration().configure("/hibernate.cfg.src.xml").buildSessionFactory();
Session session = sessionFactory.openSession();
// search the Contrat object
Criteria crit = session.createCriteria(Contrat.class);
CriteriaUtil.addEqualCriteria(crit, "column", "65465454");
Contrat contrat = (Contrat)crit.list().get(0);
session.close();
SessionFactory sessionFactoryDest = new AnnotationConfiguration().configure("/hibernate.cfg.dest.xml").buildSessionFactory();
Session sessionDest = sessionFactoryDest.openSession();
Transaction transaction = sessionDest.beginTransaction();
// setting id to null, also for the elements in the elements Set
contrat.setId(null);
for (MonElement element:contrat.getElements()) {
element.setId(null);
}
// writing the object in the database
sessionDest.save(contrat);
transaction.commit();
sessionDest.flush();
sessionDest.close();
This is way faster than managing myself the queries and the primary / foreign keys and dependencies between objects.
Does anyone have an idea to get rid of this exception ?
Or maybe I should change the state of the Set.
In fact I'm not trying to delete any element of this Set, I just want them to be considered as new objects.
If I don't find a solution, I will do something dirty : duplicate all hibernate entity objects in my new project and remove the DELETE_ORPHAN parameter in the newly created Contrat.
So the application will continue using its mapping and my new project will use my specific mapping. But I want to avoid that.
Thanks
A correct solution has been written by crizzis as a comment to my question.
I quote him :
I'd try wrapping the contrat.elements in a new collection (contrat.setElements(new HashSet<>(contrat.getElements())) before trying to persist the contract with the new session
It works well.

JPA lazy fetch list invokes SELECT queries in setter

There is a method which returns entity from database by JPA. This entity has list for other entities, fetch type is LAZY. When I want to add object to this list I got exception:
Caused by: Exception [EclipseLink-7242] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.ValidationException
Exception Description: An attempt was made to traverse a relationship using indirection that had a null Session. This often occurs when an entity with an uninstantiated LAZY relationship is serialized and that lazy relationship is traversed after serialization. To avoid this issue, instantiate the LAZY relationship prior to serialization.
So in order to overcome this I can initialize this list by doing .size() on it. The thing is I don't really need these objects to be fetched from database so I would like to do something like this:
fetchedEntity.setMyLazyFetchList(new ArrayList<>()); which works fine. I can further access my list, but the problem is as following: set method invokes the same select queries as fetchedEntity.getMyLazyFetchList().size() does. These queries are useless as I set value to a new list, so why are they invoked?
Method fetching entity
public Competitor findAndInitializeEmptyGroups(Integer idCompetitor) {
Competitor entity = em.find(Competitor.class, idCompetitor);
System.out.println("Before set ");
entity.setGroupCompetitorList(new ArrayList<>());
System.out.print("After set lazy list size ");
System.out.print(entity.getGroupCompetitorList().size());
return entity;
}
Lazy fetch list field in entity (Competitor)
#OneToMany(mappedBy = "idCompetitor")
private List<GroupCompetitor> groupCompetitorList = new ArrayList<>();
Second end relationship field (GroupCompetitor)
#JoinColumn(name = "id_competitor", referencedColumnName = "id_competitor")
#ManyToOne(optional = false)
private Competitor idCompetitor;
What logs say:
Info: Before set
Fine: SELECT id_group_competitor, id_competitor, id_group_details FROM group_competitor WHERE (id_competitor = ?)
bind => [43]
Fine: SELECT id_group_details, end_date, start_date, version, id_competition, id_group_name FROM group_details WHERE (id_group_details = ?)
bind => [241]
...
many more SELECTs
Info: After set lazy list size
Info: 0
After replacing line
entity.setGroupCompetitorList(new ArrayList<>());
with
entity.getGroupCompetitorList().size();
And logs (they are the same except the list now consists of fetched entities):
Info: Before set
Fine: SELECT id_group_competitor, id_competitor, id_group_details FROM group_competitor WHERE (id_competitor = ?)
bind => [43]
Fine: SELECT id_group_details, end_date, start_date, version, id_competition, id_group_name FROM group_details WHERE (id_group_details = ?)
bind => [241]
...
many more SELECTs
Info: After set lazy list size
Info: 44
So my question is: why SELECT queries are invoked when I do entity.setGroupCompetitorList(new ArrayList<>());? I don't want them for the performance reasons. Is there any way to eliminate this issue or what exactly causes this behavior?
Using:
EclipseLink JPA 2.1
GlassFish 4.1
Java 8
You can't not fetch a list that is a member of an entity if you want to add an element and have the JPA provider persist it. The JPA provider has to track the owner, the ownee, and handle any cascading (which I don't see you have defined but I doubt there's a different code path for each combination of cascading options). The simplest way would be to have the list in memory and then decide what operation to perform on the DB at commit/flush time.
I believe the cause of your original exception about traversing a LAZY is due to accessing outside on a managed context. Once you return from an EJB method, the entity you're returning becomes detached. You have to reattach it to another EntityManager or make sure all the lazy relationships you're about to use have been loaded before you leave the method. Calling fetchedEntity.getMyLazyFetchList().size() would be an example of that and works fine in a single entity case. If you want to force the load of a LAZY in a list of entities I suggest you read up on LEFT JOIN FETCH clauses. I'm assuming here that your findAndInitializeEmptyGroups() method is in an EJB, judging by what looks to me like an injected EntitManager em in that method, and that the methods will get the default #TransactionAttribute(REQUIRED) treatment since I don't see any annotations to the contrary.
Now, let's go back to your original problem:
I want to add object to this list
The problem you're trying to solve is to add an element to a list without fetching the entire list. You're using a mappedBy attribute, meaning you've created a bidirectional relationship. If getGroupCompetitorList() returns an unordered list (a 'bag' in Hibernate speak) then you don't have to load the list at all. Try something like this:
Change GroupCompetitor's Integer idCompetitor to a #ManyToOne(fetch=FetchType.LAZY) Competitor competitor. Adjust the getters and setters accordingly.
Change Competitor's groupCompetitorList mappedBy to competitor. Add get/set methods.
Then you can add to the list from the child side with a method like this in the EJB:
public void addNewGroupCompetitorToCompetitor(Competitor comp, GroupCompetitor gComp) {
gComp.setCompetitor(comp);
em.persist(gComp);
em.flush();
}
The next time you fetch the Competitor again it and traverse entity.getGroupCompetitorList() (while managed by an EntityManager) it should have the new GroupCompetitor you've added. This kind of thing gets more complicated depending if comp is a new entity that has not been persisted, but that's the basic idea. It might need some adjustment to work correctly with EclipseLink, but I do the same kind of operation with Hibernate as the JPA provider and it works.

How to solve Hibernate's LazyInitializationException

I have CustomerEntity mapped to table customers in database. It has one-to-many relations to orders and addresses. Now I retreive customer with orders with next code:
DetachedCriteria criteria = DetachedCriteria.forClass(CustomerEntity.class);
criteria.add(Restrictions.eq("id", patientId));
criteria.createCriteria("orders", CriteriaSpecification.LEFT_JOIN).add(Restrictions.and(
Restrictions.isNull("deletedDate"),
Restrictions.or(Restrictions.isNull("old"), Restrictions.eq("old", BoolType.FALSE))));
CustomerEntity customer = queryDao.searchSingle(criteria);
QueryDAO:
public <T> T searchSingle(DetachedCriteria criteria) {
return (T) criteria.getExecutableCriteria(getCurrentSession()).uniqueResult();
}
But when I try to invoke customer.getAddresses() next exception is thrown:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: , no session or session was closed
It happens because by default hibernate isn't loaded one-to-many entities.
How can I without modifying Customer entity retreive his addresses too?
This exception occurs when you try to access the lazy-loading collection or field at the moment when session is not available already. My guess would be - the backend of your application loads data and closes the session, then you pass this data to frontend, for example, where there is no session already.
To resolve this, there are several possible ways to go:
Eager loading. Reliable, but possibly slow solution;
Changing the way session management is done. Consider strategy session-per-request which is suitable for typical web application. The question Create Hibernate-Session per Request contains information might be usable for you.
For information about another session management strategies, try reading this wiki page.
There are a few things that can assist you:
The use of Hibernate.initialize(customer.getAddresses())
You do not show where customer.getAddresses() is used ... so one other method is to use Hibernate Open Session in View (along with other session management strategies mentioned in the previous answer.

JPA EntityManager with Hibernate - find and remove does not delete data from database

Having trouble getting the following code to work...
I've got a JpaTransactionManager txManager autowired into this test. I know record with ID 39 does exist. It still exists at the end of the transactions, too...
TransactionStatus status = txManager.getTransaction(def);
A a = mock(A.class);
when(a.getId()).thenReturn(Long.valueOf(39));
sut.delete(a);
txManager.commit(status);
status = txManager.getTransaction(def);
a = sut.get(a.getId());
txManager.commit(status);
assertNull(a);
Code in class A:
public void delete(A a) {
a = getEntityManager().find(A.class, a.getId());
getEntityManager().remove(a);
}
Is there any reason the above assertNull check always fails? I cannot delete the object from my system no matter what I do - no error returned, and no issue with the delete reported. (As an aside, running a query directly in HQL does result in an update of the database...I just can't get it to work using the delete method supplied using JPA...)
Any assistance appreciated
You should take a look into these Hibernate classes/methods:
org/hibernate/engine/spi/ActionQueue.java executeActions(), unScheduleDeletion()
org/hibernate/event/internal/DefaultPersistEventListener.java onPersist()
I had the same problem - not being able to remove an entity. In my case, entityManager had two entities in its 'context': a parent with a list of children entities (cascade = CascadeType.ALL) and a child (from the list) to remove. So when I was trying to remove a child, parent still had a link to it, which was causing Hibernate to 'unScheduleDeletion' upon flushing.
So here is the solution:
Add orphanRemoval = true to the collection of children
Create method deleteChild(Child child) {child.setParent(null); children.remove(child);}
Use this method to delete children
Looks like another solution is to remove cascading, so that merging of parent entity wouldn't cause saving all its children. Not quite sure here (haven't checked).
Also, as far as I remember, JPA spec describes this situation.

How to test whether lazy loaded JPA collection is initialized?

I have a service that gets a JPA entity from outside code. In this service I would like to iterate over a lazily loaded collection that is an attribute of this entity to see if the client has added something to it relative to the current version in the DB.
However, the client may have never touched the collection so it's still not initialized. This results in the well known
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.SomeEntity.
Of course, if the client never touched the collection, my service doesn't have to check it for possible changes. The thing is that I can't seem to find a way to test whether the collection is initialized or not. I guess I could call size() on it and if it throws LazyInitializationException I would know, but I'm trying not to depend on such patterns.
Is there some isInitialized() method somewhere?
Are you using JPA2?
PersistenceUnitUtil has two methods that can be used to determine the load state of an entity.
e.g. there is a bidirectional OneToMany/ManyToOne relationship between Organization and User.
public void test() {
EntityManager em = entityManagerFactory.createEntityManager();
PersistenceUnitUtil unitUtil =
em.getEntityManagerFactory().getPersistenceUnitUtil();
em.getTransaction().begin();
Organization org = em.find(Organization.class, 1);
em.getTransaction().commit();
Assert.assertTrue(unitUtil.isLoaded(org));
// users is a field (Set of User) defined in Organization entity
Assert.assertFalse(unitUtil.isLoaded(org, "users"));
initializeCollection(org.getUsers());
Assert.assertTrue(unitUtil.isLoaded(org, "users"));
for(User user : org.getUsers()) {
Assert.assertTrue(unitUtil.isLoaded(user));
Assert.assertTrue(unitUtil.isLoaded(user.getOrganization()));
}
}
private void initializeCollection(Collection<?> collection) {
// works with Hibernate EM 3.6.1-SNAPSHOT
if(collection == null) {
return;
}
collection.iterator().hasNext();
}
org.hibernate.Hibernate.isInitialized(..)
There is no standard JPA solution to my knowledge. But if you want to actually initialize collections, you can create an utility method and iterate them (only one iteration is enough).
For eclipselink, users cast the collection you are trying to access to an org.eclipse.persistence.indirection.IndirectList, and then call its isInstantiated() method. The following link has more information:
http://www.eclipse.org/eclipselink/api/1.1/org/eclipse/persistence/indirection/IndirectList.html#isInstantiated.

Categories

Resources