Hibernate Criteria and row count restriction - java

i have two entities named Parent and Child, linked in a one-to-many relationship. The Child entity has a boolean isStudent property.
How do i get, using the Hibernate Criteria API, all the Parent entities that have at least one Child with isStudent = true?
I was trying to use a Projection object to count all the parents that have at least one Child with the property correctly set, and then return those whose row count is greater than zero, like in the following piece of code (which doesn't work, though):
Criteria criteria = getCurrentSession().createCriteria(Parent.class);
criteria.setProjection(Projections.alias(Projections.rowCount(), "count"))
.add(Restrictions.gt("count", 0)).createCriteria("children")
.add(Restrictions.eq("isStudent", true));
Thanks for your help

This worked for me:
DetachedCriteria crit = DetachedCriteria.forClass( Parent.class, "theparent" );
DetachedCriteria countSubquery = DetachedCriteria.forClass( Child.class , "child" );
countSubquery
.add( Property.forName("theparent.id").eqProperty( "parent.id" ) )
.setProjection(Projections.count("id"));
crit.add(Subqueries.lt(Long.valueOf(0), countSubquery));
[Edit: fixed a bug a pointed out by #brabenetz]
Where there is a bidirectional relation between Parent and Child, i.e. the Child has a field "parent"
It returns the Parents that have >0 children.

The Answer from RobAu is nearly what we needed.
But there is a small bug in it: Instead of Subqueries.gt(..) you need Subqueries.lt(...)
crit.add(Subqueries.lt(Long.valueOf(0), countSubquery));

Related

Hibernate criteria, selecting from entity A where someId in B

Given entity classes:
class A { String name ... }
class B { #OneToOne A a = ... ; String gender; ... }
I am able to create a hibernate criteria and query it and all of it's associations.
However, B is not referenced from A at all. It's populated in a separate fashion and I would like to keep it as such.
Is there a way to perform a hibernate criteria query on A, and say, where this also is in B's relation, or where it is in B's relation and some field in B is this and that?
Here is some non-working pseudo code:
criteria(A.class).add(
Restrictions.eq("name", "something")
).createAlias(
B.class, "..."
).add(
Restrictions.eq("gender", "Female")
);
Note, I would prefer not having to create a collection on A containing B's and using addAll.
I might consider adding a dead reference though, meaning something that is never intended to be accessed or updated but needed for hibernate to pull this off. It could be something like saying that this is maintained by another table.
You can use a subquery, as follows:
// create a 'DetachedCriteria' query for all the items in 'B'
DetachedCriteria dc = DetachedCriteria.forClass(B.class).setProjection(
Projections.projectionList().add(Projections.property("propertyInB"))
);
// then: search for A.id by adding 'dc' as asubquery:
session.createCriteria(A.class).add(
Subqueries.propertiesIn( new String[]{"propertyInA"}, dc)
).list();
This is roughly equivalent to: 'SELECT * FROM A a WHERE a.id in(SELECT id FROM B).
I hope my SQL is valid ;) .
See hibernate documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/querycriteria.html

query that should return entities qith specific related entities

Generally my questian is very simple I think, nevertheless I couldn't find a good solution. Let's say I have an Entity class called MyEntity which has a OneToMany relation to an Entity class called EntityAttribute, so it has a list or set attributes with objects of this class. EntityAttribute has an attribute name of type String.
Now I want to implement a method which takes attribute names and returns all entities that contains for each name in attributes at least one attribute with that name. Although this sounds very straight forward, the only solution I found was to execute a query for each attribute name and merge the results like this:
for (String name : attributeNames) {
CriteriaQuery<MyEntity> cq = cb.createQuery(MyEntity.class);
Root<MyEntity> entity = cq.from(MyEntity.class);
Join<MyEntity, EntityAttribute> attributeJoin = entity.join(MyEntity_.attributes);
cq.where(attributeJoin.get(EntityAttribute_.name).equals(name));
cq.select(entity);
... // get result list and merge
}
This code isn't tested but generally is one solution. This doesn't seem to be the most efficient one.
Another solution I testet was to use multiple joins like
CriteriaQuery<MyEntity> cq = cb.createQuery(MyEntity.class);
Root<MyEntity> entity = cq.from(MyEntity.class);
List<Predicate> predicates = new ArrayList<>();
for (String name : attributeNames) {
Join<MyEntity, EntityAttribute> attributeJoin = entity.join(MyEntity_.attributes);
predicates.add(attributeJoin.get(EntityAttribute_.name).equals(name));
}
cq.select(predicates.toArray(new Predicate[] {}));
... // get result list
This seems to be more efficient, but it iterates over the cartesian products... So it's highly inefficient.
I could also imagine to nest subqueries, but this seems to be very complicated.
The question simply is: What is the best solution for this problem? Afterwards I would also like to implement AND and OR, so I can query for all entities with attributes x and (y or z) or something like that. But for now I only want to make the AND case.
Thanks in advance
Maybe you could achieve this using in clause + group by + having + count, if I understand your question correctly. The idea is to count the number of matches for each MyEntity. If the count is equal to the number of attributes passed in, it means that each of them was found for that entity (assuming they are unique). In JPQL the query would look like this:
select e from MyEntity e join e.attributes a
where a.name in (:attributeNames)
group by e having count(*) = :attributeCount
where :attributeCount is the value of attributeNames.size().
I'm not very familiar with the criteria API, but you can experiment with something like this:
...
cq.groupBy(entity);
cq.having(cb.equal(cb.count(entity), attributeNames.size()));
// TODO: add IN clause
...

Hibernate and criteria that return the same items multiple times

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.

How to apply DISTINCT for Child Object using hibernate criteria

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.

How to add property counted in DB to #Entity class?

I have an Entity. And sometimes I need this object also contains some value, call it 'depth'. The query may look like 'select b.id, b.name, b..., count(c.id) as depth from Entity b, CEntity c where ...'. So I've created class NonHibernateEntity which extends Entity. And then results of query written above are perfectly stored as List of NonHibEntity, which consists of all the fields of Entity (as they are extended), and property 'depth'. I make it by setting aliasToBean results transformer: .setResultTransformer(Transformers.aliasToBean(NHEntity.class)).
But, it is annoying and inconvenient - to specify all the aliases of all the needed fields.
And then, if I want to save one of this object to DB - session.saveOrUpdate((Enity)nHibEntity) - there are an exception about nHibEntity isn't Hibernate Entity.
I heard about storing 'entity' as field in NonHibEntity (aggregation, not inheritance). But it seems this is rather inconvenient too.
What do you think? What is an appropriate solution?
A Formula column mapping may be suitable for your needs. I would try this first.
If this causes performance issues as you fear, you might try a mapped class hierarchy with this field only in the child, and mapping both to the same table. Not sure this will actually work though...
As a last resort, do what you've got now using a non-mapped class, but with the entity as a field in your other class - aggregation instead of inheritance as you say, and make sure there's a way of retrieving the mapped entity from the unmapped one so that you can save. It be sensible to make it a Decorator, so that it's both a subclass and aggregate and you can continue to ignore the distinction in much of your code.
With the non-mapped subclass and/or aggregate, however, you'll have to pull out the entity in order to save.
If somebody want to know - I solved this problem in such way:
just made this calculated field as #Transient, and then
List<BaseEntry> result = new ArrayList<BaseEntry>();
Iterator it = session()
.createQuery("select b, (select count(p.id) as depth from BaseEntry p " +
" where ... ) as d" +
" from BaseEntry b " +
" where ... ")
.list().iterator();
while ( it.hasNext() ) {
Object[] row = (Object[]) it.next();
BaseEntry entry = (BaseEntry) row[0];
Long d = (Long) row[1];
entry.setD(d);
result.add(entry);
}
return result;
It works good, and seems that it can be easily supported in future

Categories