I'm sure I'm being stupid but I can't seem to figure this one out...
I have two tables:
department( did, name )
employee( eid, first, last, did )
they have corresponding entities JPA managed entites Department and Employee. Employee has a Deparment field, Department doesn't maintain an Employee list. What I want to do though is find all the Departments that have no Employees. Using plain old SQL this is easy with a left join:
SELECT d.*
FROM department as d LEFT OUTER JOIN employee as e
ON d.did = e.did
WHERE e.did IS NULL
I can't see how to translate this query into JPQL though. All the examples I've found for JPQL left joins traverse the link the other way, for example.
SELECT e FROM Employee e LEFT JOIN e.departmert d
Whereas I need something more like
SELECT d FROM Department d LEFT JOIN d.???? WHERE e.department IS NULL
but the department doesn't maintain a reference to it's employees (in my application it's not departments and employees obviously). Is this even possible in JPQL?
To do what you are trying to do, you would need to setup a mapping from Departments -> Employees (using your example entities). You could used the mappedBy attribute of #OneToMany, which will most likely not disrupt your schema, e.g.,
#Entity
class Department {
...
#OneToMany(mappedBy="employee")
Collection<Employee> getEmployees() {
....
}
...
}
This would allow you to run something like:
SELECT d FROM Department d WHERE d.employees IS EMPTY
Which should give you equivalent results.
Without altering your mapping, you should be able to run something like this query to get the results you want:
SELECT d from Department d WHERE NOT EXIST (SELECT e FROM Employee e where e.department = d)
Related
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.
I have three entities: Person, Country and CountryTranslation.
Person is related to one Country and Country have many CountryTranslations.
I want my query to fetch both Country and CountryTranslations when it fetches a Person in order to avoid multiple queries.
To bring the Country along with Person I do:
List<Person> persons = (List<Person>) entityManager
.createQuery("SELECT person from Person person " +
"left join fetch person.country")
.getResultList()
This works fine, I see on hibernate it is fetching nicely and no extra queries are executed to bring Country, but to bring CountryTranslations it still execute extra queries. Then I tried:
List<Person> persons = (List<Person>) entityManager
.createQuery("SELECT person from Person person " +
"left join fetch person.country " +
"left join fetch person.country.translations")
.getResultList()
And I get the error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
What is the right way to do this fetching?
Thanks in advance
You correct this giving an alias for each relationship.
SELECT person
FROM person person
LEFT JOIN FETCH person.country country
LEFT JOIN FETCH country.translations
It's a framework's "limitation": When you use linked fetchs you should to give an alias for each relationship!
This behavior can also be explained because will be difficult to understand what these linked fetches really mean: Does the framework will fetch each relationship or only the last one? It's more simple to just tell to the framework what fetch relationship you want.
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"
)
I have defined a JPA entity "A" with a #ManyToOne relationship to another JPA entity "B".
#Entity
class A {
String name;
int sortOrder
#ManyToOne
B b;
}
This reference can be null or it can be populated. Now, I would like to query all the As and sort by B.name. Unfortunately, something like
find("order by B.name, sortOrder, name")
seems to drop any As such that (A.b = null). What is the best way to write this query?
I want all the As and I want them sorted by their B.name reference if it exists ... lumping all those with no B property together.
you need a left join.
select a from A a left join a.b b order by b.name, a.sortOder, a.name
or something like that.
There are several approaches … Sets/Comparators, HQL, Native Sql, #Sort annotations …
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.