简体   繁体   中英

Loading Self Referential Relationships Lazily with Spring Data JPA and Hibernate

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM