Entity ObjA has a One-To-Many relationship with another entity ObjB.
The following code in my dao works as expected:
Session session = getSessionFactory().openSession();
Criteria criteria = session.createCriteria(ObjA);
//......
criteria.setReadOnly(true);
criteria.setCacheable(false);
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
List<ObjA> results = criteria.list();
To optimize above code (to avoid OutOfMemoryException), I tried using ScrollableResults instead of criteria.list():
//same as above code..
ScrollableResults results = criteria.scroll(ScrollMode.FORWARD_ONLY);
while(results.next()){
ObjA a = results.get()[0];
session.evict(a);
//store 'a' in a collection
}
However, using this approach, I ended up getting individual rows for each ObjB corresponding to ObjA (in a sense, duplicate root entities). I tried not evicting the entity, but still got duplicate rows.
Any idea what I am doing wrong/missing here?
P.S. Hibernate version - 3.5.1-FINAL
Seems like this is a never-ending bug/required enhancement in hibernate.
I ended up using fetch="select" for all one-to-many joins.
Although this increased the db queries, but removed the duplicate root entities.
Related
I'm trying to use Hibernate criteria without joining the sub-classes. Here is my case:
Notification.hbm.xml has the relation:
<many-to-one name="myObject" class="MyObject" column="MY_OBJECT_UID" not-null="true" index="NOTIFICATIONS_OBJECT" foreign-key="NOTIFICATIONS_OBJECT"/>
my JAVA code:
1. Criteria criteria = session.createCriteria(Notification.class.getName());
2. criteria.createAlias("myObject", "myObject", CriteriaSpecification.LEFT_JOIN);
3. criteria.addOrder(Order.desc("myObject.name").ignoreCase());
4. List<Notification> res = (List<Notification>) criteria.list();
If i remove lines 2 and 3 i get the result without joining MyObject, but if i add them, then the query is massive and doing Join with all of MyObject sub classes (and there are many).
I need a solution to avoid those Hibernate joins. One join for MyObject is OK (although not necessary), but Join to every subClass of it, is bad.
What are my option here?
Thanks.
One solution is to use projection, to query only the field you need
ProjectionList properties = Projections.projectionList();
properties.add(Projections.property("property1").as( "property1"));
...
criteria.setProjection(properties);
criteria.setResultTransformer(new AliasToBeanResultTransformer(Notification.class));
You can map it, to you domain Object but its is better to map it to custom DTO like a NotificationDTO, because it won't be managed entity but simple pojo:
You won't be able to update/ delete it. But if you need the data in "read only" mode its the best solution.
I've "Student" and "Class" in relation many to many. In between there is a linking table.
If I want to retrieve all the students with HQL in the following way, everything is fine:
Query queryObject = getSession().createQuery("from Student");
return queryObject.list();
With the above code if there are three students I get a list of three students.
If I use criterias, however, it's fine only as long as there are no associations in the linking table.
Criteria crit = getSession().createCriteria(Student.getClass());
return crit.list();
With the second code, I get three results only when the linking table is empty. However when I add an association, I get 6 results. Looking at the log, Hibernate generates multiple selects. How is it possible?
Can someone explain why does this happen? How can I fix the criteria in order to return the three records only once?
EDIT
In the Student class I mapped in this way:
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(
name="room_student_rel"
, joinColumns={
#JoinColumn(name="id_student")
}
, inverseJoinColumns={
#JoinColumn(name="id_room")
}
)
private List<Room> rooms;
In the room (class) I mapped in this way:
#OneToMany(fetch=FetchType.EAGER, mappedBy="room")
#Fetch(FetchMode.SELECT)
private List<RoomStudent> roomStudents;
To expand on my previous comment, Hibernate will try to select using an outer join when the FetchType is set to EAGER.
See 2.2.5.5 at the following:
http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
For the implications of this see here:
Hibernate Criteria returns children multiple times with FetchType.EAGER
Now you can also specify a FetchMode (on your criteria.setFetchMode("assocFiled",FetchMode.LAZY) ) to set it to something other than JOIN or you can use something like criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); to filter the duplicates.
Ideally you should avoid EAGER on your entity mappings and where eager fetching is required enable it explicitly on entity load using some hint or via FetchProfile and then deal with the duplicates if required.
i have written a small piece of code like this. i need all the students and they phones in a one to many relationship.
public ArrayList<Student>getListOfStudents()
{
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria like = session.createCriteria(Student.class).setFetchMode("phone",FetchMode.JOIN).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
prepareForSelect(like);//some filtering
like.addOrder(Order.asc("name"));
ArrayList<Student> results = new ArrayList<Student>(like.list());
session.close();
return results;
}
this is working very good but i need to tweak it because i need to implement the same criteria filtering in phone as well i have redesign my snippet to something like this.
public ArrayList<Student>getListOfStudents()
{
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria like = session.createCriteria(Student.class).setFetchMode("phone",FetchMode.JOIN).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
prepareForSelect(like);//some filtering
Criteria innerCriteria = like.createCriteria("phone");
prepareForSelect(innerCriteria);//same filtering but in phone now..
like.addOrder(Order.asc("c01"));
ArrayList<Student> results = new ArrayList<Student>(like.list());
session.close();
return results;
}
but hibernate has changed from left outer join to a inner join and when i run the data in the same methods the before code was running i can see a error is throw.
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.generic.model.Student.phone, no session or session was closed.
my question is what i am doing wrong how can i apply the filters and get the joins correctly thanks a lot.
Well, the problem is that the phones of a student are not eagerly fetched. And since you are closing the session, when you are try to access the phones you are getting LazyInitializationException.
But you are asking yourself I had set the fetch mode to eager by using setFetchMode("phone",FetchMode.JOIN) and it was working just fine. The thing is, there is a bug in Hibernate which disables eager fetching when you add an inner query to a criteria. It is still open. See here:
https://hibernate.atlassian.net/browse/HHH-3524
And if you have time you can take a look at here:
https://forum.hibernate.org/viewtopic.php?f=1&t=944439
If you do, make sure you read the second page, it gets funnier.
So my suggestion is to make the fetch eager at the class level, by using fetch=FetchType.EAGER or switch to CriteriaBuilder (JPA).
I have object called 'MasterObj'. In that 'MasterObj', I have a child object called 'EmployeeObj'(foreign Key)
The relation ship between 'MasterObj' and 'EmployeeObj' is one to Many.
And my 'MasterObj' had so many duplicate 'employeeObj'
I need a count of MasterObj with DISTINCT or without duplication of emp_SlNo
How can I filter the duplicate emp_SlNo from my MasterObj using hibernate criteria.
Sorry for the my bad english.
Thanks in adance.
After so much of google, finally I got this code:
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("id.state"));
projList.add(Projections.property("id.uspsCity"));
criteria.setProjection(Projections.distinct(projList));
And it works fine for me.
It eliminates the duplicate child object from parent objects.
I have the following code:
Person a = new Person();
a.setName("John");
Session session = openHibernateSession();
session.beginTransaction();
session.saveOrUpdate(a);
Criteria critera = session.createCriteria(Person.class);
critera.add(Restrictions.eq("name","John"));
Person personFromCache = (Person) criteria.uniqueResult();
...
session.commit();
What I want is to have the ability to search objects from both the database and Hibernate's cache. The following example returns null upon calling uniqueResult. Is there any way to retrieve saved objects that have not yet been committed to the database?
If you are searching other than ID then Hibernate will not use first level cache. Hibernate get and load is related to first level cache by default but criteria query is not. In your case there are two solution from my side
By flushing session = Just flush your session like this session.flush(); while doing so data from session will be synchronize to database hence Id will ge generated and as result Criteria query will find the result in database and will result list to you.
Enable hibernate second level cache = You can enable second level cache by hibernate cache providers like ehCache and apply the trick.
You can use the StatelessSession but be warned:
those entitys are not bound to any session and you will get Exceptions if you like to resolve relations or lazy fields!
session.beginTransaction();
session.saveOrUpdate(a);
session.flush();
Criteria critera = session.createCriteria(Person.class);
critera.add(Restrictions.eq("name","John"));
Person personFromCache = (Person) criteria.uniqueResult();
We do some similar things except using TestNg test framework. Several of the answers discuss the session.flush() method call. This is correct. The call to flush tells Hibernate to do several things, including making sure that all database calls currently waiting in the queue get executed and cleared from the queue.
It returns data even if you are selecting on the basis of username. It is not returning null.