HHH-11453 - Documentation: explain/state find() and Query on a single entity can behave differently

This commit is contained in:
Vlad Mihalcea 2017-02-06 11:42:27 +02:00
parent a38ea758e3
commit efc041c9e6
4 changed files with 191 additions and 3 deletions

View File

@ -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.
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]]
=== 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.
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.
@ -99,7 +160,7 @@ include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-no-fetching-scal
=== Dynamic fetching via queries
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]]
.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.
[[fetching-strategies-dynamic-fetching-entity-graph]]

View File

@ -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

View File

@ -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

View File

@ -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[]
}