JPQL query : Finding the largest object in a collection - java

I have two entities
Class A{
#OneToMany(mappedBy = "a", cascade = CascadeType.REMOVE)
private List<B> b;
private Integer credits;
//other fields
}
Class B{
#ManyToOne
#JoinColumn(name = "a_fk")
private A a;
private Integer reputations;
//other fields
}
Now I want select the B with the highest reputation for each A.
What I am doing currently is
"select a FROM A a ORDER BY a.credits DESC"; //maximum results set to 500
And then finding the highest B in each A.
for (A a: aList) {
bList.add(Collections.max(a.getB(),new bComparator()));
}
But this solution isn't very intuitive.
I tried writing the query below, but doesn't give the desired result.
select b FROM B b WHERE b.reputations = (SELECT MAX(b.reputations) FROM B bInner WHERE bInner.a.id = b.a.id) ORDER BY b.a.credits DESC
Any suggestions for an optimized query?? Thanks in advance.

This should be your query, I've tested it:
select distinct a,b
FROM A a JOIN a.b b
WHERE b.reputations=(SELECT MAX(b2.reputations) FROM B b2 WHERE b2.a=a)

Related

Springboot giving redundant fields with multiple tables native query

I have a native query in spring boot like this:
select
a.x,
a.y,
b.m,
b.n
from
table1 a,
table2 b
where
a.x = b.x
order by a.x
here for each entry for table1 there can be multiple matching rows in table2. I.e there is OneToMany mapping.
for this i created an object to hold the data:
#Entity
#Table(name="table1")
#SecondaryTable(name = "table2", pkJoinColumns = {#PrimaryKeyJoinColumn(name = "x", referencedColumnName = "x")})
public class TableData {
#Id
#Column(name = "x")
public String x;
#Column(name = "y")
public String y;
#Column(name = "m", table = "table2")
public String m;
#Column(name = "n", table = "table2")
public String n;
//getters and setters
When i execute the sql in my db i see the results coming fine. I.e i see two different rows with same a.x but with different b.m and b.n
But my json response has two identical rows with same a.x and same b.m and b.n
Sample data:
table1:
ManagerID|ManagerName|Role
'1','John','Manager'
table2:
ManagerID|AssociateName|AssociateId
'1','Peter','2'
'1','Jacob','3'
Expected output
'1','John','Peter','2'
'1','John','Jacob','3'
Output i am seeing right now:
'1','John','Peter','2'
'1','John','Peter','2'
Any guess where i am getting wrong ??
My problem got resolved by using the #IdClass annotation as the resulting data from the query was a join and the uniqueness can be maintained by a combination of fields !!

Sorting by relation property

Here's my working database setup:
#Entity
class Foo {
#Id
#Column(name = "ID")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "FOO_ID")
private Set<Bar> bars;
//...
}
#Entity
class Bar {
#Id
#Column(name = "ID")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(name = "STATUS")
private String status;
//...
}
FooRepository extends CrudRepository {
#Query("select distinct f from Foo f left join f.bars b where b.status = :status ")
public Page<Bar> findByBarStatus(#Param("status") BarStatus status, Pageable pageable);
}
I'd like to be able to sort this query by Bar.status, here's how I tried to change the query:
#Query("select distinct f from Foo f left join f.bars b where b.status = :status order by b.status desc")
public Set<Bar> findByBarStatus(#Param("status") BarStatus status);
However that causes sql syntax error:
org.h2.jdbc.JdbcSQLException: Order by expression "BARS1_.STATUS" must be in the result list in this case;
Here you have applied distinct on f and thus you can not have some other column in order by. Actually, order by item must be there in select list.
So, the problem is in query, you can remove distinct if you are sure that f will be unique (but that's not the case I guess) or you can try with clause,
with temp as
(select distinct f, b.status
from Foo f left join f.bars b
where b.status = :status order by b.status desc)
select f from temp
you have to call b.status in your query select statement like below query.
select DISTINCT(f), b.status from Foo f
LEFT JOIN f.bars b where b.status = :status order by b.status desc

HQL: How to query certain fields when one of those fields is a one-to-many List?

I am having difficulty writing a HQL query to select ONLY the caseid, title, and caseStatus fields from my Cases entity. The cases returned have to be distinct based on caseid. I do not want the name and userid fields to be included. I also do not want to use Lazy fetching for caseid, title, and caseStatus fields. Note that the caseStatus field is a one-to-many List. Below are the entities. The getters/setters are omitted to save space.
#Entity
#Table(name = "Cases")
public class Cases {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "caseid", nullable = false)
private Integer caseid;
private Integer userid;
private String name;
private String title;
#OrderBy("caseStatusId DESC")
#OneToMany(mappedBy = "cases", fetch = FetchType.EAGER)
private List<CaseStatus> caseStatus;
}
#Entity
#Table(name = "CaseStatus")
public class CaseStatus {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "caseStatusId", nullable = false)
private Integer caseStatusId;
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "caseid")
private Cases cases;
}
My goal is to retrieve a distinct List<Cases> or List<Object[]> of the Cases entity containing only caseid, title, and a List<CaseStatus>. The List<CaseStatus> will contain CaseStatus objects with all of its fields populated.
public List<Object[]> getCases(String title) {
TypedQuery<Object[]> q = em.createQuery("select distinct c.caseid, c.title, cs "
+ "FROM Cases c join c.caseStatus cs "
+ "where c.title like :title", Object[].class);
q.setParameter("title", "%" + title + "%");
List<Object[]> results = q.getResultList();
return results;
}
The above method is close, but not correct because rather than returning a List<CaseStatus> in one of the indexes, it is only returning a single CaseStatus entity.
For example, if my DB contains a single Case with a List<CaseStatus> having a size of n for example, the results will be similar to the example below:
Example of results I'm getting now. Not correct:
List<Object[]> index 0:
Contains an Object[] where:
Object[0] = {some caseid}
Object[1] = {some title}
Object[2] = {1st CaseStatus}
List<Object[]> index 1:
Contains an Object[] where:
Object[0] = {same caseid as the one found in index 0 above}
Object[1] = {same title as the one found in index 0 above}
Object[2] = {2nd CaseStatus}
...
List<Object[]> index n-1:
Contains an Object[] where:
Object[0] = {same caseid as all the previous}
Object[1] = {same title as all the previous}
Object[2] = {nth CaseStatus}
Example of results I hope to achieve:
List<Object[]> index 0:
Contains an Object[] where:
Object[0] = {unique caseid}
Object[1] = {some title}
Object[2] = List<CaseStatus> with size of n
Updated the question. Instead of name, title, and List<CaseStatus>, the fields I want to retrieve are caseid, title, and List<CaseStatus>. caseid is the primary key of Cases.
I found various threads Select Collections with HQL - hibernate forum and Select collections with HQL - stackoverflow. It's pretty much the problem I ran into. Looks like no one found a solution in these threads.
Hibernates a bit confused about the query; in HQL do your join like this (apologies, I've not been able to test before posting due to wonky computer, but you should get the idea)
select distinct c from Cases c left join fetch c.caseStatus cs where....
the "fetch" makes it eager. Note that this will return an array of type Cases. You where clauses look about right.
In fact HQL is fully object-oriented and uses your classes structure in the Query, so by writing c.caseStatus HQL expects that your Cases class has a caseStatus property, which is wrong because it's a collection.
If you take a look at Hibernate HQL documentation you can see that:
Compared with SQL, however, HQL is fully object-oriented and understands notions like inheritance, polymorphism and association.
I think what you need to do here is to change your query so it matches your classes structures:
Query q = em.createQuery("select distinct c.name, c.title, cs.caseStatus FROM Cases c left join c.caseStatus where "
+ "c.name like :name and "
+ "c.title like :title");
Correct syntax should be
TypedQuery<Object[]> q = em.createQuery("select c.name, c.title, cs FROM Cases c "
+ "join c.caseStatus cs where "
+ "c.name = :name and "
+ "c.title = :title", Object[].class);
Return type will be List<Object[]>, where in first index of Object[] is c.name, second is c.title and third is associated caseStatus entity. It is possible to query for multiple instances (rows).
We need JOIN because relationship between CaseStatus and Case is mapped via collection.
SELECT cs
FROM Case c JOIN c.cases cs;
Why don't you just use
Query q = em.createQuery("select distinct c from Cases c where "
+ "c.name like :name and "
+ "c.title like :title");
Just try this. This may be a naive approach but should be able to solve the problem. You may be getting more fields than you required but the return type would be list of Cases.

How to retrieve a complex class and its members using Hibernate Projection?

I have a class as following that need to retrieve from DB using Hibernate.
The problem is my class has multiple members and majority of them are classes, how can I retrieve them?
#Entity
public class Student {
#Id
long id;
String name;
String fname;
#OneToMany
List<Course> courses;
#ManyToOne
Dealer dealer;
...
}
#Entity
public class Dealer {
#Id
long id;
String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "cr.dealer", cascade = CascadeType.ALL)
Set<Car> cars = new HashSet<Cars>(0);
..
}
I need to retrieve student id 1 and all its courses, its dealer and list of dealers' cars.
My projection is as following but it does not return anything.
...
.setProjection(Projections.projectionList()
.add(Projections.property("friends.cars").as("cars")
...
// Projection is not needed, Hibernate will load child values as shown below
Student student = session.get(Student.class);
List<Course> courses = student.getCourses();
Dealer dealer = student.getDealer();
// If u want records only where child records are present, u can use LEFT_OUTER_JOIN
Criteria criteria = getHibernateSession().createCriteria(Student.class);
criteria.createAlias("Course", "Course", JoinType.LEFT_OUTER_JOIN);
// If u want to use Projections for performance, u have to add each and every column in projection
Criteria criteria = getHibernateSession().createCriteria(A.class);
criteria.createAlias("b", "b", JoinType.INNER_JOIN);
criteria.createAlias("b.r", "b.r", JoinType.INNER_JOIN);
criteria.createAlias("b.c", "b.c", JoinType.LEFT_OUTER_JOIN);
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.groupProperty("column1"));
projectionList.add(Projections.property("column2"));
projectionList.add(Projections.property("column3"));
criteria.setProjection(projectionList);
criteria.setResultTransformer(Transformers.aliasToBean(Table.class));
Because you have a List of Courses and a Set of Cars, you can simply fetch the whole graph in a single query:
select s
from Student s
left join fetch s.courses
left join fetch s.dealer d
left join fetch d.cars
where s.id = :id
Because you are fetching two collections, this query will generate a Cartesian Product, so you need to make sure that the selected children collections don't have too many entries.
If you don;t want to run into a Cartesian product, you can simply run this query:
select s
from Student s
left join fetch s.courses
left join fetch s.dealer d
where s.id = :id
and then you access the dealer.cars to fetch that collection with a separate query:
Student s = ...;
s.getDealer().getCars().size();
If high performences are not a concern, then you should let Hibernate do his work.
Just use the getters of you entities.
For exemple:
Student student1 = session.get(Student.class, 1L);
List<Course> courses = student1.getCourses();
Dealer dealer = student1.getDealer();
Set<Car> cars = dealer.getCars();
I am not sure if you can use QueryOver but it would be very easy for these kind of tasks.
Student student = null;
Dealer dealer = null;
Course course = null;
Car car = null;
var myStudent = Session.QueryOver<Student>(() => student)
.Left.JoinQueryOver(() => student.courses, () => courses)
.Left.JoinQueryOver(() => student.dealer, () => dealer)
.Left.JoinQueryOver(() => dealer.cars, () => car)
.SelectList(list => list
.Select(() => student.Name)
.Select(() => student.Age)
.Select(() => courses.Description)
.Select(() => dealer.locaiton)
.Select(() => car.Model))
.TransformUsing(Transformers.AliasToBean<StudentModel>())
.List<StudentModel>().AsQueryable();
Create a StudentModel DTO to have the results. This is just a hint to start with, you can modify this according to your requirement. I hope this will work. :)

Hibernate criteria with 2 entities table and 1 association table

I need to make a query inside a DAO using hibernate criteria, but im not sure how.
I have 2 entities, A and B, and an association table, that contains both A and B ID's.
A doesnt know B, and B doesnt know A.
I want to find all A's that are associated with a certain B, using criteria.
I made a diagram, hope it helps to explain.
Example image
As you can see, table A have 3 records, table B have 3 records too and table AssocAB 3 records as well. I want to find all A's that are associated in AssocAB with B1. The query should return A1 and A3.
is it possible?
Here's the classes, and the relationship annotated.
Entity A: No annotation
Entity B: Have a Set of Entity A
#ManyToMany
#JoinTable(name = "Assoc_AB", joinColumns = { #JoinColumn(name = "ID_B")}, inverseJoinColumn = { #JoinColumn(name = "ID_A")})
#ForeignKey(name = "FK_A_B", inverseName = "FK_B_A")
public set<A> getA(){
return this.listOfA;
}
And no class for association class, the mapping on B creates the assoc table.
Thanks in advance.
I tried to write a criteria query with your indications, but it's not easy because you tell that A and B tables have a int ID, but then you put Strings in your rows ("A1", "B1"...). I supposed that your id is a String instead of an int. You should have something like this:
List<A> listOfA = new ArrayList<A>();
Criteria criteria = session.createCriteria(B.class, "b");
criteria.add(Restrictions.eq("b.id", "B1"));
List<B> listOfBs = criteria.list();
for (B b : listOfBs) {
listOfA.addAll(b.getA());
}
If you have a name property in B class, you would have to modify the restriction:
criteria.add(Restrictions.eq("b.name", "B1"));
I hope this code can help you solve your problem.

Categories