11. Fetching

Fetching, essentially, is the process of grabbing data from the database and making it available to the application. Tuning how an application does fetching is one of the biggest factors in determining how an application will perform. Fetching too much data, in terms of width (values/columns) and/or depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. Fetching too little data might cause additional fetching to be needed. Tuning how an application fetches data presents a great opportunity to influence the overall application performance.

11.1. The basics

The concept of fetching breaks down into two different questions.

  • When should the data be fetched? Now? Later?

  • How should the data be fetched?

“Now” is generally termed eager or immediate while “later” is generally termed lazy or delayed.

There are a number of scopes for defining fetching:

static

Static definition of fetching strategies is done in the mappings. The statically-defined fetch strategies are used in the absence of any dynamically defined strategies.

  • SELECT

    Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). This is the strategy generally termed N+1.

    JOIN

    Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of an SQL outer join.

    BATCH

    Performs a separate SQL select to load a number of related data items using an IN-restriction as part of the SQL WHERE-clause based on a batch size. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).

    SUBSELECT

    Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).

dynamic (sometimes referred to as runtime)

The dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching:

  • fetch profiles

    defined in mappings, but can be enabled/disabled on the Session.

    HQL / JPQL

    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.

11.2. 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:

Example 403. Domain model

  1. @Entity(name = "Department")
  2. public static class Department {
  3. @Id
  4. private Long id;
  5. //Getters and setters omitted for brevity
  6. }
  7. @Entity(name = "Employee")
  8. public static class Employee {
  9. @Id
  10. private Long id;
  11. @NaturalId
  12. private String username;
  13. @ManyToOne(fetch = FetchType.EAGER)
  14. private Department department;
  15. //Getters and setters omitted for brevity
  16. }

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:

Example 404. Direct fetching example

  1. Employee employee = entityManager.find( Employee.class, 1L );
  1. select
  2. e.id as id1_1_0_,
  3. e.department_id as departme3_1_0_,
  4. e.username as username2_1_0_,
  5. d.id as id1_0_1_
  6. from
  7. Employee e
  8. left outer join
  9. Department d
  10. on e.department_id=d.id
  11. where
  12. e.id = 1

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:

Example 405. Entity query fetching example

  1. Employee employee = entityManager.createQuery(
  2. "select e " +
  3. "from Employee e " +
  4. "where e.id = :id", Employee.class)
  5. .setParameter( "id", 1L )
  6. .getSingleResult();
  1. select
  2. e.id as id1_1_,
  3. e.department_id as departme3_1_,
  4. e.username as username2_1_
  5. from
  6. Employee e
  7. where
  8. e.id = 1
  9. select
  10. d.id as id1_0_0_
  11. from
  12. Department d
  13. where
  14. d.id = 1

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.

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 lead to N+1 query issues.

For this reason, you should prefer LAZY associations.

11.3. Applying fetch strategies

Let’s consider these topics as it relates to a simple domain model and a few use cases.

Example 406. Sample domain model

  1. @Entity(name = "Department")
  2. public static class Department {
  3. @Id
  4. private Long id;
  5. @OneToMany(mappedBy = "department")
  6. private List<Employee> employees = new ArrayList<>();
  7. //Getters and setters omitted for brevity
  8. }
  9. @Entity(name = "Employee")
  10. public static class Employee {
  11. @Id
  12. private Long id;
  13. @NaturalId
  14. private String username;
  15. @Column(name = "pswd")
  16. @ColumnTransformer(
  17. read = "decrypt( 'AES', '00', pswd )",
  18. write = "encrypt('AES', '00', ?)"
  19. )
  20. private String password;
  21. private int accessLevel;
  22. @ManyToOne(fetch = FetchType.LAZY)
  23. private Department department;
  24. @ManyToMany(mappedBy = "employees")
  25. private List<Project> projects = new ArrayList<>();
  26. //Getters and setters omitted for brevity
  27. }
  28. @Entity(name = "Project")
  29. public class Project {
  30. @Id
  31. private Long id;
  32. @ManyToMany
  33. private List<Employee> employees = new ArrayList<>();
  34. //Getters and setters omitted for brevity
  35. }

The Hibernate recommendation is to statically mark all associations lazy and to use dynamic fetching strategies for eagerness.

This is unfortunately at odds with the JPA specification which defines that all one-to-one and many-to-one associations should be eagerly fetched by default.

Hibernate, as a JPA provider, honors that default.

11.4. No fetching

For the first use case, consider the application login process for an Employee. Let’s assume that login only requires access to the Employee information, not Project nor Department information.

Example 407. No fetching example

  1. Employee employee = entityManager.createQuery(
  2. "select e " +
  3. "from Employee e " +
  4. "where " +
  5. " e.username = :username and " +
  6. " e.password = :password",
  7. Employee.class)
  8. .setParameter( "username", username)
  9. .setParameter( "password", password)
  10. .getSingleResult();

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.

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.

Example 408. No fetching (scalar) example

  1. Integer accessLevel = entityManager.createQuery(
  2. "select e.accessLevel " +
  3. "from Employee e " +
  4. "where " +
  5. " e.username = :username and " +
  6. " e.password = :password",
  7. Integer.class)
  8. .setParameter( "username", username)
  9. .setParameter( "password", password)
  10. .getSingleResult();

11.5. 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.

Example 409. Dynamic JPQL fetching example

  1. Employee employee = entityManager.createQuery(
  2. "select e " +
  3. "from Employee e " +
  4. "left join fetch e.projects " +
  5. "where " +
  6. " e.username = :username and " +
  7. " e.password = :password",
  8. Employee.class)
  9. .setParameter( "username", username)
  10. .setParameter( "password", password)
  11. .getSingleResult();

Example 410. Dynamic query fetching example

  1. CriteriaBuilder builder = entityManager.getCriteriaBuilder();
  2. CriteriaQuery<Employee> query = builder.createQuery( Employee.class );
  3. Root<Employee> root = query.from( Employee.class );
  4. root.fetch( "projects", JoinType.LEFT);
  5. query.select(root).where(
  6. builder.and(
  7. builder.equal(root.get("username"), username),
  8. builder.equal(root.get("password"), password)
  9. )
  10. );
  11. Employee employee = entityManager.createQuery( query ).getSingleResult();

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.

11.6. Dynamic fetching via JPA entity graph

JPA 2.1 introduced entity graph so the application developer has more control over fetch plans. It has two modes to choose from:

fetch graph

In this case, all attributes specified in the entity graph will be treated as FetchType.EAGER, and all attributes not specified will ALWAYS be treated as FetchType.LAZY.

load graph

In this case, all attributes specified in the entity graph will be treated as FetchType.EAGER, but attributes not specified use their static mapping specification.

Below is an fetch graph dynamic fetching example:

Example 411. Fetch graph example

  1. @Entity(name = "Employee")
  2. @NamedEntityGraph(name = "employee.projects",
  3. attributeNodes = @NamedAttributeNode("projects")
  4. )
  1. Employee employee = entityManager.find(
  2. Employee.class,
  3. userId,
  4. Collections.singletonMap(
  5. "javax.persistence.fetchgraph",
  6. entityManager.getEntityGraph( "employee.projects" )
  7. )
  8. );

When executing a JPQL query, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly, which can lead to N+1 query issues.

For this reason, it’s better to use LAZY associations, and only fetch them eagerly on a per-query basis.

An EntityGraph is the root of a “load plan” and must correspond to an EntityType.

11.6.1. JPA (key) subgraphs

A sub-graph is used to control the fetching of sub-attributes of the AttributeNode it is applied to. It is generally defined via the @NamedSubgraph annotation.

If we have a Project parent entity which has an employees child associations, and we’d like to fetch the department for the Employee child association.

Example 412. Fetch graph with a subgraph mapping

  1. @Entity(name = "Project")
  2. @NamedEntityGraph(name = "project.employees",
  3. attributeNodes = @NamedAttributeNode(
  4. value = "employees",
  5. subgraph = "project.employees.department"
  6. ),
  7. subgraphs = @NamedSubgraph(
  8. name = "project.employees.department",
  9. attributeNodes = @NamedAttributeNode( "department" )
  10. )
  11. )
  12. public static class Project {
  13. @Id
  14. private Long id;
  15. @ManyToMany
  16. private List<Employee> employees = new ArrayList<>();
  17. //Getters and setters omitted for brevity
  18. }

When fetching this entity graph, Hibernate generates the following SQL query:

Example 413. Fetch graph with a subgraph mapping

  1. Project project = doInJPA( this::entityManagerFactory, entityManager -> {
  2. return entityManager.find(
  3. Project.class,
  4. 1L,
  5. Collections.singletonMap(
  6. "javax.persistence.fetchgraph",
  7. entityManager.getEntityGraph( "project.employees" )
  8. )
  9. );
  10. } );
  1. select
  2. p.id as id1_2_0_, e.id as id1_1_1_, d.id as id1_0_2_,
  3. e.accessLevel as accessLe2_1_1_,
  4. e.department_id as departme5_1_1_,
  5. decrypt( 'AES', '00', e.pswd ) as pswd3_1_1_,
  6. e.username as username4_1_1_,
  7. p_e.projects_id as projects1_3_0__,
  8. p_e.employees_id as employee2_3_0__
  9. from
  10. Project p
  11. inner join
  12. Project_Employee p_e
  13. on p.id=p_e.projects_id
  14. inner join
  15. Employee e
  16. on p_e.employees_id=e.id
  17. inner join
  18. Department d
  19. on e.department_id=d.id
  20. where
  21. p.id = ?
  22. -- binding parameter [1] as [BIGINT] - [1]

Specifying a sub-graph is only valid for an attribute (or its “key”) whose type is a ManagedType. So while an EntityGraph must correspond to an EntityType, a Subgraph is legal for any ManagedType. An attribute’s key is defined as either:

  • For a singular attribute, the attribute’s type must be an IdentifiableType and that IdentifiableType must have a composite identifier. The “key sub-graph” is applied to the identifier type. The non-key sub-graph applies to the attribute’s value, which must be a ManagedType.

  • For a plural attribute, the attribute must be a Map and the Map’s key value must be a ManagedType. The “key sub-graph” is applied to the Map’s key type. In this case, the non-key sub-graph applies to the plural attribute’s value/element.

11.6.2. JPA SubGraph sub-typing

SubGraphs can also be sub-type specific. Given an attribute whose value is an inheritance hierarchy, we can refer to attributes of a specific sub-type using the forms of sub-graph definition that accept the sub-type Class.

11.6.3. Creating and applying JPA graphs from text representations

Hibernate allows the creation of JPA fetch/load graphs by parsing a textual representation of the graph. Generally speaking, the textual representation of a graph is a comma-separated list of attribute names, optionally including any sub-graph specifications. org.hibernate.graph.EntityGraphParser is the starting point for such parsing operations.

Parsing a textual representation of a graph is not (yet) a part of the JPA specification. So the syntax described here is specific to Hibernate. We do hope to eventually make this syntax part of the JPA specification proper.

Example 414. Parsing a simple graph

  1. final EntityGraph<Project> graph = GraphParser.parse(
  2. Project.class,
  3. "employees( department )",
  4. entityManager
  5. );

This example actually functions exactly as Fetch graph with a subgraph mapping, just using a parsed graph rather than a named graph.

The syntax also supports defining “key sub-graphs”. To specify a key sub-graph, .key is added to the end of the attribute name.

Example 415. Parsing an entity key graph

  1. final EntityGraph<Movie> graph = GraphParser.parse(
  2. Movie.class,
  3. "cast.key( name )",
  4. entityManager
  5. );

Example 416. Parsing a map key graph

  1. final EntityGraph<Ticket> graph = GraphParser.parse(
  2. Ticket.class,
  3. "showing.key( movie( cast ) )",
  4. entityManager
  5. );

Parsing can also handle sub-type specific sub-graphs. For example, given an entity hierarchy of LegalEntity ← (Corporation | Person | NonProfit) and an attribute named responsibleParty whose type is the LegalEntity base type we might have:

  1. responsibleParty(Corporation: ceo)

We can even duplicate the attribute names to apply different sub-type sub-graphs:

  1. responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsibleParty(NonProfit: sector)

The duplicated attribute names are handled according to the JPA specification which says that duplicate specification of the attribute node results in the originally registered AttributeNode to be re-used effectively merging the 2 AttributeNode specifications together. In other words, the above specification creates a single AttributeNode with 3 distinct SubGraphs. It is functionally the same as calling:

  1. Class<Invoice> invoiceClass = ...;
  2. javax.persistence.EntityGraph<Invoice> invoiceGraph = entityManager.createEntityGraph( invoiceClass );
  3. invoiceGraph.addAttributeNode( "responsibleParty" );
  4. invoiceGraph.addSubgraph( "responsibleParty" ).addAttributeNode( "taxIdNumber" );
  5. invoiceGraph.addSubgraph( "responsibleParty", Corporation.class ).addAttributeNode( "ceo" );
  6. invoiceGraph.addSubgraph( "responsibleParty", NonProfit.class ).addAttributeNode( "sector" );

11.6.4. Combining multiple JPA entity graphs into one

Multiple entity graphs can be combined into a single “super graph” that acts as a union. Graph from the previous example can also be built by combining separate aspect graphs into one, such as:

Example 417. Combining multiple graphs into one

  1. final EntityGraph<Project> a = GraphParser.parse(
  2. Project.class, "employees( username )", entityManager
  3. );
  4. final EntityGraph<Project> b = GraphParser.parse(
  5. Project.class, "employees( password, accessLevel )", entityManager
  6. );
  7. final EntityGraph<Project> c = GraphParser.parse(
  8. Project.class, "employees( department( employees( username ) ) )", entityManager
  9. );
  10. final EntityGraph<Project> all = EntityGraphs.merge( entityManager, Project.class, a, b, c );

11.7. Dynamic fetching via Hibernate profiles

Suppose we wanted to leverage loading by natural-id to obtain the Employee information in the “projects for and employee” use-case. Loading by natural-id uses the statically defined fetching strategies, but does not expose a means to define load-specific fetching. So we would leverage a fetch profile.

Example 418. Fetch profile example

  1. @Entity(name = "Employee")
  2. @FetchProfile(
  3. name = "employee.projects",
  4. fetchOverrides = {
  5. @FetchProfile.FetchOverride(
  6. entity = Employee.class,
  7. association = "projects",
  8. mode = FetchMode.JOIN
  9. )
  10. }
  11. )
  1. session.enableFetchProfile( "employee.projects" );
  2. Employee employee = session.bySimpleNaturalId( Employee.class ).load( username );

Here the Employee is obtained by natural-id lookup and the Employee’s Project data is fetched eagerly. If the Employee data is resolved from cache, the Project data is resolved on its own. However, if the Employee data is not resolved in cache, the Employee and Project data is resolved in one SQL query via join as we saw above.

11.8. Batch fetching

Hibernate offers the @BatchSize annotation, which can be used when fetching uninitialized entity proxies.

Considering the following entity mapping:

Example 419. @BatchSize mapping example

  1. @Entity(name = "Department")
  2. public static class Department {
  3. @Id
  4. private Long id;
  5. @OneToMany(mappedBy = "department")
  6. //@BatchSize(size = 5)
  7. private List<Employee> employees = new ArrayList<>();
  8. //Getters and setters omitted for brevity
  9. }
  10. @Entity(name = "Employee")
  11. public static class Employee {
  12. @Id
  13. private Long id;
  14. @NaturalId
  15. private String name;
  16. @ManyToOne(fetch = FetchType.LAZY)
  17. private Department department;
  18. //Getters and setters omitted for brevity
  19. }

Considering that we have previously fetched several Department entities, and now we need to initialize the employees entity collection for each particular Department, the @BatchSize annotations allows us to load multiple Employee entities in a single database roundtrip.

Example 420. @BatchSize fetching example

  1. List<Department> departments = entityManager.createQuery(
  2. "select d " +
  3. "from Department d " +
  4. "inner join d.employees e " +
  5. "where e.name like 'John%'", Department.class)
  6. .getResultList();
  7. for ( Department department : departments ) {
  8. log.infof(
  9. "Department %d has {} employees",
  10. department.getId(),
  11. department.getEmployees().size()
  12. );
  13. }
  1. SELECT
  2. d.id as id1_0_
  3. FROM
  4. Department d
  5. INNER JOIN
  6. Employee employees1_
  7. ON d.id=employees1_.department_id
  8. SELECT
  9. e.department_id as departme3_1_1_,
  10. e.id as id1_1_1_,
  11. e.id as id1_1_0_,
  12. e.department_id as departme3_1_0_,
  13. e.name as name2_1_0_
  14. FROM
  15. Employee e
  16. WHERE
  17. e.department_id IN (
  18. 0, 2, 3, 4, 5
  19. )
  20. SELECT
  21. e.department_id as departme3_1_1_,
  22. e.id as id1_1_1_,
  23. e.id as id1_1_0_,
  24. e.department_id as departme3_1_0_,
  25. e.name as name2_1_0_
  26. FROM
  27. Employee e
  28. WHERE
  29. e.department_id IN (
  30. 6, 7, 8, 9, 1
  31. )

As you can see in the example above, there are only two SQL statements used to fetch the Employee entities associated with multiple Department entities.

Without @BatchSize, you’d run into a N+1 query issue, so, instead of 2 SQL statements, there would be 10 queries needed for fetching the Employee child entities.

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.

11.9. 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 lazily using a secondary select for each individual entity, collection, or join load. It’s equivalent to JPA FetchType.LAZY fetching strategy.

JOIN

Use an outer join to load the related entities, collections or joins when using direct fetching. It’s equivalent to JPA FetchType.EAGER fetching strategy.

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.

11.10. FetchMode.SELECT

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

Example 421. FetchMode.SELECT mapping example

  1. @Entity(name = "Department")
  2. public static class Department {
  3. @Id
  4. private Long id;
  5. @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
  6. @Fetch(FetchMode.SELECT)
  7. private List<Employee> employees = new ArrayList<>();
  8. //Getters and setters omitted for brevity
  9. }
  10. @Entity(name = "Employee")
  11. public static class Employee {
  12. @Id
  13. @GeneratedValue
  14. private Long id;
  15. @NaturalId
  16. private String username;
  17. @ManyToOne(fetch = FetchType.LAZY)
  18. private Department department;
  19. //Getters and setters omitted for brevity
  20. }

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 422. FetchMode.SELECT mapping example

  1. List<Department> departments = entityManager.createQuery(
  2. "select d from Department d", Department.class )
  3. .getResultList();
  4. log.infof( "Fetched %d Departments", departments.size());
  5. for (Department department : departments ) {
  6. assertEquals( 3, department.getEmployees().size() );
  7. }
  1. SELECT
  2. d.id as id1_0_
  3. FROM
  4. Department d
  5. -- Fetched 2 Departments
  6. SELECT
  7. e.department_id as departme3_1_0_,
  8. e.id as id1_1_0_,
  9. e.id as id1_1_1_,
  10. e.department_id as departme3_1_1_,
  11. e.username as username2_1_1_
  12. FROM
  13. Employee e
  14. WHERE
  15. e.department_id = 1
  16. SELECT
  17. e.department_id as departme3_1_0_,
  18. e.id as id1_1_0_,
  19. e.id as id1_1_1_,
  20. e.department_id as departme3_1_1_,
  21. e.username as username2_1_1_
  22. FROM
  23. Employee e
  24. WHERE
  25. 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 issues.

11.11. FetchMode.SUBSELECT

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

Example 423. FetchMode.SUBSELECT mapping example

  1. @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
  2. @Fetch(FetchMode.SUBSELECT)
  3. 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

  1. List<Department> departments = entityManager.createQuery(
  2. "select d " +
  3. "from Department d " +
  4. "where d.name like :token", Department.class )
  5. .setParameter( "token", "Department%" )
  6. .getResultList();
  7. log.infof( "Fetched %d Departments", departments.size());
  8. for (Department department : departments ) {
  9. assertEquals( 3, department.getEmployees().size() );
  10. }
  1. SELECT
  2. d.id as id1_0_
  3. FROM
  4. Department d
  5. where
  6. d.name like 'Department%'
  7. -- Fetched 2 Departments
  8. SELECT
  9. e.department_id as departme3_1_1_,
  10. e.id as id1_1_1_,
  11. e.id as id1_1_0_,
  12. e.department_id as departme3_1_0_,
  13. e.username as username2_1_0_
  14. FROM
  15. Employee e
  16. WHERE
  17. e.department_id in (
  18. SELECT
  19. fetchmodes0_.id
  20. FROM
  21. Department fetchmodes0_
  22. WHERE
  23. d.name like 'Department%'
  24. )

11.12. FetchMode.JOIN

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

Example 425. FetchMode.JOIN mapping example

  1. @OneToMany(mappedBy = "department")
  2. @Fetch(FetchMode.JOIN)
  3. 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 426. FetchMode.JOIN mapping example

  1. Department department = entityManager.find( Department.class, 1L );
  2. log.infof( "Fetched department: %s", department.getId());
  3. assertEquals( 3, department.getEmployees().size() );
  1. SELECT
  2. d.id as id1_0_0_,
  3. e.department_id as departme3_1_1_,
  4. e.id as id1_1_1_,
  5. e.id as id1_1_2_,
  6. e.department_id as departme3_1_2_,
  7. e.username as username2_1_2_
  8. FROM
  9. Department d
  10. LEFT OUTER JOIN
  11. Employee e
  12. on d.id = e.department_id
  13. WHERE
  14. d.id = 1
  15. -- Fetched department: 1

This time, there was no secondary query because the child collection was loaded along with the parent entity.

11.13. @LazyCollection

The @LazyCollection annotation is used to specify the lazy fetching behavior of a given collection. The possible values are given by the [LazyCollectionOption](https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/annotations/LazyCollectionOption.html) enumeration:

TRUE

Load it when the state is requested.

FALSE

Eagerly load it.

EXTRA

Prefer extra queries over full collection loading.

The TRUE and FALSE values are deprecated since you should be using the JPA FetchType attribute of the @ElementCollection, @OneToMany, or @ManyToMany collection.

The EXTRA value has no equivalent in the JPA specification, and it’s used to avoid loading the entire collection even when the collection is accessed for the first time. Each element is fetched individually using a secondary query.

Example 427. LazyCollectionOption.EXTRA mapping example

  1. @Entity(name = "Department")
  2. public static class Department {
  3. @Id
  4. private Long id;
  5. @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
  6. @OrderColumn(name = "order_id")
  7. @LazyCollection( LazyCollectionOption.EXTRA )
  8. private List<Employee> employees = new ArrayList<>();
  9. //Getters and setters omitted for brevity
  10. }
  11. @Entity(name = "Employee")
  12. public static class Employee {
  13. @Id
  14. private Long id;
  15. @NaturalId
  16. private String username;
  17. @ManyToOne(fetch = FetchType.LAZY)
  18. private Department department;
  19. //Getters and setters omitted for brevity
  20. }

LazyCollectionOption.EXTRA only works for ordered collections, either List(s) that are annotated with @OrderColumn or Map(s).

For bags (e.g. regular List(s) of entities that do not preserve any certain ordering), the @LazyCollection(LazyCollectionOption.EXTRA) behaves like any other FetchType.LAZY collection (the collection is fetched entirely upon being accessed for the first time).

Now, considering we have the following entities:

Example 428. LazyCollectionOption.EXTRA Domain Model example

  1. Department department = new Department();
  2. department.setId( 1L );
  3. entityManager.persist( department );
  4. for (long i = 1; i <= 3; i++ ) {
  5. Employee employee = new Employee();
  6. employee.setId( i );
  7. employee.setUsername( String.format( "user_%d", i ) );
  8. department.addEmployee(employee);
  9. }

When fetching the employee collection entries by their position in the List, Hibernate generates the following SQL statements:

Example 429. LazyCollectionOption.EXTRA fetching example

  1. Department department = entityManager.find(Department.class, 1L);
  2. int employeeCount = department.getEmployees().size();
  3. for(int i = 0; i < employeeCount; i++ ) {
  4. log.infof( "Fetched employee: %s", department.getEmployees().get( i ).getUsername());
  5. }
  1. SELECT
  2. max(order_id) + 1
  3. FROM
  4. Employee
  5. WHERE
  6. department_id = ?
  7. -- binding parameter [1] as [BIGINT] - [1]
  8. SELECT
  9. e.id as id1_1_0_,
  10. e.department_id as departme3_1_0_,
  11. e.username as username2_1_0_
  12. FROM
  13. Employee e
  14. WHERE
  15. e.department_id=?
  16. AND e.order_id=?
  17. -- binding parameter [1] as [BIGINT] - [1]
  18. -- binding parameter [2] as [INTEGER] - [0]
  19. SELECT
  20. e.id as id1_1_0_,
  21. e.department_id as departme3_1_0_,
  22. e.username as username2_1_0_
  23. FROM
  24. Employee e
  25. WHERE
  26. e.department_id=?
  27. AND e.order_id=?
  28. -- binding parameter [1] as [BIGINT] - [1]
  29. -- binding parameter [2] as [INTEGER] - [1]
  30. SELECT
  31. e.id as id1_1_0_,
  32. e.department_id as departme3_1_0_,
  33. e.username as username2_1_0_
  34. FROM
  35. Employee e
  36. WHERE
  37. e.department_id=?
  38. AND e.order_id=?
  39. -- binding parameter [1] as [BIGINT] - [1]
  40. -- binding parameter [2] as [INTEGER] - [2]

Therefore, the child entities were fetched one after the other without triggering a full collection initialization.

For this reason, caution is advised since accessing all elements using LazyCollectionOption.EXTRA can lead to N+1 query issues.