Hibernate recursive query - java

My desired query is to get a list of Course objects that belong to a Category. My objects are as follows:
public class Course{
String name;
List<Category> categories;
}
public class Category{
String name;
Category parent;
}
Since the categories reference each other, they can have an infinite depth:
A
A.A
A.A.A
A.A.B
A.B
A.B.A
B
B.A
B.B
C
How can I query for courses within the category "A.A", and return all Courses associated with A.A, A.A.A, and A.A.B?

If you are willing to use native SQL and your database supports recursive common table expressions (basically all major DBMS except MySQL) it's pretty easy:
WITH RECURSIVE course_tree (name) AS (
SELECT name
FROM course
WHERE name = 'A.A'
UNION ALL
SELECT name
FROM course
WHERE parent_id = course_tree.id
)
SELECT *
FROM course_tree

Because you do not know how deep is the tree, you can use some kind of pattern as follows
select distinct
c
from
Course c
left join fetch
c.categories c
where
c.name like 'A.A%'

Related

JPQL: query entities with Many-to-Many relationship modeled using a composite key

I read this article of how to build Many-To-Many using composite key.
The entities are
class Student {
// ...
#OneToMany(mappedBy = "student")
Set<CourseRating> ratings;
// ...
}
class Course {
// ...
#OneToMany(mappedBy = "course")
Set<CourseRating> ratings;
// ...
}
#Entity
class CourseRating {
#EmbeddedId
CourseRatingKey id;
#ManyToOne
#MapsId("student_id")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("course_id")
#JoinColumn(name = "course_id")
Course course;
int rating;
// standard constructors, getters, and setters
}
#Embeddable
class CourseRatingKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
// standard constructors, getters, and setters
// hashcode and equals implementation
}
Now, let's assume, I want to get students that has specific courseId.
How will my JPQ look like then:
select s from Student s join fetch s.ratings r where r.id.courseId=:courserId
OR
select s from Student s join fetch s.ratings r where r.course.id=:courserId
or it would be totally different?
You should check the SQL carefully on this sort of JPA queries because there is probably more going on there than you realize.
You did a great job of setting up the ManyToMany relation with the attribute rating and your second query will work but could easily cause you problems and possible performance considerations.
For getting only the students the correct query could be
em.createQuery("select s from Student s join fetch s.ratings r where r.course.id = 1", Student.class).getResultList().forEach(s->System.out.println(s + ":" + s.getRatings()));
That gives the logs
Hibernate: select student0_.id as id1_2_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, ratings1_.rating as rating3_1_1_, ratings1_.student_id as student_2_1_0__, ratings1_.course_id as course_i1_1_0__ from Student student0_ inner join CourseRating ratings1_ on student0_.id=ratings1_.student_id where ratings1_.course_id=1
Hibernate: select course0_.id as id1_0_0_ from Course course0_ where course0_.id=?
Student(id=1):[CourseRating(id=model.CourseRatingKey#dd5, student=Student(id=1), course=Course(id=1), rating=11)]
Student(id=2):[CourseRating(id=model.CourseRatingKey#e10, student=Student(id=2), course=Course(id=1), rating=21)]
JPA will generate an extra query to get the course information since it was not prefetched. You can solve that by prefetching it yourself.
em.createQuery("select s from Student s join fetch s.ratings r join fetch r.course c where c.id = 1", Student.class).getResultList().forEach(s->System.out.println(s + ":" + s.getRatings()));
and that gives the logs
Hibernate: select student0_.id as id1_2_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, course2_.id as id1_0_2_, ratings1_.rating as rating3_1_1_, ratings1_.student_id as student_2_1_0__, ratings1_.course_id as course_i1_1_0__ from Student student0_ inner join CourseRating ratings1_ on student0_.id=ratings1_.student_id inner join Course course2_ on ratings1_.course_id=course2_.id where course2_.id=1
Student(id=1):[CourseRating(id=model.CourseRatingKey#dd5, student=Student(id=1), course=Course(id=1), rating=11)]
Student(id=2):[CourseRating(id=model.CourseRatingKey#e10, student=Student(id=2), course=Course(id=1), rating=21)]
There is an issue in that you have students with only a partial set of course ratings. If you attempt to update a rating for a student I think you will cause the other course ratings to be lost. Perhaps not a problem for your use case but you can also get the course with the list of students:
em.createQuery("select distinct c from Course c join fetch c.ratings r join fetch r.student where c.id = 1", Course.class).getSingleResult().getRatings().forEach(r->System.out.println(r.getStudent() + ":" + r));
And that will give the same results with a slightly different query and you can update a student's rating without affecting other courses.
Hibernate: select distinct course0_.id as id1_0_0_, ratings1_.course_id as course_i1_1_1_, ratings1_.student_id as student_2_1_1_, student2_.id as id1_2_2_, ratings1_.rating as rating3_1_1_, ratings1_.course_id as course_i1_1_0__, ratings1_.student_id as student_2_1_0__ from Course course0_ inner join CourseRating ratings1_ on course0_.id=ratings1_.course_id inner join Student student2_ on ratings1_.student_id=student2_.id where course0_.id=1
Student(id=2):CourseRating(id=model.CourseRatingKey#e10, student=Student(id=2), course=Course(id=1), rating=21)
Student(id=1):CourseRating(id=model.CourseRatingKey#dd5, student=Student(id=1), course=Course(id=1), rating=11)
This can also effect the JSON formatting if you are creating a REST service. In the first instance you have an multiple arrays of CourseRatings with one entry and in the second instance you have just have one array of CourseRatings with rating and student entries. Basically a partial list of Students each with a partial list of Courses is not an accurate representation of your database where as a Course with a complete list of its Students is. I'm not sure at this point which one is more efficient on the SQL server but there should be a lot less Courses than Students so if you want all the students for a Course it is probably better this way.
With JPA you should check the SQL and test your use cases carefully. Since there are lots of students and several courses per student the performance or memory overhead could be worth considering. Note also that the results can get even stranger if you were not using fetch in your queries to begin with.
This is the correct version:
SELECT s
FROM Student s
JOIN FETCH s.ratings r WHERE r.course.id = :courserId
You can put Student object inside of your embeddable id as well, in case you don't need its id. That way it will be easier to read your code, and you won't have to make #Column(name = "student_id") Long studentId to be non-insertable and non-updatable.

JPA support for outer joins without object relational mapping

Recently i had to use an outer join between two JPA entities that doesnt have object relational mapping. Going by the spec and the forum posts, outer joins are supported only if the entities are mapped at JPA level.
Example code below. Requirement is to find customers without any orders.
#Entity
class Customer {
private int id;
}
#Entity
class Order {
private int customerId;
public int getCustomerId() { return customerId; }
public void setCustomerId(int customerId) { this.customerId = customerId ; }
}
In my case, i had to opt for the native queries to get the job done.
Any thoughts on if future JPA specs going to support outer joins without relational mapping ?
Thanks
Rakesh
You can use a theta-style join to emulate an INNER JOIN:
select c, o
from Customer c, Order o
where c.id= o.customerId
Most modern database engine query optimizers will turn it into an INNER JOIN equivalent anyway.
Suppose you have customerId field (Integer) in the Order entity. In order to find customers without any orders, you can avoid outer join and native query by using subquery:
select c from Customer c where id not in (select customerId from Order)
This way you achieve the same goal in JPQL (HQL).

Hibernate Query Criteria for mappings involving inheritance

Lets say that class 'X' is mapped to table 'X' class 'A' is mapped to Table 'A' and Class 'B is mapped to table 'B'.
Table X Structure:(X_ID, some other columns
Table A Structure:(A_Id,X_Id, some other columns)
Table B Structure:(A_Id, some other columns)...Table B also has A_Id
Class 'B' extends class 'A'. We have the mapping files for both of them as:
Class 'A' Parent Mapping file:
#Entity
#Table(name = 'A')
#Inheritance(stratergy=InheritanceType.Joined)
public abstract class A {
#Id #Clumns(name = "A_Id)
#GeneratedValue
protected Long aId;
-- some more A specific fields
}
Class 'B' Mapping file:
#Entity
#Table(name= 'B')
Public class B extends A{
---- B specific fields
}
Now, I have a SQL Query as below that I need to write using hibernate criteria API.
select * from X
INNER JOIN A
ON X.id = A.id
INNER JOIN B
ON A.id = B.id
where B.name = 'XYZ'
and B.Sex = 'M'
I have come up with:
Criteria c = session.createCriteria(x.class, "x");
.createAlias("x.a", "a")
.createAlias("a.b", "b")
.add(Restrictions.eq("b.sex", "M"))
.add(Restrictions.eq("b.name", "XYZ"));
But, if we check the mapping file, there is no direct reference of B in A. Hence hibernate throws out "B not related to A" entity.
Is there any way this inheritance can be mapped in query crteria
You shouldn't need to reference A at all in your criteria, or use any aliases.
Criteria c = session.createCriteria(B.class);
.add(Restrictions.eq("sex", "M"))
.add(Restrictions.eq("name", "XYZ"));
will give you the result you need.
Because of the InheritanceType.Joined, this will probably produce SQL that includes a join to the the A table (something close to the sql you show), but it isn't necessary to specify that join in the criteria.
The things that look like columns in the criteria are actually (reflective) references to fields in your Java objects. Hibernate figures out the columns to put in the sql from your annotations, and should the join to the A table if it's needed based on the inheritance annotation.
To be sure of this in your context, and to understand all this a bit better, I'd advise trying it and turning on logging of the generated sql as described in this answer to another SO hibernate question.
Try this way:
Criteria rootCrit = session.createCriteria(A.class);
rootCrit.createAlias("B", "B");
rootCrit.add(Restrictions.eq("B.sex", "M"));
rootCrit.add(Restrictions.eq("B.name", "XYZ"));

HQL to get elements that possess all items in a set

Currently, I have an HQL query that returns all Members who possess ANY Award from a set of specified Awards:
from Member m left join m.awards as a where a.name in ("Trophy","Ribbon");
What I now need is HQL that will return all Members who possess ALL Awards specified in the set of Awards.
So, assuming this data:
Joe has Trophy, Medal
Sue has Trophy, Ribbon
Tom has Trophy, Ribbon, Medal
The query above would return Joe, Sue, and Tom because all three possess at least one of Trophy or Ribbon. But I need to return only Sue and Tom, because they are the only ones who possess all of the specified awards (Trophy and Ribbon).
Here's the class structure (simplified):
class Member {
private String name;
private Set<Award> awards;
}
class Award {
private String name;
}
select m from Member m left join m.awards as a where a.name in ("Trophy","Ribbon") group by m having count(a)=2
Just repeating myself...
The code to get the members that have EXACTLY the given collection of awards:
from Member m
where not exists (
from Award a where a.name in {"Trophy", "Ribbon"}
and a not in(
select * from Award a2 where a2.owner = m
)
) and not exists (
from Award a3 where a3.owner = m and a3 not in {"Trophy", "Ribbon"}
)
You can force distinct results by adding a DISTINCT_ROOT_ENTITY result transformer to the query call, IE:
getSession().createQuery(hql).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
I'm having a similar problem, but what I need to do is (following your example) to select all the members, who posess ALL of the awards and no more. So in your example the only correct result would be Sue. Any ideas?

Java JPA Inner JOIN with WHERE statement

I want to annotate following structure:
I have this query:
SELECT A.*, BES.*, BES_2.*
INNER JOIN BES ON A.a = BES.a AND A.b = BES.b
INNER JOIN BES AS BES_2 ON A.a = BES_2.a AND A.b = BES_2.b
WHERE (BES.c = N'foo') AND (BES_2.c = N'bar')
I have the entities Job (representing A) and JobEndPoint (representing BES). The Job object should contain two JobEndPoint which map like a one-to-one relation. I need two JOIN the table two times checking for the same values only differed by the column "c" which I check in the WHERE statement.
#OneToOne
private JobEndPoint from;
#OneToOne
private JobEndPoint to;
My problem is now that the database columns and the object fields differ a lot and I don't know how to add the WHERE statement.
Create a JPA repository, and type a custom #Query.
I assume you already linked the parent and JobEndPoint classes over a and b fields. (To do that, define a multiple-column id on JobEndPoint and specify joinColumns in the parent class.)
#Query("SELECT u FROM parent
LEFT JOIN u.from bes
LEFT JOIN u.to bes2
WHERE bes.c = 'foo'
AND bes2.c = 'bar'")
Set<Parent> findMatchingParents()

Categories

Resources