Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Should I Go Eager or Lazy With JPA Mappings?

DZone's Guide to

Should I Go Eager or Lazy With JPA Mappings?

Lazy loading? Eager loading? Which should you choose and when? Here are one dev's thoughts on when to use which for your database relations.

· Database Zone
Free Resource

Download the Guide to Open Source Database Selection: MySQL vs. MariaDB and see how the side-by-side comparison of must-have features will ease the journey. Brought to you in partnership with MariaDB.

The short answer is: go Lazy if you care about number of queries happening under the hood. “But what if I always need a particular relation?” — still go lazy. Here is why.

There Is No Such Thing As a Free Lunch

I used to think: “In many scenarios, eager is for free. If in most queries you need a particular relation, then it’s better to fetch it eagerly in one join, and in those rare cases when don’t need it — one additional join costs almost nothing.”

An example would be a Worker, always going in pair with his Unit.

@Entity
class Worker {
    @Id
    private String personalId;

    private String surname;

    @ManyToOne
    private Unit unit;
}

@Entity
class Unit {
    @Id
    private Long id;
}

interface WorkerRepository extends CrudRepository<Worker, String> {
} 

By calling workerRepository.findOne(personalId), I will produce

select worker0_.personal_id as personal1_1_0_, worker0_.surname as surname2_1_0_, worker0_.unit_id as unit_id3_1_0_, unit1_.id as id1_0_1_ from worker worker0_ left outer join unit unit1_ on worker0_.unit_id=unit1_.id where worker0_.personal_id=?

This is perfect. I got only one query (join on worker and unit tables) and I can access all Unit information for free — without a need for an additional query. It’s true — but only when querying by id.

The Problem With Eager Mapping

Imagine you need to query by surname — findBySurname()

interface WorkerRepository extends CrudRepository<Worker, String> {
    Worker findBySurname(String surname);
}

Now suddenly you've got two queries:

select worker0_.personal_id as personal1_1_0_, worker0_.surname as surname2_1_0_, worker0_.unit_id as unit_id3_1_0_ from worker worker0_ where worker0_.personal_id=?
select unit0_.id as id1_0_0_ from unit unit0_ where unit0_.id=?

Two queries even if you’ll never access Worker.unit property.

def "should create two queries when calling by findBySurname"() {
    when:
        workerRepository.findBySurname('Eager')
    then:
        queryStatistics.nrOfQueries() == 2
}

source

It happens because, with findBySurname(), you are asking only for the Worker entity. Then — when the object is constructed — the framework finds out that Worker.unit has an eager mapping — so it needs to be set, but because Unit data is not present. one more query needs to be performed.

The same will be true for JPQL:

interface WorkerRepository extends CrudRepository<Worker, String> {
    @Query("select w from Worker w where w.surname = :surname")
    Worker findBySurnameJPQL(@Param("surname") String surname);
}


Since Unit has eager mapping - to avoid additional select you will always need to fetch it manually.

  • By JPQL
interface WorkerRepository extends CrudRepository<Worker, String> {
    @Query("select w from EagerWorker w join fetch w.unit u where w.surname = :surname")
    Worker findBySurnameJPQLFetchingUnit(@Param("surname") String surname);
}
  • or by EntityGraph
interface WorkerRepository extends CrudRepository<Worker, String> {
    @EntityGraph(attributePaths = "unit")
    Worker findBySurname(String surname);
}

 

Avoiding the Danger

So if you add an eager mapping — every time anyone adds new query method to your repository and forget to fetch all eager relations, an additional query will be created for every eager mapping in your entity!

And from the other side: For every query method in your repo, you will always need to manually fetch all eager relations — even when you don’t need them.

Imagine having a repo with 10 query methods — all fetching all eager relations. Now you need to modify your entity. If you add a new eager relation, you will need to add it to every existing query method to avoid additional joins. It doesn’t end with one repository. Imagine Unit having an eager relation on its own. Now you would need to add another fetch:

interface EagerWorkerRepository extends CrudRepository<EagerWorker, String> {
    @Query("select w from Worker w join fetch w.unit u join fetch u.owner where w.surname = :surname")
    Worker findBySurname(@Param("surname") String surname);
}

Somebody adding eager relations somewhere can cause performance issues in other repositories.

Of course, you can use entity graphs. These can be reused when being created in entity classes. But then you need to remember to add references over repository methods, and they also pollute entity classes. Moreover, they would need to repeat data in case when having an eager relation in an eager relation. (The EntityGraph for Unit needs to have information to eagerly fetch Owner, and the entity Graph for Worker needs to have information to eagerly fetch Unit and again Owner.)

The better solution would be to set all your relations as LAZY.

Conclusion

This model is lazy, but you can still have eager queries — by JPQL. You still need to manually fetch your needed relations (not doing so will create, as before, additional queries when in transaction or LazyIntializationException when not). But  when you don’t need to access relation data, you are not forced to do any fetching and no extra query is performed. Moreover, adding a new Lazy relation shouldn’t affect exisiting code.

To sum it up: I’m not saying that everything should be lazy-loaded. The conclusion is: all entity mappings should be lazy, whereas eager loading should be performed by explicit fetching.

Examples can be found here.

Interested in reducing database costs by moving from Oracle Enterprise to open source subscription?  Read the total cost of ownership (TCO) analysis. Brought to you in partnership with MariaDB.

Topics:
jpa ,java ,database ,lazy loading

Published at DZone with permission of Dawid Kublik, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}