In this chapter, we will discuss various ways to fetch the data from the permanent store. In previous chapters, we have already seen some ways to fetch the data when working with annotated entities. Now, we will focus a little more on the annotations that are related to data fetch, Hibernate Query Language (HQL), execution of native SQL, criteria objects, and filters. We will also demonstrate how to perform pagination, which is a functionality that's very common in most enterprise applications.
We will cover the following topics in this chapter:
In Java Persistence API (JPA), you can provide a hint to fetch the data lazily or eagerly using the FetchType
implementation. However, some implementations may ignore the lazy strategy and just fetch everything eagerly. Hibernate's default to reduce the memory footprint of your application is FetchType.LAZY
.
The lazy and eager strategies impact the associated entity. When you set fetch strategy of an associated entity to lazy
, Hibernate will not fetch the associated entity until you access the associated entity. You must access the associated entity in the same session where you fetched the parent entity; otherwise, you will encounter an exception. On the other hand, the eager
fetch strategy forces Hibernate to retrieve the associated entity when the parent entity is fetched.
As mentioned in the previous chapter, Hibernate offers additional fetch modes in addition to the commonly-used JPA fetch types. Here, we will discuss how they are related and provide an explanation so that you understand when to use which.
The JOIN
fetch type forces Hibernate to create a SQL join
statement to populate both the entities and their related entities using just one SQL statement. However, the JOIN
fetch mode also implies that the fetch type is EAGER
, so there is no need to specify the fetch type.
To understand this better, consider the following classes:
@Entity public class Course { @Id @GeneratedValue priva te long id; private String title; @OneToMany(cascade=CascadeType.ALL, mappedBy="course") @Fetch(FetchMode.JOIN) private Set<Student> students = new HashSet<Student>(); // getters and setters } @Entity public class Student { @Id @GeneratedValue private long id; private String name; private char gender; @ManyToOne private Course course; // getters and setters }
In this case, we are instructing Hibernate to use JOIN
to fetch Course
and Student
in one SQL statement. This is the SQL that is composed by Hibernate:
select course0_.id as id1_0_0_, course0_.title as title2_0_0_, students1_.course_id as course_i4_0_1_, students1_.id as id1_1_1_, students1_.gender as gender2_1_2_, students1_.name as name3_1_2_ from Course course0_ left outer join Student students1_ on course0_.id=students1_.course_id where course0_.id=?
As you can see, Hibernate uses left join
for all courses and any students that may have signed up for these courses. Another important thing to note is that if you use HQL, Hibernate will ignore JOIN fetch mode, and you'll have to specify the join
in the HQL. (We will discuss HQL in the next section.) In other words, let's suppose that you fetch a course
entity using a statement, such as the following:
List<Course> courses = session .createQuery("from Course c where c.id = :courseId") .setLong("courseId", chemistryId) .list();
In this case, Hibernate will use SELECT
mode. But if you don't use HQL, as shown here, Hibernate will pay attention to the fetch mode instructions that are provided by the annotation:
Course course = (Course) session.get(Course.class, chemistryId);
In the SELECT
fetch mode, Hibernate uses an additional SELECT
statement to fetch the related entities. This mode doesn't affect the behavior of the fetch type (LAZY
or EAGER
), so they will work as expected. To demonstrate this, consider the same example that was used in the last section, and let's examine the output, as follows:
select id, title from Course where id=? select course_if, id, gender, name from Student where course_id=?
Note that first, Hibernate fetches and populates the Course
entity and then uses the course ID to fetch the related students. Of course, if your fetch type is set to LAZY
and you never referenced the related entities, the second SELECT
is never executed.
The SUBSELECT
fetch mode is used to minimize the number of SELECT
statements that are executed to fetch the related entities. If you first fetch the owner entities and then try to access the associated owned entities without SUBSELECT
, Hibernate will issue an additional SELECT
statement for every one of the owner entities.
Using SUBSELECT
, you instruct Hibernate to use a SQL subselect to fetch all the owners for the list of owned entities that are already fetched.
To understand this better, let's explore the following entity classes:
@Entity public class Owner { @Id @GeneratedValue private long id; private String name; @OneToMany(cascade=CascadeType.ALL, mappedBy="owner") @Fetch(FetchMode.SUBSELECT) private Set<Car> cars = new HashSet<Car>(); // getters and setters } @Entity public class Car { @Id @GeneratedValue private long id; private String model; @ManyToOne private Owner owner; // getters and setters }
If you try to fetch from the Owner
table, Hibernate will only issue two select statements, one to fetch the owners
entity and another to fetch the cars
entity for these owners using a subselect, as shown here:
select id, name from Owner select owner_id, id, model from Car where owner_id in (select id from Owner)
Without the SUBSELECT
fetch mode, instead of the second select statement, as shown in the preceding code, Hibernate would execute a select
statement for every entity returned by the first statement. This is known as the n+1 problem, where one SELECT
statement is executed, and then for each returned entity, another SELECT
statement is executed to fetch the associated entities.
Finally, the SUBSELECT
fetch mode is not supported in ToOne associations, such as OneToOne or ManyToOne, because it was designed for relationships where the ownership of the entities is clear.
Another strategy offered by Hibernate is batch fetching. This is very similar to SUBSELECT
except that instead of using SUBSELECT
, the entity IDs are explicitly listed in the SQL, and the list size is determined by the @BatchSize
annotation. This may perform slightly better for smaller batches. (Keep in mind that all commercial database engines also perform query optimization.)
To demonstrate this, let's consider the following entity classes:
@Entity public class Owner { @Id @GeneratedValue private long id; private String name; @OneToMany(cascade=CascadeType.ALL, mappedBy="owner") @BatchSize(size=10) private Set<Car> cars = new HashSet<Car>(); // getters and setters } @Entity public class Car { @Id @GeneratedValue private long id; private String model; @ManyToOne private Owner owner; // getters and setters }
Using @BatchSize
, we instruct Hibernate to fetch the related entities (cars
) using a SQL statement that uses a "where in" clause, which lists the relevant ID for the owner entity:
select id, name from Owner select owner_id, id, model from Car where owner_id in (?, ?)
In this case, the first select statement only returned two rows. But if this returned more than the batch size, there would be multiple select statements to fetch the owned entities, each fetching 10 entities at a time.
18.119.159.178