Почему spring jpa игнорирует javax.persistence.FetchType.LAZY и javax.persistence.NamedEntityGraph?

У меня есть базовый пример "Отдел"/"Сотрудник".

Полный пример здесь (в основной ветке): https://github.com/granadacoder/jpa-simple-example-one.git

Если вы настроите 4 переменные среды (перечислены в README), код можно запускать/отлаживать.

Для моего метода «findAll()» я пытаюсь вернуть только скаляры объекта «Отдел». (ключ и имя). Ака, я НЕ хочу, чтобы какие-либо дочерние сотрудники были привязаны к отделу... когда я выполняю findAll().

Я пытался использовать имя EntityGraph, но оно не работает (я все еще получаю полный граф объектов)

Я также использую FetchType.LAZY. (имхо должно было хватить)... И я вообще не звоню (отдел).getEmployees.

Но я предпринял дополнительный шаг, определив и используя «departmentJustScalarsEntityGraphName».

    @EntityGraph("departmentJustScalarsEntityGraphName")
    List<Department> findAll();

Вышеприведенное загружает весь граф (все скаляры отдела И сотрудников) и делает это в порядке N+1. :(
@EntityGraph должен загружать только ключ и имя для метода findAll().

Если вы найдете комментарий к коду

/* right here, desperately hoping for each Department in the "entities" to NOT have employees hydrated */

вы можете поставить отладчик туда и посмотреть проблему.

Обратите внимание, что когда я выполняю "поиск по одному" (findById(key) или findDepartmentByDepartmentNameEquals(name))...... Мне DO нужны сотрудники. Таким образом, ответы, которые в широком смысле диссоциируют сотрудников, не идеальны.

Независимо от того, что я пробовал, я получаю проблему N + 1. (В выборке исходных данных есть три отдела.)

Hibernate: select department0_.DepartmentKey as departme1_0_, department0_.CreateOffsetDateTime as createof2_0_, department0_.DepartmentName as departme3_0_ from DepartmentTable department0_
Hibernate: select employees0_.DepartmentForeignKey as departme6_1_0_, employees0_.EmployeeKey as employee1_1_0_, employees0_.EmployeeKey as employee1_1_1_, employees0_.CreateOffsetDateTime as createof2_1_1_, employees0_.FirstName as firstnam3_1_1_, employees0_.LastName as lastname4_1_1_, employees0_.DepartmentForeignKey as departme6_1_1_, employees0_.Ssn as ssn5_1_1_ from EmployeeTable employees0_ where employees0_.DepartmentForeignKey=?
Hibernate: select employees0_.DepartmentForeignKey as departme6_1_0_, employees0_.EmployeeKey as employee1_1_0_, employees0_.EmployeeKey as employee1_1_1_, employees0_.CreateOffsetDateTime as createof2_1_1_, employees0_.FirstName as firstnam3_1_1_, employees0_.LastName as lastname4_1_1_, employees0_.DepartmentForeignKey as departme6_1_1_, employees0_.Ssn as ssn5_1_1_ from EmployeeTable employees0_ where employees0_.DepartmentForeignKey=?
Hibernate: select employees0_.DepartmentForeignKey as departme6_1_0_, employees0_.EmployeeKey as employee1_1_0_, employees0_.EmployeeKey as employee1_1_1_, employees0_.CreateOffsetDateTime as createof2_1_1_, employees0_.FirstName as firstnam3_1_1_, employees0_.LastName as lastname4_1_1_, employees0_.DepartmentForeignKey as departme6_1_1_, employees0_.Ssn as ssn5_1_1_ from EmployeeTable employees0_ where employees0_.DepartmentForeignKey=?

Вот основные компоненты кода:

Департамент.java

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.mycompany.organizationdemo.domain.constants.OrmConstants;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.NamedEntityGraphs;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.LinkedHashSet;
import java.util.Set;



@Entity
@NamedEntityGraphs({
@NamedEntityGraph(name = "departmentJustScalarsEntityGraphName", attributeNodes = {
        @NamedAttributeNode("departmentKey"),
        @NamedAttributeNode("departmentName")})
})
@Table(name = "DepartmentTable")
public class Department implements Serializable {

    @Id
    @Column(name = "DepartmentKey", unique = true)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long departmentKey;

    @Column(name = "DepartmentName", unique = true)
    private String departmentName;

    @Column(name = "CreateOffsetDateTime", columnDefinition = OrmConstants.OffsetDateTimeColumnDefinition)
    private OffsetDateTime createOffsetDateTime;

    //region Navigation


    @OneToMany(
            mappedBy = "parentDepartment",
            cascade = CascadeType.REMOVE,
            orphanRemoval = true,
            fetch = FetchType.LAZY /* Lazy or Eager here */
    )

    private Set<Employee> employees = new LinkedHashSet<>();
    //endregion

    public long getDepartmentKey() {
        return departmentKey;
    }

    public void setDepartmentKey(long departmentKey) {
        this.departmentKey = departmentKey;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    public OffsetDateTime getCreateOffsetDateTime() {
        return createOffsetDateTime;
    }

    public void setCreateOffsetDateTime(OffsetDateTime createOffsetDateTime) {
        this.createOffsetDateTime = createOffsetDateTime;
    }

    //region Navigation
    public Set<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(Set<Employee> employees) {
        this.employees = employees;
    }
    //endregion


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        Department that = (Department) o;

        return new org.apache.commons.lang3.builder.EqualsBuilder()
                .append(departmentKey, that.departmentKey)
                .append(departmentName, that.departmentName)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
                .append(departmentKey)
                .append(departmentName)
                .toHashCode();
    }
}

Сотрудник.java

import com.mycompany.organizationdemo.domain.constants.OrmConstants;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;
import java.time.OffsetDateTime;



@Entity
@Table(name = "EmployeeTable")
public class Employee implements Serializable {

    @Id
    @Column(name = "EmployeeKey", unique = true)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long employeeKey;

    @Column(name = "Ssn")
    private String ssn;

    @Column(name = "LastName")
    private String lastName;

    @Column(name = "FirstName")
    private String firstName;

    @Column(name = "CreateOffsetDateTime", columnDefinition = OrmConstants.OffsetDateTimeColumnDefinition)
    private OffsetDateTime createOffsetDateTime;

    //region Navigation

    @ManyToOne(fetch = FetchType.LAZY, targetEntity = Department.class)//, cascade = CascadeType.REMOVE)
    @JoinColumn(name = "DepartmentForeignKey")
    private Department parentDepartment;
    //endregion


    public long getEmployeeKey() {
        return employeeKey;
    }

    public void setEmployeeKey(long departmentKey) {
        this.employeeKey = departmentKey;
    }

    public String getSsn() {
        return ssn;
    }

    public void setSsn(String ssn) {
        this.ssn = ssn;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public OffsetDateTime getCreateOffsetDateTime() {
        return createOffsetDateTime;
    }

    public void setCreateOffsetDateTime(OffsetDateTime createOffsetDateTime) {
        this.createOffsetDateTime = createOffsetDateTime;
    }

    //region Navigation

    public Department getParentDepartment() {
        return parentDepartment;
    }

    public void setParentDepartment(Department parentDepartment) {
        this.parentDepartment = parentDepartment;
    }

    //endregion


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        Employee employee = (Employee) o;

        return new EqualsBuilder()
                .append(employeeKey, employee.employeeKey)
                .append(ssn, employee.ssn)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37)
                .append(employeeKey)
                .append(ssn)
                .toHashCode();
    }
}

Весна JPA

import com.mycompany.organizationdemo.domain.entities.Department;
import com.mycompany.organizationdemo.domaindatalayer.interfaces.IDepartmentRepository;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import javax.transaction.Transactional;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public interface DepartmentJpaRepository extends JpaRepository<Department, Long>, IDepartmentRepository {


    @EntityGraph("departmentJustScalarsEntityGraphName")
    List<Department> findAll();




    @Query("SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.departmentName = :departmentName") /* this works because departmentName is a UNIQUE constraint...otherwise it might give back duplicate parents (Departments) */
    Optional<Department> findDepartmentByDepartmentNameEquals(@Param("departmentName") String departmentName);

    /* note the below, this is "lookup strategy".  see https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods */
    Collection<Department> findByCreateOffsetDateTimeBefore(OffsetDateTime zdt);

    //@Query("SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.departmentKey IN ?1")  /* here a Query will bring back repeat parent (Department) rows */
    @EntityGraph(attributePaths = {"employees"})
    Collection<Department> findDepartmentByDepartmentKeyIn(Set<Long> departmentKeys);

    @Modifying
    @Transactional
    int deleteDepartmentByDepartmentKey(long departmentKey); /* suffers from N+1 problem */
}

и интерфейс репозитория (обычный jane)

import com.mycompany.organizationdemo.domain.entities.Department;

import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public interface IDepartmentRepository {

    List<Department> findAll();

    Optional<Department> findById(long key);

    Optional<Department> findDepartmentByDepartmentNameEquals(String departmentName);

    Collection<Department> findByCreateOffsetDateTimeBefore(OffsetDateTime zdt);

    Collection<Department> findDepartmentByDepartmentKeyIn(Set<Long> departmentKeys);

    Department save(Department item);

    int deleteDepartmentByDepartmentKey(long departmentKey);
}

Я нашел это:

JPARepository Spring Data: как условно получить дочерние элементы

и это:

https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs002.htm

Spring Boot довольно новый. (из настроек gradle в проекте)

    spring_plugin_version = '2.2.6.RELEASE'
    springBootVersion = '2.2.6.RELEASE'
    slf4jVersion = "1.7.25"
    javaxInjectVersion = "1"
    javaxPersistenceApiVersion = "2.2"
    junitVersion = "4.12"
    mockitoVersion = "3.3.0"
    jacksonAnnotationsVersion = "2.11.0"
    modelMapperVersion = "2.3.7"
    commonsLangVersion = '3.7'

PS

Я также использую трюк «преобразовать в Dto» (как показано здесь): https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application, но Департамент (orm-entity ) к этому моменту уже гидратирован. :(


person granadaCoder    schedule 02.06.2020    source источник
comment
Попробуйте добавить атрибуты @JsonIgnoreon, которые вы не должны загружать   -  person SSK    schedule 03.06.2020
comment
Спасибо за предложение... и я попробовал это... но это не сработало. @com.fasterxml.jackson.annotation.JsonIgnore @OneToMany(mappedBy = parentDepartment, cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) private Set‹Employee› employee = new LinkedHashSet‹›();   -  person granadaCoder    schedule 03.06.2020