Categories

  • articles

Tags

  • java
  • mysql

Fetching strategies are used to optimize the SQL generated by Hibernate. Using Object Relational Mapping (ORM) tools does have a overhead for the all the benefits it provides but this can be reduced by understanding it.

Firstly there is FetchType, this can be either EAGER or LAZY. Best to explain is with example. If you have a table “owner” that has a one to many relationship with “dog”. When the collection “dog” is set to EAGER all “dogs” will be loaded when the “owner” is selected form the table. When the collection “dog” is set to LAZY the collection will only be initialized when you call and use owner.getDogs(). Not going to go more into detail about this since there is loads of info online about this and different ways to implement JPA/hibernate annotation or XML.

Fetch mode (@FetchMode annotation) has 3 options but there is a 4rd additional mode (@BatchSize annotation):

  1. JOIN – Hibernate retrieves the associated instance or collection in the same SELECT, using an OUTER JOIN
  2. SELECT – a second SELECT is used to retrieve the associated entity or collection. Unless you explicitly disable lazy fetching by specifying lazy=”false” (EAGER), this second select will only be executed when you access the association.
  3. SUBSELECT – a second SELECT is used to retrieve the associated collections for all entities retrieved in a previous query or fetch. Unless you explicitly disable lazy fetching by specifying lazy=”false”, this second select will only be executed when you access the association.
  4. BATCH – an optimization strategy for select fetching. Hibernate retrieves a batch of entity instances or collections in a single SELECT by specifying a list of primary or foreign keys.

Example Time

Fist things first I will use hibernate version 4.3.7.Final with MariaDB using JDBC driver 1.1.7. To start we create two very simple tables.

@Entity
@Table(name = "OWNER")
public class Owner {

	private Integer	id;
	private Set<Dog>	dogs	= new HashSet<>(0);

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	public Integer getId() {
		return this.id;
	}

	public void setId(Integer id) {
		this.id = id;
	}


	@OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
	public Set<Dog> getDogs() {
		return this.dogs;
	}

	public void setDogs(Set<Dog> dogs) {
		this.dogs = dogs;
	}
}

@Entity
@Table(name = "DOG")
public class Dog {

	private Integer	id;
	private Owner	owner;

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "ID", unique = true, nullable = false)
	public Integer getId() {
		return this.id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "OWNER_ID", nullable = false)
	public Owner getOwner() {
		return this.owner;
	}

	public void setOwner(Owner owner) {
		this.owner = owner;
	}
}

In the examples below I will repeat for each combination using a single select and a multi select. For single select I will use:

Owner owner = entityManager.find(Owner.class, 1);

For multi select I will use:

List<Owner> owners = entityManager.createQuery("Select owner FROM Owner owner WHERE ID < 5", Owner.class).getResultList();

1. FetchMode.DEFAULT and LAZY

Please note that FetchMode.JOIN is the default so I have left it out below but will be @Fetch(FetchMode.JOIN)

@OneToMany(fetch = FetchType.LAZY, mappedBy = "owner")
public Set<Dog> getDogs() {

1.1 Single

When I now perform a single lookup you get the following SQL. (enitityManger.find(Owner.class, 1))

select owner0_.ID as ID1_1_0_ from testdb.OWNER owner0_ where owner0_.ID=1

When you use the getDogs() call on Owner will generate a second query:

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=1

1.2 Multiple

Multiple will be the same it will execute SQL:

select owner0_.ID as ID1_1_ from testdb.OWNER owner0_ where ID<5

Then as you call owner.getDogs() it will execute the following SQL for each first call to each owner.getDogs().

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=1

2. FetchMode.DEFAULT and EAGER

Please note that FetchMode.JOIN is the default so I have left it out below but will be @Fetch(FetchMode.JOIN)

@OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
public Set<Dog> getDogs() {

2.1 Single

This perfroms a single query doing a LEFT OUTER JOIN with table “Dogs”. This causes duplication of “Owner” details. This could be a problem depending on how many “Dogs” a “Owner” has and how many columns the “Owner” has.

select owner0_.ID as ID1_1_0_, dogs1_.OWNER_ID as OWNER_ID2_1_1_, dogs1_.ID as ID1_0_1_, dogs1_.ID as ID1_0_2_, dogs1_.OWNER_ID as OWNER_ID2_0_2_ from testdb.OWNER owner0_ left outer join testdb.Dog dogs1_ on owner0_.ID=dogs1_.OWNER_ID where owner0_.ID=1
Owner.ID Dog.ID
1 1
1 2
1 3

2.2 Multiple

This cause a single call to pull all the “Owners” then for each owner it produces a call to get all the associated “Dogs”. This is not ideal.

select owner0_.ID as ID1_1_ from testdb.OWNER owner0_ where ID<5

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=4

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=3

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=2

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=1

3. FetchMode.JOIN and LAZY/EAGER

@OneToMany(fetch = FetchType.LAZY, mappedBy = "owner")
@Fetch(FetchMode.JOIN)

Something to note there when you set FetchMode to JOIN it disables LAZY, sets it to EAGER.

This will produce exactly the same results as in 2 FetchMode.DEFAULT and EAGER

4. FetchMode.SELECT and LAZY/EAGER

@OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
@Fetch(FetchMode.SELECT)

4.1 Single:

select owner0_.ID as ID1_1_0_ from testdb.OWNER owner0_ where owner0_.ID=1

-- For LAZY only after getDogs() is called:

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=1

4.2 Multiple:

For both LAZY and EAGER the following is executed:

select owner0_.ID as ID1_1_ from testdb.OWNER owner0_ where ID<5

For EAGER the following is also executed:

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=4

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=3

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=2

select dogs0_.OWNER_ID as OWNER_ID2_1_0_, dogs0_.ID as ID1_0_0_, dogs0_.ID as ID1_0_1_, dogs0_.OWNER_ID as OWNER_ID2_0_1_ from testdb.Dog dogs0_ where dogs0_.OWNER_ID=1

For LAZY each of the “Dogs” (each line above) is executed one at time as needed when the owner.getDogs() is called on each of the owners.

5. FetchMode.SELECT and LAZY/EAGER with BatchSize

@OneToMany(fetch = FetchType.LAZY/EAGER, mappedBy = "owner")
@Fetch(FetchMode.SELECT)
@BatchSize(size=50)

5.1 Single:

This is the same as 4.1

5.2 Multiple:

Again LAZY will only execute line 2 once owner.getDogs() is called on any of the owners. As you can see here it now instead of calling each of the owners dogs individually it does it in a batch.