HHH-11186 - Add examples for all Hibernate annotations

Document more annotations:

- @Fetch along with SELECT, SUBSELECT, and JOIN FetchMode
This commit is contained in:
Vlad Mihalcea 2016-11-17 17:57:24 +02:00
parent de346827d0
commit cc1730fb01
8 changed files with 592 additions and 1 deletions

View File

@ -749,6 +749,8 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Fetch.html[`@Fetch`] annotation is used to specify the Hibernate specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/FetchMode.html[`FetchMode`] (e.g. `JOIN`, `SELECT`, `SUBSELECT`) used for the current annotated association:
See the <<chapters/fetching/Fetching.adoc#fetching-fetch-annotation, `@Fetch` mapping>> section for more info.
[[annotations-hibernate-fetchprofile]]
==== `@FetchProfile`

View File

@ -217,4 +217,138 @@ instead of 2 SQL statements, there would be 10 queries needed for fetching the `
However, although `@BatchSize` is better than running into an N+1 query issue,
most of the time, a DTO projection or a `JOIN FETCH` is a much better alternative since
it allows you to fetch all the required data with a single query.
====
====
[[fetching-fetch-annotation]]
=== The `@Fetch` annotation mapping
Besides the `FetchType.LAZY` or `FetchType.EAGER` JPA annotations,
you can also use the Hibernate-specific `@Fetch` annotation that accepts one of the following `FetchMode`s:
SELECT::
Use a secondary select for each individual entity, collection, or join load.
JOIN::
Use an outer join to load the related entities, collections or joins.
SUBSELECT::
Available for collections only.
When accessing a non-initialized collection, this fetch mode will trigger loading all elements of all collections of the same role
for all owners associated with the persistence context using a single secondary select.
[[fetching-fetchmode-select]]
=== `FetchMode.SELECT`
To demonstrate how `FetchMode.SELECT` works, consider the following entity mapping:
[[fetching-strategies-fetch-mode-select-mapping-example]]
.`FetchMode.SELECT` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchModeSelectTest.java[tags=fetching-strategies-fetch-mode-select-mapping-example]
----
====
Considering there are multiple `Department` entities, each one having multiple `Employee` entities,
when executing the following test case, Hibernate fetches every uninitialized `Employee`
collection using a secondary `SELECT` statement upon accessing the child collection for the first time:
[[fetching-strategies-fetch-mode-select-example]]
.`FetchMode.SELECT` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchModeSelectTest.java[tags=fetching-strategies-fetch-mode-select-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/fetching-strategies-fetch-mode-select-example.sql[]
----
====
The more `Department` entities are fetched by the first query, the more secondary `SELECT` statements are executed to initialize the `employees` collections.
Therefore, `FetchMode.SELECT` can lead to N+1 query issues.
[[fetching-fetchmode-subselect]]
=== `FetchMode.SUBSELECT`
To demonstrate how `FetchMode.SUBSELECT` works, we are going to modify the <<fetching-strategies-fetch-mode-select-mapping-example>> to use
`FetchMode.SUBSELECT`:
[[fetching-strategies-fetch-mode-subselect-mapping-example]]
.`FetchMode.SUBSELECT` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchModeSubselectTest.java[tags=fetching-strategies-fetch-mode-subselect-mapping-example]
----
====
Now, we are going to fetch all `Department` entities that match a given filtering criteria
and then navigate their `employees` collections.
Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all `employees` collections
for all `Department` entities that were previously fetched.
Instead of using passing all entity identifiers, Hibernate simply reruns the previous query that fetched the `Department` entities.
[[fetching-strategies-fetch-mode-subselect-example]]
.`FetchMode.SUBSELECT` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchModeSubselectTest.java[tags=fetching-strategies-fetch-mode-subselect-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/fetching-strategies-fetch-mode-subselect-example.sql[]
----
====
[[fetching-fetchmode-join]]
=== `FetchMode.JOIN`
To demonstrate how `FetchMode.JOIN` works, we are going to modify the <<fetching-strategies-fetch-mode-select-mapping-example>> to use
`FetchMode.JOIN` instead:
[[fetching-strategies-fetch-mode-join-mapping-example]]
.`FetchMode.JOIN` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchModeJoinTest.java[tags=fetching-strategies-fetch-mode-join-mapping-example]
----
====
Now, we are going to fetch one `Department` and navigate its `employees` collections.
[NOTE]
====
The reason why we are not using a JPQL query to fetch multiple `Department` entities is because
the `FetchMode.JOIN` strategy would be overridden by the query fetching directive.
To fetch multiple relationships with a JPQL query, the `JOIN FETCH` directive must be used instead.
Therefore, `FetchMode.JOIN` is useful for when entities are fetched directly, via their identifier or natural-id.
Also, the `FetchMode.JOIN` acts as a `FetchType.EAGER` strategy.
Even if we mark the association as `FetchType.LAZY`, the `FetchMode.JOIN` will load the association eagerly.
====
Hibernate is going to avoid the secondary query by issuing an OUTER JOIN for the `employees` collection.
[[fetching-strategies-fetch-mode-join-example]]
.`FetchMode.JOIN` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchModeJoinTest.java[tags=fetching-strategies-fetch-mode-join-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/fetching-strategies-fetch-mode-join-example.sql[]
----
====
This time, there was no secondary query because the child collection was loaded along with the parent entity.

View File

@ -0,0 +1,16 @@
SELECT
d.id as id1_0_0_,
e.department_id as departme3_1_1_,
e.id as id1_1_1_,
e.id as id1_1_2_,
e.department_id as departme3_1_2_,
e.username as username2_1_2_
FROM
Department d
LEFT OUTER JOIN
Employee e
on d.id = e.department_id
WHERE
d.id = 1
-- Fetched department: 1

View File

@ -0,0 +1,28 @@
SELECT
d.id as id1_0_
FROM
Department d
-- Fetched 2 Departments
SELECT
e.department_id as departme3_1_0_,
e.id as id1_1_0_,
e.id as id1_1_1_,
e.department_id as departme3_1_1_,
e.username as username2_1_1_
FROM
Employee e
WHERE
e.department_id = 1
SELECT
e.department_id as departme3_1_0_,
e.id as id1_1_0_,
e.id as id1_1_1_,
e.department_id as departme3_1_1_,
e.username as username2_1_1_
FROM
Employee e
WHERE
e.department_id = 2

View File

@ -0,0 +1,26 @@
SELECT
d.id as id1_0_
FROM
Department d
where
d.name like 'Department%'
-- Fetched 2 Departments
SELECT
e.department_id as departme3_1_1_,
e.id as id1_1_1_,
e.id as id1_1_0_,
e.department_id as departme3_1_0_,
e.username as username2_1_0_
FROM
Employee e
WHERE
e.department_id in (
SELECT
fetchmodes0_.id
FROM
Department fetchmodes0_
WHERE
d.name like 'Department%'
)

View File

@ -0,0 +1,120 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.NaturalId;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public class FetchModeJoinTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( FetchModeJoinTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class,
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( long i = 0; i < 2; i++ ) {
Department department = new Department();
department.id = i + 1;
entityManager.persist( department );
for ( long j = 0; j < 3; j++ ) {
Employee employee1 = new Employee();
employee1.username = String.format( "user %d_%d", i, j );
employee1.department = department;
entityManager.persist( employee1 );
}
}
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::fetching-strategies-fetch-mode-join-example[]
Department department = entityManager.find( Department.class, 1L );
log.infof( "Fetched department: %s", department.getId());
assertEquals( 3, department.getEmployees().size() );
//end::fetching-strategies-fetch-mode-join-example[]
} );
}
@Entity(name = "Department")
public static class Department {
@Id
private Long id;
//tag::fetching-strategies-fetch-mode-join-mapping-example[]
@OneToMany(mappedBy = "department")
@Fetch(FetchMode.JOIN)
private List<Employee> employees = new ArrayList<>();
//end::fetching-strategies-fetch-mode-join-mapping-example[]
//Getters and setters omitted for brevity
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
@Entity(name = "Employee")
public static class Employee {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String username;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
//Getters and setters omitted for brevity
}
}

View File

@ -0,0 +1,127 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.NaturalId;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public class FetchModeSelectTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( FetchModeSelectTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class,
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( long i = 0; i < 2; i++ ) {
Department department = new Department();
department.id = i + 1;
entityManager.persist( department );
for ( long j = 0; j < 3; j++ ) {
Employee employee1 = new Employee();
employee1.username = String.format( "user %d_%d", i, j );
employee1.department = department;
entityManager.persist( employee1 );
}
}
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::fetching-strategies-fetch-mode-select-example[]
List<Department> departments = entityManager.createQuery(
"select d from Department d", Department.class )
.getResultList();
log.infof( "Fetched %d Departments", departments.size());
for (Department department : departments ) {
assertEquals( 3, department.getEmployees().size() );
}
//end::fetching-strategies-fetch-mode-select-example[]
} );
}
//tag::fetching-strategies-fetch-mode-select-mapping-example[]
@Entity(name = "Department")
public static class Department {
@Id
private Long id;
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
@Fetch(FetchMode.SELECT)
private List<Employee> employees = new ArrayList<>();
//Getters and setters omitted for brevity
//end::fetching-strategies-fetch-mode-select-mapping-example[]
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
//tag::fetching-strategies-fetch-mode-select-mapping-example[]
}
@Entity(name = "Employee")
public static class Employee {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String username;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
//Getters and setters omitted for brevity
}
//end::fetching-strategies-fetch-mode-select-mapping-example[]
}

View File

@ -0,0 +1,138 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.NaturalId;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public class FetchModeSubselectTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( FetchModeSubselectTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class,
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( long i = 0; i < 2; i++ ) {
Department department = new Department();
department.id = i + 1;
department.name = String.format( "Department %d", department.id );
entityManager.persist( department );
for ( long j = 0; j < 3; j++ ) {
Employee employee1 = new Employee();
employee1.username = String.format( "user %d_%d", i, j );
employee1.department = department;
entityManager.persist( employee1 );
}
}
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::fetching-strategies-fetch-mode-subselect-example[]
List<Department> departments = entityManager.createQuery(
"select d " +
"from Department d " +
"where d.name like :token", Department.class )
.setParameter( "token", "Department%" )
.getResultList();
log.infof( "Fetched %d Departments", departments.size());
for (Department department : departments ) {
assertEquals( 3, department.getEmployees().size() );
}
//end::fetching-strategies-fetch-mode-subselect-example[]
} );
}
@Entity(name = "Department")
public static class Department {
@Id
private Long id;
private String name;
//tag::fetching-strategies-fetch-mode-subselect-mapping-example[]
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<Employee> employees = new ArrayList<>();
//end::fetching-strategies-fetch-mode-subselect-mapping-example[]
//Getters and setters omitted for brevity
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
@Entity(name = "Employee")
public static class Employee {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String username;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
//Getters and setters omitted for brevity
}
}