Backend Development

Handling Circular References in Java Spring Boot

Nov 4, 2023

  • 7 mins read

Here one of our developers shares his experience with handling circular references in Java Spring Boot.

Hafizur Rahman
Hafizur Rahman

Handling Circular References in Java Spring Boot

Spring Boot is undoubtedly one of the finest frameworks for creating enterprise-grade web applications. It reduces lots of development time and increases productivity. It feels like magic when a lot of boilerplate code can be replaced by just one annotation. Finally, the community of java is super-enriched with a lot of resources. However, Almost all Java projects that store information in a relational database use JPA with Hibernate or EclipseLink as its most popular implementations. Sometimes in large projects, it can be difficult to maintain references for other classes in the entity class. Today I will discuss one of the challenges I faced when working on this.

1. The problem   

Let me first define circular dependency in OOP terms.

Suppose, I have a class called A which has class B’s Object. (in UML terms A HAS B). at the same time, we have another class B which is also composed of Object of class A (in UML terms B HAS A). This creates the circular dependency because while creating the object of A, Jackson must serialize or deserialize. On the other hand, while creating an object of B, Jackson must serialize/ deserialize again. This is something like egg vs. chicken problem.

– Talk is cheap, Show me the code!


-Okay, Sure!


@Entity
@Table(name = AppTables.employee)
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = AppTables.employeeTable.id)
    private long id;
    @Column(name = AppTables.employeeTable.name)
    private String name;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = AppTables.employeeTable.department)
    private Department department;
// Getters and setters are omitted for brevity
}

This is the Employee model class. Employees are assigned to a department. Joining the department is done with ManyToOne annotation. Now, let us take a look at our Department class.


@Entity
@Table(name = AppTables.department)
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = AppTables.departmentTable.id)
    private Long id;
    @Column(name = AppTables.departmentTable.name)
    private String name;
    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = AppTables.departmentTable.departmentHead)
    private Employee departmentHead;
    // Getters and setters are omitted for brevity
}

Look at the department class closely. You see, not only Employee class have Department but also Department has one department head (which is an employee). Can you guess where the problem is?

The problem occurs when we want to fetch an employee who is the department head of his own department. Not only it will create a cycle but also it will cause an infinite loop to be returned.

Part of the error log is shown below.

java.lang.StackOverflowError: nullat java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_241]at java.lang.ClassLoader.defineClass(Unknown Source) ~[na:1.8.0_241]at java.security.SecureClassLoader.defineClass(Unknown Source) ~[na:1.8.0_241]at java.net.URLClassLoader.defineClass(Unknown Source) ~[na:1.8.0_241]at java.net.URLClassLoader.access$100(Unknown Source) ~[na:1.8.0_241]at java.net.URLClassLoader$1.run(Unknown Source) ~[na:1.8.0_241]at java.net.URLClassLoader$1.run(Unknown Source) ~[na:1.8.0_241]at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_241]at java.net.URLClassLoader.findClass(Unknown Source) ~[na:1.8.0_241]at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_241]at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) ~[na:1.8.0_241]at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_241]at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:740) ~[jackson-databind-2.10.2.jar:2.10.2]

I tried to solve this problem with the following code in Employee class.


    public Department getDepartment() {
        if (this.department != null) {
            if (this.department.getDepartmentHead() != null {
                this.department.setDepartmentHead(null)
            }
        }
        return department;
    }

What I tried to achieve is, that when employees are retrieved department of the department head of that particular employee will be excluded. Thus there will be no circular references. 

-That’s it. Right?


-Umm, Not really.

You should not mess with default getters and setters. The reason is Jackson. Jackson by default reuses objects during serialization. So, When I try to retrieve departments. Once a department head is set to null, Always the department head returns as null. Which is not expected behavior.

2. The Bad Solution 

Jackson provides @JsonIdentityInfo in order to solve this problem. @JsonIdentityInfo is used to handle circular reference of an object by serializing the back-reference’s identifier rather than serializing the complete reference.@JsonIdentityInfo allows serializing a POJO by id when it is encountered second time during serialization.


@Entity
@Table(name=AppTables.department)
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Department {
    //.....//.....
}

Which solves the problem of circular referencing and infinite loop. But, deserializing it on the client-side is not feasible. Even sometimes it might not even be possible to deserialize this json without writing up custom deserializer. 

3. The Good Solution

@JsonIgnoreProperties annotation from Jackson comes to rescue here. In Employee class this following code just before department attribute will do the job for us.


@JsonIgnoreProperties(ignoreUnknown = true, value = {"department"})
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = AppTables.departmentTable.departmentHead) 
private Employee departmentHead;

It will do exactly what needs to be done. It will ignore the department head when the department of an employee is deserialized. Similarly, in Department class, we can add the annotation just before departmentHead to ignore department of the department head.


@JsonIgnoreProperties(ignoreUnknown = true, value = {"department"})@OneToOne(fetch = FetchType.EAGER)@JoinColumn(name = AppTables.departmentTable.departmentHead)private Employee departmentHead;

Not only this solves the deserialization problem but also it gives us the control to ignore one or more attribute to ignore during serialization. 

Suggested Reading

Spring Boot JPA Performance Tuning: Speeding Up Hibernate Batch Processing Time

Backend Development

Spring Boot JPA Performance Tuning: Speeding Up Hibernate Batch Processing Time

Nov 2, 2023

  • 2 mins read

In this blog post, one of our developers discusses one of the hibernate performance issues we faced during one of our projects and how we resolved it.

Hafizur Rahman
Hafizur Rahman

Kotlin vs. Java: The Battle of Languages in Android Development

Backend Development

Kotlin vs. Java: The Battle of Languages in Android Development

Dec 20, 2023

  • 2 mins read

The mobile application for Android is growing rapidly. As a result, the programming languages Kotlin and Java have been fiercely competing.

Guest Author
Guest Author

Contributor

Making Dynamic Tables with Jasper Report and Spring Boot

Backend Development

Making Dynamic Tables with Jasper Report and Spring Boot

Jan 6, 2023

  • 8 mins read

Jasper report is an open-source java class library that aids developers to add reporting capabilities to their Java applications.

Sadia Zahin Prodhan
Sadia Zahin Prodhan

logo

Dhrubok Infotech Services Ltd. (DISL) is one of the top full-service software development companies in Dhaka, Bangladesh that delivers up-to-the-minute iOS, Android Apps and Enterprise Web Solutions. We exist to help startups and enterprises of all sizes to build better products, reach more people and have a prominent online presence.

iconiconiconicon

Follow Us

iconiconiconiconiconicon

© 2024 Dhrubok Infotech Services ltd.

Terms of Use & Privacy Policy