JPA: Query that returns multiple entities - java

I'm writing a JPQL query that joins across three tables. In my resultlist I would like to get all three entities per matching row (hope that makes sense).
Any ideas?
Hibernate 3.x is my JPA provider.

IIRC, you can do a SELECT o1, o2, o3 FROM EntityA o1, EntityB o2, EntityC o3 WHERE ...., and the result will be a List<Object[3]>, where the array contents will contain the o1,o2,o3 values.

This is a Spring Data sample, however its works the same way in JPA
//HQL query
#Query("SELECT c,l,p,u FROM Course c, Lesson l, Progress p, User u "
+ "WHERE c.id=l.courseId AND l.id = p.lessonId AND p.userId = u.id AND u.id=:userId AND c.id=:courseId")
public List<Object[]> getLessonsWithProgress(#Param("userId") Integer userId, #Param("courseId")Integer courseId);
Then, I call this method and print the results:
List<Object[]> lst = courseRepository.getLessonsWithProgress(userId, courseId);
for (Object o[] : lst) {
Course c = (Course) o[0];
Lesson l = (Lesson) o[1];
Progress p = (Progress) o[2];
User u = (User) o[3];
//all the classes: Course, Lesson, Progress and User have the toString() overridden with the database ID;
System.out.printf("\nUser: %s \n Lesson: %s \n Progress: %s \n Course: %s",u,l,p,c);
}
The output #Test is here:
User: com.cassio.dao.model.User[ id=1965 ]
Lesson: com.cassio.dao.model.Lesson[ id=109 ]
Progress: com.cassio.dao.model.Progress[ id=10652 ]
Course: com.cassio.dao.model.Course[ id=30 ]
Cheers

Since You are asking JPA: Query that returns multiple entities, EclipseLink too comes under it. And I reached on this question googling for EclipseLink. So here is my solution. Hope it works for you.
TypedQuery<Object[]> query = entityManager.createQuery("select p from Post p where p.publisher.pubId= :ID order by p.createdAt desc",
Object[].class);
query.setParameter("ID", publisherID);
Then you can loop through the result objects and cast them accordingly.
for (Object result : query.getResultList()) {
myList.add((Post) result);
}
You can also try this,
Query query = entityManager.createQuery("select p from Post p where p.publisher.pubId= :ID order by p.createdAt desc");
Reference:http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL

In case of many to one or one to many relationship how to get multiple records of one of the entities? lets say A is one entity and B is another entity but they have one to many relationship and when you get result you expect B has one record and A has more than 1 record? My query is as below but I don't know how shall I get multiple records of 2nd entity?
#Query("SELECT wl, gr FROM WatchList as wl, GeozoneReference gr " +
"WHERE wl.watchlistId = gr.objWatchList.watchlistId " +
"AND wl.watchlistId =:watchlistId")
List<Object[]> findWatchlistByWatchlistId(#Param("watchlistId") Long watchlistId);

Related

Select multiple items based on multiple IDs with hibernate

I am using hibernate in eclipse.
And i have 3 ONE TO MANY relation tables.
ONE [Company] --> MANY [Officer], And ONE [Officer] --> MANY [Task].
They all have unique id (companyId, officerId, taskId).
Currently i know how to find all the tasks belong to an officer and i also know how to find all the officers belong to a company.
here is the code snippet:
public static ArrayList<Officer> getOfficersByCompany(Company company){
ArrayList<Officer> officers = new ArrayList<Officer>();
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Officer.class);
detachedCriteria.add(Restrictions.eq(Key.COMPANY, company));
detachedCriteria.add(Restrictions.eq(Key.OBJSTATUS, Value.ACTIVED));
List<Object> list = HibernateUtil.detachedCriteriaReturnList(detachedCriteria);
for(Object o : list){
officers.add((Officer) o);
}
return officers;
}
And below is the detachedCriteriaReturnList method in HibernateUtil class.
public static List<Object> detachedCriteriaReturnList(DetachedCriteria dc){
Session session = getSessionFactory().openSession();
session.beginTransaction();
Criteria criteria = dc.getExecutableCriteria(session);
List<Object> list = criteria.list();
session.getTransaction().commit();
session.close();
return list;
}
However, if i try to get all the task belongs to a company, how should i implement the code. I have tried using: detachedCriteria.add(Restrictions.allEq(officers));
public static ArrayList<Task> getTasksByOfficers(Map<String, Object> officers){
ArrayList<Task> tasks = new ArrayList<Task>();
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Task.class);
detachedCriteria.add(Restrictions.allEq(officers));
List<Object> list = HibernateUtil.detachedCriteriaReturnList(detachedCriteria);
for(Object o : list){
tasks.add((Task) o);
}
return tasks;
}
But i realise that the map only stores unique key and value pair, if i try to use the second officer's id the first one will be replaced.
Or is there any other ways to perform the selection faster and more efficient?
ONE [Company] --> MANY [Officer], And ONE [Officer] --> MANY [Task].
In HQL this should be pretty straight forward (I gave up using Criteria API, as it did not allow joining the same object class twice):
Obviously, I did not test the queries, but they should work...
select Task
from Task join Task.Officer o join o.Company c
where c.name = 'xxx'
or
select t
from Company c join c.Officers o join o.Tasks t
where c.name = 'xxx'
Note: My experience with criteria is limited, as I have said. But looking at your code, I have two comments. Maybe you should not use the Map of officers but the list officers.values() to allEq(). Second remark: If you didn't do a mistake, Hibernate obviously magically finds the reference to the property you want to be allEq with, otherwise, the allEq misses a property you want to compare to.
UPDATE: Ok, as promised, I checked the Javadocs for you: https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/Criteria.html
A join in HQL is the equivalent of a criteria added to another criteria or an alias:
detachedCriteria = DetachedCriteria.forClass(Task.class)
.createCriteria("Officer") // Officer property of task
.createCriteria("Company") // Company property of officer
.add(Restriction.eq(Key.COMPANY, compKey); // the company
List<Object> list = detachedCriteria
.getExecutableCriteria(hibernateSession)
.list();
This should get you going...
Note on HQL:
To run the query, you get a query from the hibernate session with the HQL and then you call list() or executeUpdate()on it. taskHqlString is a HQL-statement as above. Replace the companyKey in the query with a named parameter :companyKey:
String taskHqlString = "select Task "
+ " from Task join Task.Officer o "
+ " join o.Company c "
+ " where c.name = :companyKey";
List<Task> list = (List<Task>)hibernateSession
.createQuery(taskHqlString)
.setParameter("companyKey", companyKeyValue)
.list();

Hibernate result list cast to get rows

Hello I had problem with iterate Hibernate ResultList
I had followed query that I got from external class:
queryContent = "select distinct c.identity, c.number, c.status, ctr.name, aab.paymentConditions.currency from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
And I must sum whole aab.paymentConditions.currency, check numbers of statutes and names.
I want to do this by iterate list of results:
Query q = session.createQuery(queryContent);
List result = q.list();
Long wholeCurrency, numberOfStatutes;
for(Object res : result){
//wholeCurrency += res.getColumnName?
}
My question is how to cast res Object to have possibility to get concrete column values? I had read about create map inside hibernate query but I don't know it is good practice to modyfied query string by adding
"new map(" prefix and then ")"
before from sql statement
Solution:
After All I decided to use map in my query. I modified my external query by adding hibernate map statement by replacing select by 'select new map(' and from by ') from'.
Additional thing is to add 'as' statement with name of key because without them column keys are integer.
So after all my query looks like follow:
"select new map( distinct c.identity, c.number, c.status as status, ctr.name as name, aab.paymentConditions.currency as currency ) from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
That was the most siutable solution for me, I tried with 'NEW com.example.MyClass' as Kostja suggested but in my case I didn't have control for incoming query so I can not rely on pernament constructor.
new List( select...
Is also interest but it also didn't tell me on with position I have my field that give me information.
If I understand correctly, you want to have a typed representation of your result without it being an entity itself. For this, you can use constructor queries:
"SELECT NEW com.example.MyClass( e.name, e.data) FROM Entity e"
MyClass has to have a matching constructor. Full qualification (com.example) is not mandatory AFAIK.
If you are using this query often, creating a view in the DB may be a good idea. You can map a view to an entity just as if it were a regular table, but please note that you cannot store changes to you data over a mapped view.
EDIT: Turns out, mapping to an unspecified Map is alright with Hibernate:
select new map( mother as mother, offspr as offspr, mate as mate )
As per http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch11.html#ql-select-clause
you can use
queryContent = "select new list(distinct c.identity, c.number, c.status, ctr.name, aab.paymentConditions.currency) from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
And you get List<List> as result.

How to flatten the results of a Hibernate query

Say I executed a theoretical HQL query like FROM Customer. And in Customer is a getOrders() getter returning a ManyToOne collection of Order objects. This executes a SQL statement selecting from Customer with a left join to Order.
Through the object model, I can programmaticly iterate over Customers and then iterate over Orders.
However, I want to convert the hierarchical object model to a flat tabular result of the left join so that the results would look much like that of this SQL query:
SELECT *
FROM Customer
LEFT JOIN Order on Customer.customerId = Order.customerId
Sample result:
Customer.customerId ... Order.orderId Order.customerId ...
1 200 1
2 201 2
2 202 2
3 NULL NULL
Is there an easy way to do this with Hibernate?
Depends what you want at the "scalar" level which you control through using an explicitl select clause.
select c, o
from Customer c left join c.orders o
returns you List of (Customer, Order) tuples. Or:
select c.id, c.name, o.id, ...
from Customer c left join c.orders o
which returns you a scalar projection of the atomic pieces.
In both cases you get back a List. You can use "dynamic instantiation" in both cases (though really its more useful in the second case imho):
select new CustomerOrderSummary( c.id, c.name, o.id, ... )
from Customer c left join c.orders o
where CustomerOrderSummary is just a plain class with matching constructor.
Possible you can do this via expressing of result set. http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/query_native.html
From the example:
#SqlResultSetMapping(name="GetNightAndArea", entities={
#EntityResult(name="org.hibernate.test.annotations.query.Night", fields = {
#FieldResult(name="id", column="nid"),
#FieldResult(name="duration", column="night_duration"),
#FieldResult(name="date", column="night_date"),
#FieldResult(name="area", column="area_id")
}),
#EntityResult(name="org.hibernate.test.annotations.query.Area", fields = {
#FieldResult(name="id", column="aid"),
#FieldResult(name="name", column="name")
})
})
I think you can try retrieving results in Object[] e.g. below:
EntityManager entityManager = EntityManager.getEntityManager();
Query query= entityManager.createQuery("select cust, ord from Customer cust left outer join cust.orders ord where cust.customerId = :customerId");
tradeQuery.setParameter("customerId", aCustomerId);
List<Object[]> resultList = (List<Object[]>)query.getResultList();
The retrieved resultsList will be list of Object array containing Customer and Order objects in flat.
if(!resultList.isEmpty()){
Iterator<Object[]> iter = resultList.iterator();
while(iter.hasNext()){
Object[] resultObj = (Object[])iter.next();
Customer customer= (Customer )resultObj[0];
Order order = (Order)resultObj[1];
}
}
Hope this helps!

How to run an aggregate function like SUM on two columns in JPA and display their results?

I am new to JPA. So my question should be so simple to some.
Below is the Simple Query in SQL which i would like to convert to JPA. I already have an entity class called TimeEnt.
SELECT
SUM(TimeEntryActualHours) as UnBilledHrs,
SUM (TimeEntryAmount) as UnbilledAmount
FROM TimeEnt WHERE MatterID = 200
The JPA Query Language does support aggregates functions in the SELECT clause like AVG, COUNT, MAX, MIN, SUM and does support multiple select_expressions in the SELECT clause, in which case the result is a List of Object array (Object[]). From the JPA specification:
4.8.1 Result Type of the SELECT Clause
...
The result type of the SELECT
clause is defined by the the result
types of the select_expressions
contained in it. When multiple
select_expressions are used in the
SELECT clause, the result of the query
is of type Object[], and the
elements in this result correspond in
order to the order of their
specification in the SELECT clause
and in type to the result types of
each of the select_expressions.
In other words, the kind of query you mentioned in a comment (and since you didn't provide your entity, I'll base my answer on your example) is supported, no problem. Here is a code sample:
String qlString = "SELECT AVG(x.price), SUM(x.stocks) FROM Magazine x WHERE ...";
Query q = em.createQuery(qlString);
Object[] results = (Object[]) q.getSingleResult();
for (Object object : results) {
System.out.println(object);
}
References
JPA 1.0 Specification
4.8.1 Result Type of the SELECT Clause
4.8.4 Aggregate Functions in the SELECT Clause
Lets think we have entity called Product:
final Query sumQuery = entityManager
.createQuery("SELECT SUM(p.price), SUM(p.sale) FROM Product p WHERE p.item=:ITEM AND ....");
sumQuery.setParameter("ITEM","t1");
final Object result= sumQuery.getSingleResult(); // Return an array Object with 2 elements, 1st is sum(price) and 2nd is sum(sale).
//If you have multiple rows;
final Query sumQuery = entityManager
.createQuery("SELECT SUM(p.price), SUM(p.sale) FROM Product p WHERE p.item in (" + itemlist
+ ") AND ....");
// Return a list of arrays, where each array correspond to 1 item (row) in resultset.
final List<IEniqDBEntity> sumEntityList = sumQuery.getResultList();
Take a look at the EJB Query Language specification.
The idiom is very similiar to standard SQL
EntityManager em = ...
Query q = em.createQuery ("SELECT AVG(x.price) FROM Magazine x");
Number result = (Number) q.getSingleResult ();
Regards,

Hibernate "IN" clause as ALL instead of ANY

I'd like to start by apologizing for my unfamiliarity with Hibernate. I'm only recently getting into it and am far from an expert.
I have three tables: Contract, Products, and a link table between them to define a many to many relationship.
I'm trying to write an HQL query to return all contracts that contain a range of products. Unfortunately, the IN syntax works like an Any instead of an All. So if I want all contracts that have ProductA, ProductB, and ProductC, the IN keyword will return me contracts that have any individual one of those products, instead of contracts that have all of them.
How should I structure my HQL query?
Why are you expecting IN to behave like a AND? To my knowledge, IN is a kind of OR, not a AND. IN might thus not be what you're looking for. Have a look at Hibernate's Expressions and especially:
HQL functions that take collection-valued path expressions: size(), minelement(), maxelement(), minindex(), maxindex(), along with the special elements() and indices functions that can be quantified using some, all, exists, any, in.
[...]
The SQL functions any, some, all, exists, in are supported when passed the element or index set of a collection (elements and indices functions) or the result of a subquery (see below):
[...]
from Show show where 'fizard' in indices(show.acts)
For more than 2000 ids at in clause use a subquery like [from group where groupid in(select id from elemtable)]
Otherwise use criteria to overcome the stackoverflow error.
Example:
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria criteriaEaquals = session.createCriteria(Elements.class);
criteriaEaquals.add(Restrictions.in("elementId", elemIds));
criteriaEaquals.setProjection(Projections.distinct(Projections.property("type")));
List list = criteriaEaquals.list();
session.close();
System.out.println("typelistis--->"+list.toString());
return list;
You can use group by / having:
select c
from Contract c join c.products p
where p.name in ('A', 'B', 'C')
group by c.id, // list ALL Contract properties
having count(*) = 3
Alternatively you can use a subquery to avoid listing all properties in group by:
from Contract c where c.id in (
select c.id
from Contract c join c.products p
where p.name in ('A', 'B', 'C')
group by c.id
having count(*) = 3
)
Obviously "3" will have to be replaced with the actual number of product names you supply in in clause.
In the blog I went over such hibernate queries, take a look at example #4.
Here is a snapshot (replace Articles with Contracts and Tags with Products):
String[] tags = {"Java", "Hibernate"};
String hql = "select a from Article a " +
"join a.tags t " +
"where t.name in (:tags) " +
"and a.id in (" +
"select a2.id " +
"from Article a2 " +
"join a2.tags t2 " +
"group by a2 " +
"having count(t2)=:tag_count) " +
"group by a " +
"having count(t)=:tag_count";
Query query = session.createQuery(hql);
query.setParameterList("tags", tags);
query.setInteger("tag_count", tags.length);
List<Article> articles = query.list();

Categories

Resources