From efc041c9e6e52a521a64b84d9387e99715c7b964 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 6 Feb 2017 11:42:27 +0200 Subject: [PATCH] HHH-11453 - Documentation: explain/state find() and Query on a single entity can behave differently --- .../userguide/chapters/fetching/Fetching.adoc | 67 +++++++++++- ...irect-vs-query-direct-fetching-example.sql | 12 +++ ...g-direct-vs-query-entity-query-example.sql | 15 +++ .../fetching/DirectVsQueryFetchingTest.java | 100 ++++++++++++++++++ 4 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-direct-fetching-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-entity-query-example.sql create mode 100644 documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index 23bb59ceef..25526e23c6 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc @@ -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]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-direct-fetching-example.sql b/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-direct-fetching-example.sql new file mode 100644 index 0000000000..82c70cbfe8 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-direct-fetching-example.sql @@ -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 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-entity-query-example.sql new file mode 100644 index 0000000000..713da63f01 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/extras/fetching-direct-vs-query-entity-query-example.sql @@ -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 \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java new file mode 100644 index 0000000000..994745cf7a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/DirectVsQueryFetchingTest.java @@ -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 . + */ +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[] +}