Chapter 4. Advanced Fetching

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:

  • Fetching strategies
  • Hibernate Query Language
  • Native SQL
  • Criteria objects
  • Filters
  • Pagination

Fetching strategy

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.

Tip

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 mode

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);

The SELECT fetch mode

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

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.

Batch fetching

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.159.178