HHH-11453 - Documentation: explain/state find() and Query on a single entity can behave differently
This commit is contained in:
parent
a38ea758e3
commit
efc041c9e6
|
@ -45,6 +45,67 @@ _dynamic_ (sometimes referred to as runtime)::
|
||||||
HQL/JPQL::: and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query.
|
HQL/JPQL::: and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query.
|
||||||
entity graphs::: Starting in Hibernate 4.2 (JPA 2.1) this is also an option.
|
entity graphs::: Starting in Hibernate 4.2 (JPA 2.1) this is also an option.
|
||||||
|
|
||||||
|
[[fetching-direct-vs-query]]
|
||||||
|
=== Direct fetching vs entity queries
|
||||||
|
|
||||||
|
To see the difference between direct fetching and entity queries in regard to eagerly fetched associations, consider the following entities:
|
||||||
|
|
||||||
|
[[fetching-direct-vs-query-domain-model-example]]
|
||||||
|
.Domain model
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DirectVsQueryFetchingTest.java[tags=fetching-direct-vs-query-domain-model-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The `Employee` entity has a `@ManyToOne` association to a `Department` which is fetched eagerly.
|
||||||
|
|
||||||
|
When issuing a direct entity fetch, Hibernate executed the following SQL query:
|
||||||
|
|
||||||
|
[[fetching-direct-vs-query-direct-fetching-example]]
|
||||||
|
.Direct fetching example
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DirectVsQueryFetchingTest.java[tags=fetching-direct-vs-query-direct-fetching-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/fetching-direct-vs-query-direct-fetching-example.sql[]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The `LEFT JOIN` clause is added to the generated SQL query because this association is required to be fetched eagerly.
|
||||||
|
|
||||||
|
On the other hand, if you are using an entity query that does not contain a `JOIN FETCH` directive to the `Department` association:
|
||||||
|
|
||||||
|
[[fetching-direct-vs-query-entity-query-example]]
|
||||||
|
.Entity query fetching example
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/DirectVsQueryFetchingTest.java[tags=fetching-direct-vs-query-entity-query-example]
|
||||||
|
----
|
||||||
|
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/fetching-direct-vs-query-entity-query-example.sql[]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Hibernate uses a secondary select instead. This is because the entity query fetch policy cannot be overridden,
|
||||||
|
so Hibernate requires a secondary select to ensure that the EAGER association is fetched prior to returning the result to the user.
|
||||||
|
|
||||||
|
[IMPORTANT]
|
||||||
|
====
|
||||||
|
If you forget to JOIN FETCH all EAGER associations, Hibernate is going to issue a secondary select for each and every one of those
|
||||||
|
which, in turn, can lean to N+1 query issues.
|
||||||
|
|
||||||
|
For this reason, you should prefer LAZY associations.
|
||||||
|
====
|
||||||
|
|
||||||
[[fetching-strategies]]
|
[[fetching-strategies]]
|
||||||
=== Applying fetch strategies
|
=== Applying fetch strategies
|
||||||
|
|
||||||
|
@ -82,7 +143,7 @@ include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-no-fetching-exam
|
||||||
====
|
====
|
||||||
|
|
||||||
In this example, the application gets the `Employee` data.
|
In this example, the application gets the `Employee` data.
|
||||||
However, because all associations from `Employee `are declared as LAZY (JPA defines the default for collections as LAZY) no other data is fetched.
|
However, because all associations from `Employee` are declared as LAZY (JPA defines the default for collections as LAZY) no other data is fetched.
|
||||||
|
|
||||||
If the login process does not need access to the `Employee` information specifically, another fetching optimization here would be to limit the width of the query results.
|
If the login process does not need access to the `Employee` information specifically, another fetching optimization here would be to limit the width of the query results.
|
||||||
|
|
||||||
|
@ -99,7 +160,7 @@ include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-no-fetching-scal
|
||||||
=== Dynamic fetching via queries
|
=== Dynamic fetching via queries
|
||||||
|
|
||||||
For the second use case, consider a screen displaying the `Projects` for an `Employee`.
|
For the second use case, consider a screen displaying the `Projects` for an `Employee`.
|
||||||
Certainly access to the `Employee `is needed, as is the collection of `Projects` for that Employee. Information about `Departments`, other `Employees` or other `Projects` is not needed.
|
Certainly access to the `Employee` is needed, as is the collection of `Projects` for that Employee. Information about `Departments`, other `Employees` or other `Projects` is not needed.
|
||||||
|
|
||||||
[[fetching-strategies-dynamic-fetching-jpql-example]]
|
[[fetching-strategies-dynamic-fetching-jpql-example]]
|
||||||
.Dynamic JPQL fetching example
|
.Dynamic JPQL fetching example
|
||||||
|
@ -119,7 +180,7 @@ include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
In this example we have an `Employee `and their `Projects` loaded in a single query shown both as an HQL query and a JPA Criteria query.
|
In this example we have an `Employee` and their `Projects` loaded in a single query shown both as an HQL query and a JPA Criteria query.
|
||||||
In both cases, this resolves to exactly one database query to get all that information.
|
In both cases, this resolves to exactly one database query to get all that information.
|
||||||
|
|
||||||
[[fetching-strategies-dynamic-fetching-entity-graph]]
|
[[fetching-strategies-dynamic-fetching-entity-graph]]
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
select
|
||||||
|
e.id as id1_1_0_,
|
||||||
|
e.department_id as departme3_1_0_,
|
||||||
|
e.username as username2_1_0_,
|
||||||
|
d.id as id1_0_1_
|
||||||
|
from
|
||||||
|
Employee e
|
||||||
|
left outer join
|
||||||
|
Department d
|
||||||
|
on e.department_id=d.id
|
||||||
|
where
|
||||||
|
e.id = 1
|
|
@ -0,0 +1,15 @@
|
||||||
|
select
|
||||||
|
e.id as id1_1_,
|
||||||
|
e.department_id as departme3_1_,
|
||||||
|
e.username as username2_1_
|
||||||
|
from
|
||||||
|
Employee e
|
||||||
|
where
|
||||||
|
e.id = 1
|
||||||
|
|
||||||
|
select
|
||||||
|
d.id as id1_0_0_
|
||||||
|
from
|
||||||
|
Department d
|
||||||
|
where
|
||||||
|
d.id = 1
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.userguide.fetching;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NaturalId;
|
||||||
|
import org.hibernate.dialect.H2Dialect;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
|
||||||
|
import org.hibernate.testing.RequiresDialect;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
@RequiresDialect(H2Dialect.class)
|
||||||
|
public class DirectVsQueryFetchingTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger( DirectVsQueryFetchingTest.class );
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Department.class,
|
||||||
|
Employee.class,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Department department = new Department();
|
||||||
|
department.id = 1L;
|
||||||
|
entityManager.persist( department );
|
||||||
|
|
||||||
|
Employee employee1 = new Employee();
|
||||||
|
employee1.id = 1L;
|
||||||
|
employee1.username = "user1";
|
||||||
|
employee1.department = department;
|
||||||
|
entityManager.persist( employee1 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::fetching-direct-vs-query-direct-fetching-example[]
|
||||||
|
Employee employee = entityManager.find( Employee.class, 1L );
|
||||||
|
//end::fetching-direct-vs-query-direct-fetching-example[]
|
||||||
|
} );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::fetching-direct-vs-query-entity-query-example[]
|
||||||
|
Employee employee = entityManager.createQuery(
|
||||||
|
"select e " +
|
||||||
|
"from Employee e " +
|
||||||
|
"where e.id = :id", Employee.class)
|
||||||
|
.setParameter( "id", 1L )
|
||||||
|
.getSingleResult();
|
||||||
|
//end::fetching-direct-vs-query-entity-query-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::fetching-direct-vs-query-domain-model-example[]
|
||||||
|
@Entity(name = "Department")
|
||||||
|
public static class Department {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::fetching-direct-vs-query-domain-model-example[]
|
||||||
|
@Entity(name = "Employee")
|
||||||
|
public static class Employee {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NaturalId
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
|
private Department department;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
}
|
||||||
|
//end::fetching-direct-vs-query-domain-model-example[]
|
||||||
|
}
|
Loading…
Reference in New Issue