2020-05-14

Hibernate JPA @Fetch Example

@Fetch

The @Fetch annotation is used to specify the Hibernate specific FetchMode (e.g. JOIN, SELECT, SUBSELECT) used for the currently annotated association.

Fetch define the fetching strategy used for the given association.


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 FetchModes:

SELECT
The association is going to be fetched using a secondary select for each individual entity, collection, or join load. This mode can be used for either FetchType.EAGER or FetchType.LAZY.

JOIN
Use an outer join to load the related entities, collections or joins when using direct fetching. This mode can only be used for FetchType.EAGER.

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.

FetchMode.SELECT

To demonstrate how FetchMode.SELECT works, consider the following entity mapping:

Example : FetchMode.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

}

@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

}
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:

Example : FetchMode.SELECT mapping 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() );
}
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
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 issue.

FetchMode.SUBSELECT

To demonstrate how FetchMode.SUBSELECT works, we are going to modify the FetchMode.SELECT mapping example to use FetchMode.SUBSELECT:

Example : FetchMode.SUBSELECT mapping example

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<Employee> employees = new ArrayList<>();
Now, we are going to fetch all Department entities that match a given filtering predicate 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.

Example 424. FetchMode.SUBSELECT mapping 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() );
}
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%'
    )

FetchMode.JOIN

To demonstrate how FetchMode.JOIN works, we are going to modify the FetchMode.SELECT mapping example to use FetchMode.JOIN instead:

Example : FetchMode.JOIN mapping example

@OneToMany(mappedBy = "department")
@Fetch(FetchMode.JOIN)
private List<Employee> employees = new ArrayList<>();
Now, we are going to fetch one Department and navigate its employees collections.

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.

Example: FetchMode.JOIN mapping example
Department department = entityManager.find( Department.class, 1L );

log.infof( "Fetched department: %s", department.getId());

assertEquals( 3, department.getEmployees().size() );
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

No comments:

Post a Comment