How to avoid an infinite loop in Hibernate when dialing bidirectional collections?

I am trying to populate some object 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 just a left-handed connection of employees with departments.

I created the annotations specified in the Hibernate documentation , but whenever I try to serialize objects, Hibernate goes into an infinite loop and ultimately throws a StackOverFlowError exception. Someone who answered my other question was able to determine that the stack overflow occurs because the Department object contains a set of Employee objects, each of which contains a Department object that contains a set of Employee objects, etc. . and etc.

This type of bidirectional communication should be legal according to the documentation mentioned above (the "mappedBy" parameter in the department should indicate Hibernate, I also tried using the "joinColumn" annotation, which is commented out in the code below) and other things that I read indicate that Hibernate should be smart enough not to go into an infinite loop in this situation, but it does not work for my example. Everything works fine if I change the bidirectional relationship with unidirectional relationships by removing the department object from the Employee class, but obviously this leads to a loss of a lot of functionality.

I also tried to discard the annotations for the old xml mapping files and setting the β€œreverse” parameter for the child table, but it still causes the same problem. How can I make these bidirectional relationships work the way they should 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 an 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;
}
}
+3
source share
2 answers

In general, you should not serialize your entities. Circular dependencies and proxies make this harder. Instead, you must manually transfer the data you need to send to the DTO (a new class for data only), and serialize it instead. He will not have lazy collections, proxies, and much more.

+6
source

, , DTO, dto .

.

/** * Atribui os valores de campos de um objeto para um outro objeto de destino.    * campos do objeto de destino que ja estiverem preenchidos nao serao substituidos    *    * @param objetoOrigem    * @param objetoDestino    * @    * @throws NegocioException    */

public static <T1, T2> T2  convertEntity(T1 objetoOrigem, T2 objetoDestino) throws NegocioException {

    if (objetoOrigem != null && objetoDestino != null) {
        Class<? extends Object> classe = objetoOrigem.getClass();
        Class<? extends Object> classeDestino = objetoDestino.getClass();

        Field[] listaCampos = classe.getDeclaredFields();
        for (int i = 0; i < listaCampos.length; i++) {
            Field campo = listaCampos[i];
            try {
                Field campoDestino = classeDestino.getDeclaredField(campo.getName());
                campo.setAccessible(true);
                campoDestino.setAccessible(true);
                atribuiValorAoDestino(objetoOrigem, objetoDestino, campo, campoDestino);
            } catch (NoSuchFieldException e) {
                LOGGER.log(Logger.Level.TRACE, (Object) e);
                continue;
            } catch (IllegalArgumentException | IllegalAccessException e) {
                LOGGER.error(e.getMessage(), e);
                throw new NegocioException(e.getMessage(), EnumTypeException.ERROR);
            }
        }
    }
    return objetoDestino;
}
0

All Articles