HHH-11186 - Add examples for all Hibernate annotations
Document more annotations: - @Fetch along with SELECT, SUBSELECT, and JOIN FetchMode
This commit is contained in:
parent
de346827d0
commit
cc1730fb01
|
@ -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`
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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%'
|
||||
)
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
|
@ -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[]
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue