Advanced Fetching

Ramin Rad

January 2016

In this article by Ramin Rad, author of the book Mastering Hibernate, we have discussed various ways of fetching the data from the permanent store. We will focus a little more on annotations related to data fetch.

(For more resources related to this topic, see here.)

Fetching strategy

In Java Persistence API, JPA, you can provide a hint to fetch the data lazily or eagerly using the FetchType. However, some implementations may ignore lazy strategy and just fetch everything eagerly. Hibernate's default strategy is FetchType.LAZY to reduce the memory footprint of your application.

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 you understand when to use which.

JOIN fetch mode

The JOIN fetch type forces Hibernate to create a SQL join statement to populate both the entities and the 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
private 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 and 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 is using a left join all courses and any student that may have signed up for those 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, if you fetch a course entity using a statement such as this:

List<Course> courses = session
       .createQuery("from Course c where c.id = :courseId")
       .setLong("courseId", chemistryId)
       .list();

Then, Hibernate will use SELECT mode; but if you don't use HQL, as shown in the next example, Hibernate will pay attention to the fetch mode instructions provided by the annotation.

Course course = (Course) session.get(Course.class, chemistryId);

SELECT fetch mode

In SELECT mode, Hibernate uses an additional SELECT statement to fetch the related entities. This mode doesn't affect the behavior of the fetch type (LAZY, EAGER), so they will work as expected. To demonstrate this, consider the same example used in the last section and lets examine the output:

select
       id, title
   from
       Course        
   where
      id=?

select course_id, id, gender, name from Student where course_id=?

Note that the first Hibernate fetches and populates the Course entity and then uses the course ID to fetch the related students. Also, if your fetch type is set to LAZY and you never reference the related entities, the second SELECT is never executed.

SUBSELECT fetch mode

The SUBSELECT fetch mode is used to minimize the number of SELECT statements 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 sub-select to fetch all the owners for the list of owned entities 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 and another to fetch the cars for those owners, by using a sub-select, as follows:

   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 section, Hibernate will 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, then, for each returned entity another SELECT statement is executed to fetch the associated entities.

Finally, SUBSELECT fetch mode is not supported in the ToOne associations, such as OneToOne or ManyToOne because it was designed for relationships where the ownership of the entities is clear.

Batch fetching

Another strategy offered by Hibernate is batch fetching. The idea 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. (Note that all the 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 are instructing Hibernate to fetch the related entities (cars) using a SQL statement that uses a where in clause; thus listing the relevant ID for the owner entity, as shown:

   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 it returns more than the batch size there would be multiple select statements to fetch the owned entities, each fetching 10 entities at a time.

Summary

In this article, we covered many ways of fetching datasets from the database.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Mastering Hibernate

Explore Title