How to avoid endless loop in Hibernate when fetching bidirectional collections?
hibernate, java, struts2
Solution
In general, you should not serialize your entities. Circular dependencies and proxies make that hard. Instead, you should manually transfer the data you need to send to a DTO (a new data-only class), and serialize it instead. It won't have the lazy collections, proxies, and whatnot.
Problem
I am trying to populate some entity objects in a very simple Hibernate example. My database consists of two tables, "Departments" (Id, Name) and "Employees" (Id, DepartmentsId, FirstName, LastName). My SQL query is simply a left join of Employees to Departments. I have set up the annotations as specified in the Hibernate documentation, but whenever I try to serialize the entities Hibernate goes into an endless loop and eventually throws a StackOverFlowError exception. Someone answering another question of mine was able to determine that the stack overflow is happening because the "Department" object contains a set of "Employee" objects, which each contain a "Department" object, which contains a set of Employee objects, etc. etc. This type of bidirectional relationship is supposed to be legal as per the documentation linked above (the "mappedBy" parameter in Department is supposed to clue Hibernate in; I have also tried using the "joinColumn" annotation that is commented out in the code below), and other things I have read indicate the Hibernate is supposed to be smart enough not to go into an endless loop in this situation, but it is not working for my example. Everything works fine if I change the bidirectional relationship to a unidirectional relationship by removing the Department object from the Employee class, but obviously this causes the loss of a lot of functionality. I have also tried foregoing the annotations for the older xml mapping files and setting the "inverse" parameter for the child table, but it still produces the same problem. How can I get this bidirectional relationship working the way it is supposed to work? Department: ``` package com.test.model; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.JoinTable; import static javax.persistence.GenerationType.IDENTITY; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.JoinColumn; import org.hibernate.Hibernate; import org.hibernate.proxy.HibernateProxy; @Entity @Table(name="Departments" ,catalog="test" ) public class Department implements java.io.Serializable { private Integer id; private String name; public Set<Employee> employees = new HashSet<Employee>(0); public Department() { } public Department(String name) { this.name = name; } public Department(String name, Set employees) { this.name = name; this.employees = employees; } @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; } @Column(name="Name", nullable=false) public String getName() { return this.name; } public void setName(String name) { this.name = name; } @OneToMany(fetch=FetchType.LAZY, mappedBy="department") /*@OneToMany @JoinColumn(name="DepartmentsId")*/ public Set<Employee> getEmployees() { return this.employees; } public void setEmployees(Set employees) { this.employees = employees; } } ``` Employee: ``` package com.test.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.JoinTable; import static javax.persistence.GenerationType.IDENTITY; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="Employees" ,catalog="test" ) public class Employee implements java.io.Serializable { private Integer id; private Department department; private String firstName; private String lastName; public Employee() { } public Employee(Department department, String firstName, String lastName) { this.department = department; this.firstName = firstName; this.lastName = lastName; } @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 @JoinColumn(name="DepartmentsId", nullable=false, insertable=false, updatable=false) public Department getDepartment() { return this.department; } public void setDepartment(Department department) { this.department = department; } @Column(name="FirstName", nullable=false) public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @Column(name="LastName", nullable=false) public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } ``` Department Manager (Contains the HQL query): ``` package com.test.controller; import java.util.Collections; import java.util.List; import java.util.Iterator; import org.hibernate.Criteria; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import com.test.model.Department; import com.test.util.HibernateUtil; public class DepartmentManager extends HibernateUtil { public List<Department> list() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List<Department> set = null; try { Query q = session.createQuery("FROM Department d JOIN FETCH d.employees e"); q.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); set = (List<Department>) q.list(); } catch (HibernateException e) { e.printStackTrace(); session.getTransaction().rollback(); } session.getTransaction().commit(); return set; } } ```