JPA join by one value or another - java

I have an example database as a simplification of a real life production environment. In this case, there's a student table (id, name, teacher) and a teacher table (id, name, badge_code). The teacher can be identified by their badge_code or by name, and there is a many-to-one relationship between these two tables, where a teacher corresponds to many students.
The thing is that the student table has its teacher column, and this column may have either the teacher's name or badge code. If I want to successfully join these tables, a query like this would be enough:
select * from student s
left join teacher t on s.teacher = t.badge_code or s.teacher = t.name
If the first join condition fails, it falls back to the second possible match. This is a left join, so it can be null cases.
I want to replicate this join in JPA and I don't know how to do it. It doesn't matter if it's by annotations, JPQL or a criteria query; any option will be fine. I know this kind of reference is not ideal between tables, but unfortunately, I'm unable to change how data is related in this case.

I solved this issue by using #JoinFormula annotation from Hibernate, so my student entity looks like this:
#Entity
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne
#JoinFormula("(select t.id from teacher t where t.badge_code = teacher or t.name = teacher)")
private Teacher teacher;
...
It looks like a native SQL query between parentheses that returns the target entity id, is enough to join given two or more conditions with the same column, like in this case.

Related

JPA & Hibernate: Eager loading performing subsequent queries to fetch all data, instead of doing it in just one query

I have the following doubt. I would like to know why when using JPA and Hibernate, when performing an Eager loading in a ManyToOne or OneToMany relationship, it calls the DB in order to obtain the Entity information but additionally, produces subsequent queries to fetch every child.
On the other side, when using a query with JOIN FETCH it performs the query as I would expect it to be, taking the information all at once, since the fetchType is denoted as "EAGER".
Here it is a simple example:
I have the Class Student which has ManyToOne relationship with the Class Classroom.
#Entity
#Table(name = "STUDENT")
public class Student {
#ManyToOne(optional = true, fetch = FetchType.EAGER)
#JoinColumn(name = "ClassroomID")
private Classroom mainClass;
In the other side there is the class named Classroom as follows:
#Entity
public class Classroom {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mainClass", fetch = FetchType.EAGER)
private List<Student> studentsList;
When obtaining the Classroom object, it performs one query to obtain the information from itself and subsequent queries to obtain the information of each student contained in the studentsList for every classRoom object.
First query:
Hibernate:
/* SELECT
r
FROM
Classroom r
LEFT JOIN
r.classStudents */
select
classroom0_.id as id1_0_,
classroom0_.number as number2_0_
from
Classroom classroom0_
left outer join
STUDENT classstude1_
on classroom0_.id=classstude1_.ClassroomID
And then it performs the next query as many times as Students assigned to each classroom.
Hibernate:
/* load one-to-many com.hw.access.Classroom.classStudents */
select
classstude0_.ClassroomID as Classroo4_0_1_,
classstude0_.id as id1_1_1_,
classstude0_.id as id1_1_0_,
classstude0_.FIRST_NAME as FIRST_NA2_1_0_,
classstude0_.LAST_NAME as LAST_NAM3_1_0_,
classstude0_.ClassroomID as Classroo4_1_0_
from
STUDENT classstude0_
where
classstude0_.ClassroomID=?
The question is: Why doesn't it takes the Information all at once? Why doesn't it takes the information in just one query? As it is already performing the Join clause there.
Why just when adding Fetch explicitly in the query, it does what it is requested?
For example:
SELECT
r
FROM
Classroom r
LEFT JOIN FETCH
r.classStudents */
And then, the output query does takes all the information in just one query:
Hibernate:
select
classroom0_.id as id1_0_0_,
classstude1_.id as id1_1_1_,
classroom0_.number as number2_0_0_,
classstude1_.FIRST_NAME as FIRST_NA2_1_1_,
classstude1_.LAST_NAME as LAST_NAM3_1_1_,
classstude1_.ClassroomID as Classroo4_1_1_,
classstude1_.ClassroomID as Classroo4_0_0__,
classstude1_.id as id1_1_0__
from
Classroom classroom0_
left outer join
STUDENT classstude1_
on classroom0_.id=classstude1_.ClassroomID
As you have a OneToMany relationship from Classroom to Student, using a single query would cause the Classroom fields to be repeated for each line.
Now imagine you have a second OneToMany relationship from Classroom to, say Course; if, for a given Classroom you have N Students and M Courses, you would have a query returning N+M rows, each containing the same fields of class Classroom.
I found it described in https://vladmihalcea.com/eager-fetching-is-a-code-smell/
under EAGER fetching inconsistencies:
Both JPQL and Criteria queries default to select fetching, therefore issuing a secondary select for each individual EAGER association. The larger the associations’ number, the more additional individual SELECTS, the more it will affect our application performance.
Also, note that Hibernate similarly ignoreg fetching annotations for HQL queries:
https://developer.jboss.org/wiki/HibernateFAQ-AdvancedProblems#jive_content_id_Hibernate_ignores_my_outerjointrue_or_fetchjoin_setting_and_fetches_an_association_lazily_using_n1_selects
Hibernate ignores my outer-join="true" or fetch="join" setting and fetches an association lazily, using n+1 selects!
HQL queries always ignore the setting for outer-join or fetch="join" defined in mapping metadata. This setting applies only to associations fetched using get() or load(), Criteria queries, and graph navigation. If you need to enable eager fetching for a HQL query, use an explicit LEFT JOIN FETCH.
By default fetchtype is lazy, that mean if you dont ask for the List in your request Hibernate will not collect it.
In first request you ask for all attribute of Classroom r including the Student list so Hibernate will load them lazily (after finding out that you need them).
But when fetchtype is set to eager hibernate collect it even if you dont ask it.

JPA Query with 2 inner joins and a sort based on either one of the joined objects

I'm creating a JPA query where I want to sort on an email address. The table I'm querying is a Member table. This Member can EITHER point at an Account OR an Invite. Whether one of those associations is filled can be seen by the MemberStatus enumeration.
#Entity
public class Member {
#JoinColumn #ManyToOne private Account account;
#JoinColumn #OneToOne private Invite invite;
#Enumerated private MemberStatus status; //value can be INVITED or JOINED
}
So BOTH Account and Invite contain a String field called emailAddress. For the intents of this question, consider them to look like this:
#Entity
public class Account/Invite {
private String emailAddress;
}
I want to retrieve all members, left join on Account and Invite and sort on emailAddress. If I write a query like this:
#NamedQuery(
name="findMembers",
query="select m from Member m
left join m.invite i
left join m.account a
order by emailAddress asc"
)
Then I get an exception saying:
org.postgresql.util.PSQLException: ERROR: column "emailaddress" does
not exist Hint: Perhaps you meant to reference the column
"invite1_.email_address" or the column "account2_.email_address".
Which makes sense of course. But is there a way to add some alias to this emailAddress field depending on which left joined table is present? Is this even possible in SQL, let alone JPA? From a database perspective I'm not sure how it would work.
Btw, I do not want to go in the direction of database inheritance where both these referenced entities have the emailAddress field. That has too many downsides compared to the benefit.
You could use SQLs coalesce function which should be supported in JPA>=2.0.
e.g.
#NamedQuery(
name="findMembers",
query="select m from Member m
left join m.invite i
left join m.account a
order by coalesce(i.emailAddress, a.emailAddress) asc"
)

JPA join tables through query

I have strange problem. I want to create database like this:
One student can has a lot of subjects. Student has one evaluation for one subject. So I have
class student with ID, Name, Surname and A_I id, like:
#Id
#GeneratedValue
long id_student;
In subject class I have:
#Id
#GeneratedValue
long id_subject;
String name;
double graduate
I have third class, named StudentWithGraduate:
#Id
#GeneratedValue
long id;
double evaluation;
#OneToOne
Student student;
#OneToOne
Subject subject;
I think I could do it better, but I don't know how. But it isn't a main problem. This, what I write up is working, but I want to do some joins in query like:
Vector<Object[]> v = (Vector<Object[]>) em.createQuery(
"select p.name, o.graduate from Student s
left join StudentWithGraduate o on s.id_student=o.student
left join Subject p on p.id_subject=o.subject where
s.surname='"+name+"'").getResultList();
And it throw an error:
Exception Description: Object comparisons can only be used with OneToOneMappings. Other mapping comparisons must be done through query keys or direct attribute level comparisons.
How can I change this DB scheme or change that query?
Sorry for my english.
PS. When I was doing research I found #joinTables, but I don't know how to use it..
You need a short introduction in JPQL, but I will try to quickly explain some missing parts:
In a JPQL query you do not write which are the JOIN conditions (i.e the ON expresions), and instead you navigate through your Entity Graph, beginning with an entity (below I begin with the StudentWithGraduate entity):
SELECT p.name, o.graduate FROM StudentWithGraduate o
LEFT JOIN o.student s
LEFT JOIN o.subject p
WHERE s.surname=:name
The ":name" is called a named parameter, which helps you agains SQL injections. In order to set a value for it, you write the following:
Query query = em.createQuery(aboveQuery);
query.setParameter("name", parameterValue);
//....the rest of parameters + getResultList();

Querying an entity with filters on a OneToMany attribute gives a weird result

I use EclipseLink for 9 months and so far no problem. Since I have the
need to query an entity with a OneToMany attribute, it's all the contrary.
It gives me a strange result.
I have simplified my entities until the maximum but the problem remains.
I will explain my need which is ultra simple : I have two entities :
Person which has a bidirectional relation with Address.
Person has potentially several Addresses but an Address belongs to one and
only Person.
In Classes, it gives that :
#Entity
public class Person implements Serializable {
#Id
private Long id;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
// Getter and setter
...
}
#Entity
public class Address implements Serializable {
#Id
private String idAddress;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "idPerson", referencedColumnName = "idPerson")
private Person person;
// Getter and setter
...
}
I want to query personne with their adresses. All that with some conditions
on personne and adresse.
My simplified query :
select pers FROM Person pers join pers.addresses address
where pers.matricule=:matricule
and address.date=:dateContract
When I execute it, I retrieve the right person but with all addresses
linked (with foreign key) with this person. Even the addresses which don't
match with the dateContract condition.
It seems that it's a problem related to the use of filtering on a oneToMany
attribute in my query. The problem is solved if i do several requests but
it will give low performances as I have several requests like this.
I have tried with the oneToMany in eager initialization and with a
fetch-join query hint but i have got the same result.
Thank you for having read me :)
PS : I have written the code manually, so a little typo is not impossible
David
Your query only returns persons. Once you get the persons, you're calling getAddresses(), which lazily loads the addresses of the person - all of them. In short, the query limits the set of returned persons, but since it only returns persons, the addresses are lazy-loaded using another query when accessing the set of addresses.
What you want to do is return the persons with some of their addresses in a single query. To do that, you need to use the fetch keyword:
select distinct pers FROM Person pers
join fetch pers.addresses address
where pers.matricule = :matricule
and address.date = :dateContract
Be very careful, though: this query returns an incorrect view of the person entity. You should make sure not to modify the collection of addresses of the returned persons (although since the addresses association is mapped by the Address.person association and there is no cascade, you should not have problems in this particular case).

How do I left join tables in unidirectional many-to-one in Hibernate?

I'm piggy-backing off of How to join tables in unidirectional many-to-one condition?.
If you have two classes:
class A {
#Id
public Long id;
}
class B {
#Id
public Long id;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id")
public A parent;
}
B -> A is a many to one relationship. I understand that I could add a Collection of Bs to A however I do not want that association.
So my actual question is, Is there an HQL or Criteria way of creating the SQL query:
select * from A left join B on (b.parent_id = a.id)
This will retrieve all A records with a Cartesian product of each B record that references A and will include A records that have no B referencing them.
If you use:
from A a, B b where b.a = a
then it is an inner join and you do not receive the A records that do not have a B referencing them.
I have not found a good way of doing this without two queries so anything less than that would be great.
Thanks.
I've made an example with what you posted and I think this may work:
select a,b from B as b left outer join b.parent as a in HQL.
I have to find a "criteria" way of doing that though.
You may do so by specifying the fetch attribute.
(10) fetch (optional) Choose between outer-join fetching and fetching by sequential select.
You find it at: Chapter 6. Collection Mapping, scroll down to: 6.2. Mapping a Collection
EDIT
I read in your question's comment that you wanted a way to perform a raw SQL query? Here a reference that might possibly be of interest:
Chapter 13 - Native SQL Queries
and if you want a way to make it possible through HQL:
Chapter 11. HQL: The Hibernate Query Language
In chapter 11, you want to scroll down to 11.3. Associations and joins.
IQuery q = session.CreateQuery(#"from A as ClassA left join B as ClassB");
I guess however that ClassB needs to be a member of ClassA. Further reasdings shall help.
Another thing that might proove to be useful to you are named queries:
<query name="PeopleByName">
from Person p
where p.Name like :name
</query>
And calling this query from within code like so:
using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction()) {
session.GetNamedQuery("PeopleByName")
.SetParameter("name", "ayende")
.List();
tx.Commit();
}
Please take a look at the referenced link by Ayende who explains it more in depth.

Categories

Resources