Suppose I have the following Entity
defined that represents and Employee
and as you can see there is a self-referential relationship between manager
and employees
defined with the @ManyToOne
and @OneToMany
relationships respectively. Additionally, please note that the employees
attribute has been annotated describing the loading behavior as LAZY
.
package demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.*;
import java.util.*;
@Entity
public class Employee {
private static final Logger LOGGER = LoggerFactory.getLogger(Employee.class);
@Id
@GeneratedValue
private long id;
private String name;
@ManyToOne
private Employee manager;
@OneToMany(mappedBy = "manager", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Employee> employees;
private Employee() {
}
public Employee(String name) {
this(name, new Employee[]{});
}
public Employee(String name, Employee... employees) {
this.name = Objects.requireNonNull(name);
this.employees = Arrays.asList(employees);
}
public void setManager(Employee manager) {
this.manager = manager;
}
public boolean isDirectManagerOf(Employee employee) {
for(Employee myEmployee: employees) {
if ( myEmployee.equals(employee) ) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return name.equals(employee.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
}
Now suppose I have a Repository
interface that looks like the following:
package demo;
import org.springframework.data.repository.CrudRepository;
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
Employee findByName(String name);
}
I'd like to understand if one Employee
is a direct manager of another Employee
. The code below defines an initial hierarchy and tests the behavior.
package demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import javax.persistence.*;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@SpringBootApplication
public class JpaCircularReferenceTestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaCircularReferenceTestApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(JpaCircularReferenceTestApplication.class, args);
EmployeeRepository employeeRepository = context.getBean(EmployeeRepository.class);
Employee emp1 = new Employee("EMP1");
Employee emp2 = new Employee("EMP2");
Employee emp3 = new Employee("EMP3");
Employee emp4 = new Employee("EMP4");
Employee director1 = new Employee("DIRECTOR1", emp1, emp2);
Employee director2 = new Employee("DIRECTOR2", emp3, emp4);
Employee vp1 = new Employee("VP1", director1);
Employee vp2 = new Employee("VP2", director2);
Employee ceo = new Employee("CEO", vp1, vp2);
vp1.setManager(ceo);
vp2.setManager(ceo);
director1.setManager(vp1);
director2.setManager(vp2);
emp1.setManager(director1);
emp2.setManager(director1);
emp3.setManager(director2);
emp4.setManager(director2);
employeeRepository.save(ceo);
Employee ceoFromRepository = employeeRepository.findByName("CEO");
Employee vp1FromRepository = employeeRepository.findByName("VP1");
LOGGER.info("Is {} direct manager of {}? {}", ceoFromRepository, vp1FromRepository, ceoFromRepository.isDirectManagerOf(vp1FromRepository));
}
}
When the relationship is set to lazily load this fails with an exception:
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: demo.Employee.employees, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:576)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:215)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:555)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:143)
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:294)
at demo.Employee.isDirectManagerOf(Employee.java:51)
at demo.JpaCircularReferenceTestApplication.main(JpaCircularReferenceTestApplication.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
When the relationship is set to load eagerly there is no exception. Unfortunately in a real use case the performance of eagerly loading the data is unacceptable.
What is the proper way to be able to run a method like isDirectManagerOf
when you want to lazily load the self-referential relationship?
Here is the pom.xml
file for reference:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.test</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JPA Circular Reference Test</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
As was mentioned above by M. Deinum, the following adjustments were made and ended up resolving the problem. First I added an EmployeeService
class annotated with @Transactional
as follows:
package demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public boolean isManagerOf(String managerName, String employeeName) {
Employee manager = employeeRepository.findByName(managerName);
Employee employee = employeeRepository.findByName(employeeName);
return manager.isDirectManagerOf(employee);
}
}
And then changed the "main" class as follows:
package demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class JpaCircularReferenceTestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaCircularReferenceTestApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(JpaCircularReferenceTestApplication.class, args);
EmployeeRepository employeeRepository = context.getBean(EmployeeRepository.class);
Employee emp1 = new Employee("EMP1");
Employee emp2 = new Employee("EMP2");
Employee emp3 = new Employee("EMP3");
Employee emp4 = new Employee("EMP4");
Employee director1 = new Employee("DIRECTOR1", emp1, emp2);
Employee director2 = new Employee("DIRECTOR2", emp3, emp4);
Employee vp1 = new Employee("VP1", director1);
Employee vp2 = new Employee("VP2", director2);
Employee ceo = new Employee("CEO", vp1, vp2);
vp1.setManager(ceo);
vp2.setManager(ceo);
director1.setManager(vp1);
director2.setManager(vp2);
emp1.setManager(director1);
emp2.setManager(director1);
emp3.setManager(director2);
emp4.setManager(director2);
employeeRepository.save(ceo);
EmployeeService employeeService = context.getBean(EmployeeService.class);
LOGGER.info("Is {} direct manager of {}? {}", "CEO", "VP1", employeeService.isManagerOf("CEO", "VP1"));
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.