Как избежать бесконечного цикла в Hibernate при получении двунаправленных коллекций?
Я пытаюсь заполнить некоторые объекты сущностей в очень простом примере Hibernate. Моя база данных состоит из двух таблиц: "Отделы" (Id, Name) и "Сотрудники" (Id, DepartmentsId, FirstName, LastName). Мой SQL-запрос - это просто соединение сотрудников с отделами слева.
Я настроил аннотации, как указано в документации Hibernate, но всякий раз, когда я пытаюсь сериализовать сущности, Hibernate заходит в бесконечный цикл и в конечном итоге выдает исключение StackOverFlowError. Кто-то, отвечая на другой мой вопрос, смог определить, что переполнение стека происходит, потому что объект "Отдел" содержит набор объектов "Сотрудник", каждый из которых содержит объект "Отдел", который содержит набор объектов Сотрудник и т. Д. и т.п.
Этот тип двунаправленных отношений должен быть допустимым в соответствии с документацией, указанной выше (параметр "mappedBy" в Department должен указывать на Hibernate; я также пытался использовать аннотацию "joinColumn", которая закомментирована в приведенном ниже коде) и другие вещи, которые я прочитал, указывают на то, что Hibernate должен быть достаточно умным, чтобы не зацикливаться в этой ситуации, но он не работает для моего примера. Все работает нормально, если я изменяю двунаправленное отношение на однонаправленное, удаляя объект Department из класса Employee, но, очевидно, это приводит к потере многих функций.
Я также попытался исключить аннотации для более старых файлов сопоставления xml и установить параметр "inverse" для дочерней таблицы, но он все еще вызывает ту же проблему. Как я могу заставить эти двунаправленные отношения работать так, как они должны работать?
Департамент:
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;
}
}
Работник:
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;
}
}
Руководитель отдела (содержит HQL-запрос):
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;
}
}
2 ответа
В общем, вы не должны сериализовать свои сущности. Круговые зависимости и прокси затрудняют это. Вместо этого вам следует вручную передать данные, которые нужно отправить, в DTO (новый класс только для данных) и вместо этого сериализовать его. У него не будет ленивых коллекций, прокси и прочего.
В качестве дополнения к верхнему ответу я сделал общий конвертер, который выполняет эту работу за меня, передавая значения сущностей в объект DTO, вам просто нужно создать поля dto с тем же именем из сопоставленной сущности.
Вот исходный код.
/ ** * Атрибуты, относящиеся к месту жительства, соответствуют требованиям, предъявляемым к объектам судьбы. Os * campos do objeto de destino que ja estiverem preenchidos nao serao replaceidos * * @param objetoOrigem * @param objetoDestino * @return * @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;
}