interleap
Published in

interleap

Problems with Hibernate One-To-Many (And Their Solutions)

This blog intends to be a reference of Hibernate mapping related problems and their solutions, and is not intended as an educational read. This is to prevent us from wasting time on finding solutions to the same problems again and again.

If you’re using JPA annotations, you may have come across the @OneToMany @JoinColumn and similar annotations. There are various nuances which come with these annotations.

Let’s begin by taking an example of what is OneToMany mapping, and what is the syntax in Hibernate.

Assume you have two tables — courses and lessons , where a Course can have multiple objects of type Lesson associated to it. In the database, it would be represented by a foreign key constraint.

For example, it could be something like this —

courses
----------------
| id | name |
| 1 | Intro to Java |
| 2 | Introduction to Docker|
lessons
| id | course_id | name |
| 1 | 1 | Basic Syntax |
| 2 | 1 | Classes in Java |

In Java, you could use JPA annotations to create an association between these 2 objects.

@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(min=5, max=60)
private String name;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "course_id", referencedColumnName = "id")
private List<Lesson> lessons;

This allows us to tell Hibernate that when it creates the Course object, it should load all lessons that have a foreign key constraint on that course.

Sounds amazing, doesn’t it?

Well, it is amazing, till the point where you start facing issues that are extremely painful and difficult to solve.

Let’s go into a little more details.

What is FetchType?

The join between Course and Lesson class could make the object quite heavy, and the query quite slow, so Hibernate gives us 2 options — Fetch the lessons eagerly or lazily.

If we choose Lazy, it will create a proxy object that will load the object only when it is queried.

Problems with FetchType.Eager

Problem: The obvious problem with FetchType.EAGER is that it can have a performance impact.

However, the more serious problem lies in a bad decision that Hibernate wants to persist with…..

Problem : When you do a OneToMany annotation, it does a Left Outer Join. For example, in the above example, if you load all records from the courses table and do an outer join on lessons, you will get 3 records. Unfortunately, when you map this to Java, Hibernate returns you 3 Course objects, not 2. That is apparently by design. You can read more in the Hibernate FAQ, or this StackOverflow question.

Solution: Various solutions are given in the Hibernate FAQ and the StackOverflow answer, including suggestions to choose a data-structure like Set instead of list to remove duplicates. Personally, I don’t like most suggestions, because they rely on the coder to solve the problem in code, rather than the framework to return only distinct records.

The solution I find most convenient is to use the FetchMode select —

@OneToMany(fetch = FetchType.EAGER)
@Fetch(FetchMode.SELECT)

This is a solution that will work, but has a major performance impact. This tells Hibernate to first load the Course objects, then go individually into each object, and do one query each to load the corresponding lessons. This makes the load really slow and bad on performance, but more accurate.

Because of these issues, you might be tempted to try FetchType Lazy

Problems with FetchType.Lazy

If you thought FetchType.Eager had problems, wait till you get into the details of the pain FetchType Lazy can cause.

Problem: Fetch Type Lazy will work only when the referenced object is queried within a Hibernate Session. If the session closes, an attempt to reference the object throws an exception.

This can be really annoying when you try to work with an object, only to realise that the items cannot be accessed because Hibernate had already closed the session.

Solution: If you annotate a Spring Method with @Transactional annotation, it does not close the Hibernate session till the transaction is complete.

Problems with the Transactional Annotation

The Transaction annotation seems like a magic pill to solve all our problems, just like Hibernate, OneToMany, FetchType Lazy, but that also has its own problems.

Problem: The @Transactional annotation will not work if the method annotated as @Transactionalis referenced from another method of the same class. For example, if you have the following code —

public class MyClass {
public void loadEverything(){
loadCourse();
}

@Transactional
public Course loadCourse(){...}
}

The Transactional annotation will not make a difference. This is because when a method annotated as @Transactional is called from outside, it is wrapped inside a proxy object that takes care of transactions. When the method is called from within the class, the method is called directly, and the transaction handling does not happen.

Solution: The solution is to use a TransactionTemplate to dynamically create a transaction and execute the method within that Transaction.

public class MyClass {

@Autowired TransactionTemplate template

public void loadEverything(){
template.execute(status -> loadCourse());
}

public Course loadCourse(){...}
}

Problem: Saving data using 2 different repositories, and loading it within the object will not work within the same session.

course = courseRepository.save(new Course(null, courseName, "desc1", null, live, price));
lesson1 = lessonService.save(course.getId(), lesson1)
savedCourse = courseRepository.findByid(course.getId())

If the above code is within a single transaction, the savedCourse object will not contain the lesson object, even if it is associated to the course. This is because the session is still ongoing.

Solution: Keep saving and retrieving transactions separate. This is difficult to do, because you might want to save a complex object in a step by step manner without using the cascading provided by Hibernate, but use Hibernate to retrieve it. However, this will not work.

Summary

In some ways, working with Hibernate reminds me of SOAP. It feels organised and structured, but requires over-engineering, and can become painful and slow. If you have a project with a fairly large amount of complexity, you might want to choose a framework that allows you to write your own queries such as Mybatis or Norm.

--

--

--

Interleap makes interactive, focused tech courses on niche technologies that help you upskill with the latest technologies and processes. Learn more at https://interleap.co

Recommended from Medium

Creating a Blockchain Network in Go

How tado° starts heating hundreds of thousands of homes at 7:00 — part 2: the solution

Android CTF — KGB Messenger

5 Most Useful Linux Commands for Beginners — Part 2

I don’t like to talk about agile development

PM power tools: Bring complex team processes &data to life with Coda.

ION Group | Interview Experience

Sort Algorithms: Visualizing Bubble Sort

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abhinav

Abhinav

Educator, Founder @ Interleap

More from Medium

CS373 Spring 2022: Yifan Zhou

Single log per API call — the good, bad and the next steps

Learned so much from this Interview

Why Gossip is Good