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();
Related
I have #OneToMany association between 2 entities (Entity1 To Entity2).
My sqlQueryString consists of next steps:
select ent1.*, ent2.differ_field from Entity1 as ent1 left outer join Entity2 as ent2 on ent1.item_id = ent2.item_id
Adding some subqueries and writing results to some_field2, some_field3 etc.
Execute:
Query sqlQuery = getCurrentSession().createSQLQuery(sqlQueryString)
.setResultTransformer(Transformers.aliasToBean(SomeDto.class));
List list = sqlQuery.list();
and
class SomeDto {
item_id;
some_filed1;
...
differ_field;
...
}
So the result is the List<SomeDto>
Fields which are highlighted with grey are the same.
So what I want is to group by, for example, item_id and
the List<Object> differFieldList would be as aggregation result.
class SomeDto {
...fields...
List<Object> differFieldList;
}
or something like that Map<SomeDto, List<Object>>
I can map it manually but there is a trouble:
When I use sqlQuery.setFirstResult(offset).setMaxResults(limit)
I retrieve limit count of records. But there are redundant rows. After merge I have less count actually.
Thanks in advance!
If you would like to store the query results in a collection of this class:
class SomeDto {
...fields...
List<Object> differFieldList;
}
When using sqlQuery.setFirstResult(offset).setMaxResults(n), the number of records being limited is based on the joined result set. After merging the number of records could be less than expected, and the data in List could also be incomplete.
To get the expected data set, the query needs to be broken down into two.
In first query you simply select data from Entity1
select * from Entity1
Query.setFirstResult(offset).setMaxResults(n) can be used here to limit the records you want to return. If fields from Entity2 needs to be used as condition in this query, you may use exists subquery to join to Entity2 and filter by Entity2 fields.
Once data is returned from the query, you can extract item_id and put them into a collection, and use the collection to query Entity 2:
select item_id, differ_field from Entity2 where item_id in (:itemid)
Query.setParameterList() can be used to set the item id collection returned from first query to the second query. Then you will need to manually map data returned from query 2 to data returned from query 1.
This seems verbose. If JPA #OneToMany mapping is configured between the 2 entity objects, and your query can be written in HQL (you said not possible in comment), you may let Hibernate lazy load Entity2 collection for you automatically, in which case the code can be much cleaner, but behind the scenes Hibernate may generate more query requests to DB while lazy loading the entity sitting at Many side.
The duplicated records are natural from a relational database perspective. To group projection according to Object Oriented principles, you can use a utility like this one:
public void visit(T object, EntityContext entityContext) {
Class<T> clazz = (Class<T>) object.getClass();
ClassId<T> objectClassId = new ClassId<T>(clazz, object.getId());
boolean objectVisited = entityContext.isVisited(objectClassId);
if (!objectVisited) {
entityContext.visit(objectClassId, object);
}
P parent = getParent(object);
if (parent != null) {
Class<P> parentClass = (Class<P>) parent.getClass();
ClassId<P> parentClassId = new ClassId<P>(parentClass, parent.getId());
if (!entityContext.isVisited(parentClassId)) {
setChildren(parent);
}
List<T> children = getChildren(parent);
if (!objectVisited) {
children.add(object);
}
}
}
The code is available on GitHub.
I have two entities Customer and Order in a one-to-many relation.
For each customer I need to count the number of associated orders and sort the results by this number.
In a native postgres query it looks like this:
select cust.id, count(order.id) from customers cust
left outer join orders order
on cust.id = order.customer_id
where .... conditions ...
group by cust.id
order by count desc;
But I must do this using CriteriaBuilder because this query is part of a larger piece of code that uses CriteriaBuilder to put in additional conditions. In Hibernate I would have probably used Projections, but I can't find anything similar in JPA.
Any help in composing the query using CriteraBuilder would be much appreciated.
Thank you in advance.
Supposing that the entity Customer has a OneToMany property like this:
#OneToMany(mappedBy = "customerId")
private Collection<Order> orders;
You can use the following query:
EntityManager em; // to be built or injected
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Customer> customer = cq.from(Customer.class);
CollectionJoin<Customer, Order> orders = customer.join(Customer_.orders, JoinType.LEFT);
cq.select(cb.tuple(customer, cb.count(orders)));
cq.where(... add some predicates here ...);
cq.groupBy(customer.get(Customer_.id));
cq.orderBy(cb.desc(cb.count(orders)));
List<Tuple> result = em.createQuery(cq).getResultList();
for (Tuple t : result) {
Customer c = (Customer) t.get(0);
Long cnt = (Long) t.get(1);
System.out.println("Customer " + c.getName() + " has " + cnt + " orders");
}
The above approach uses Metamodel. If you don't like it, you can replace Customer_.orders with "orders" and Customer_.id with "id".
If the OneToMany property is of another type, replace CollectionJoin with the collection of the proper type (ListJoin, SetJoin, MapJoin).
Use this inside the specification
cq.orderBy(cb.desc(cb.count(orders)));
Also send PageRequest(1, 10, Sort.unsorted()). This is how I did it.
If you are passing the Sort value as unsorted and then override criteria query with your own logic of sorting on your joined entity
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!
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);
I have the Vehicles class and mapping file for it and i want to get all rows from vehicles table ordered by ID desc (I also need the same for my other tables).
I got the following code:
session = HibernateUtil.getSessionFactory().getCurrentSession();
tx = session.beginTransaction();
q = session.createQuery("from Vehicles order by ID DESC");
for (Iterator it=q.iterate(); it.hasNext();){
//some logic
}
But my set isn't ordered by ID and each time it has a different order like RAND() or something. I was wondering what is the easiest way to keep the functionality and just to add order by clause because I have the same syntax on many places...
Try after "q = session.createQuery(...);" part:
List results = q.list()
//loop through results
There is probably something wrong elsewhere, because "sort by id desc" part is correct. Check your database/mapping files if you have correct data types and if indexes are set properly.
I'm assuming your vehicles class looks like this? I'm using JPA here because thats what I know...
class Vehicles {
#Id
#Column(name="vehicles_id")
private int id;
// other stuff here
}
I don't expect your session.createQuery to be different from mine so wouldn't something like this work?
Query q = session.createQuery("select v from Vehicles v order by v.id desc");
Also you could use criteria if you wanted yeah?
class Main {
List<Vehicles> cars;
}
Criteria main = session.createCriteria(Main.class);
Criteria secondary = main.createCriteria("cars");
secondary.addOrder(Order.asc("id"));
Have you tried "from Vehicles v order by v.id desc"? Also another option is to add the comparable interface to the entity and then bring the list created by the query into a sortedset. That's typically what I do when I need to sort.