Spring Data REST HATEOS:不是延迟加载

时间:2018-06-16 19:57:37

标签: java spring hibernate spring-data-rest spring-hateoas

问题

我定义了两个实体:SchoolDistrict。一个地区可以有很多学校,学校可以属于一个学区 在针对此端点GET执行http://localhost:8080/districts请求时,我希望获得所有区域的列表,而不会获取每个区域的相关学校集。但似乎无论我做什么,hibernate都会调用DB来为每个学校单独获取数据。

实体

学校

@Getter
@Setter
@NoArgsConstructor
@Entity
public class School {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Column(unique=true)
    private Long number;

    @NotNull
    @Column
    private String name;

    @NotNull
    private boolean closed;

    @Embedded
    private ContactInfo contactInfo;

    private String gradeLow;
    private String gradeHigh;
    private int enrollment;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "district_id")
    private District district;

} 

@Getter
@Setter
@NoArgsConstructor
@Entity
public class District {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(unique = true)
    private Integer number;

    private String  name;
    private String  type;
    private int     enrollment;
    private Date    updated;

    @Embedded
    private ContactInfo contactInfo;

    @Getter(AccessLevel.NONE)
    @JsonIgnore
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "district")
    private Set<School> schoolList;

}

日志输出

SELECT district0_.id          AS id1_5_, 
       district0_.city        AS city2_5_, 
       district0_.email       AS email3_5_, 
       district0_.fax         AS fax4_5_, 
       district0_.first_name  AS first_na5_5_, 
       district0_.last_name   AS last_nam6_5_, 
       district0_.name_prefix AS name_pre7_5_, 
       district0_.phone       AS phone8_5_, 
       district0_.state       AS state9_5_, 
       district0_.street      AS street10_5_, 
       district0_.title       AS title11_5_, 
       district0_.website     AS website12_5_, 
       district0_.zip         AS zip13_5_, 
       district0_.enrollment  AS enrollm14_5_, 
       district0_.NAME        AS name15_5_, 
       district0_.number      AS number16_5_, 
       district0_.type        AS type17_5_, 
       district0_.updated     AS updated18_5_ 
FROM   district district0_ 

SELECT schoollist0_.district_id AS distric20_7_0_, 
       schoollist0_.id          AS id1_7_0_, 
       schoollist0_.id          AS id1_7_1_, 
       schoollist0_.closed      AS closed2_7_1_, 
       schoollist0_.city        AS city3_7_1_, 
       schoollist0_.email       AS email4_7_1_, 
       schoollist0_.fax         AS fax5_7_1_, 
       schoollist0_.first_name  AS first_na6_7_1_, 
       schoollist0_.last_name   AS last_nam7_7_1_, 
       schoollist0_.name_prefix AS name_pre8_7_1_, 
       schoollist0_.phone       AS phone9_7_1_, 
       schoollist0_.state       AS state10_7_1_, 
       schoollist0_.street      AS street11_7_1_, 
       schoollist0_.title       AS title12_7_1_, 
       schoollist0_.website     AS website13_7_1_, 
       schoollist0_.zip         AS zip14_7_1_, 
       schoollist0_.district_id AS distric20_7_1_, 
       schoollist0_.enrollment  AS enrollm15_7_1_, 
       schoollist0_.grade_high  AS grade_h16_7_1_, 
       schoollist0_.grade_low   AS grade_l17_7_1_, 
       schoollist0_.NAME        AS name18_7_1_, 
       schoollist0_.number      AS number19_7_1_ 
FROM   school schoollist0_ 
WHERE  schoollist0_.district_id = ? 

SELECT schoollist0_.district_id AS distric20_7_0_, 
       schoollist0_.id          AS id1_7_0_, 
       schoollist0_.id          AS id1_7_1_, 
       schoollist0_.closed      AS closed2_7_1_, 
       schoollist0_.city        AS city3_7_1_, 
       schoollist0_.email       AS email4_7_1_, 
       schoollist0_.fax         AS fax5_7_1_, 
       schoollist0_.first_name  AS first_na6_7_1_, 
       schoollist0_.last_name   AS last_nam7_7_1_, 
       schoollist0_.name_prefix AS name_pre8_7_1_, 
       schoollist0_.phone       AS phone9_7_1_, 
       schoollist0_.state       AS state10_7_1_, 
       schoollist0_.street      AS street11_7_1_, 
       schoollist0_.title       AS title12_7_1_, 
       schoollist0_.website     AS website13_7_1_, 
       schoollist0_.zip         AS zip14_7_1_, 
       schoollist0_.district_id AS distric20_7_1_, 
       schoollist0_.enrollment  AS enrollm15_7_1_, 
       schoollist0_.grade_high  AS grade_h16_7_1_, 
       schoollist0_.grade_low   AS grade_l17_7_1_, 
       schoollist0_.NAME        AS name18_7_1_, 
       schoollist0_.number      AS number19_7_1_ 
FROM   school schoollist0_ 
WHERE  schoollist0_.district_id = ? 

SELECT schoollist0_.district_id AS distric20_7_0_, 
       schoollist0_.id          AS id1_7_0_, 
       schoollist0_.id          AS id1_7_1_, 
       schoollist0_.closed      AS closed2_7_1_, 
       schoollist0_.city        AS city3_7_1_, 
       schoollist0_.email       AS email4_7_1_, 
       schoollist0_.fax         AS fax5_7_1_, 
       schoollist0_.first_name  AS first_na6_7_1_, 
       schoollist0_.last_name   AS last_nam7_7_1_, 
       schoollist0_.name_prefix AS name_pre8_7_1_, 
       schoollist0_.phone       AS phone9_7_1_, 
       schoollist0_.state       AS state10_7_1_, 
       schoollist0_.street      AS street11_7_1_, 
       schoollist0_.title       AS title12_7_1_, 
       schoollist0_.website     AS website13_7_1_, 
       schoollist0_.zip         AS zip14_7_1_, 
       schoollist0_.district_id AS distric20_7_1_, 
       schoollist0_.enrollment  AS enrollm15_7_1_, 
       schoollist0_.grade_high  AS grade_h16_7_1_, 
       schoollist0_.grade_low   AS grade_l17_7_1_, 
       schoollist0_.NAME        AS name18_7_1_, 
       schoollist0_.number      AS number19_7_1_ 
FROM   school schoollist0_ 
WHERE  schoollist0_.district_id = ? 

SELECT schoollist0_.district_id AS distric20_7_0_, 
       schoollist0_.id          AS id1_7_0_, 
       schoollist0_.id          AS id1_7_1_, 
       schoollist0_.closed      AS closed2_7_1_, 
       schoollist0_.city        AS city3_7_1_, 
       schoollist0_.email       AS email4_7_1_, 
       schoollist0_.fax         AS fax5_7_1_, 
       schoollist0_.first_name  AS first_na6_7_1_, 
       schoollist0_.last_name   AS last_nam7_7_1_, 
       schoollist0_.name_prefix AS name_pre8_7_1_, 
       schoollist0_.phone       AS phone9_7_1_, 
       schoollist0_.state       AS state10_7_1_, 
       schoollist0_.street      AS street11_7_1_, 
       schoollist0_.title       AS title12_7_1_, 
       schoollist0_.website     AS website13_7_1_, 
       schoollist0_.zip         AS zip14_7_1_, 
       schoollist0_.district_id AS distric20_7_1_, 
       schoollist0_.enrollment  AS enrollm15_7_1_, 
       schoollist0_.grade_high  AS grade_h16_7_1_, 
       schoollist0_.grade_low   AS grade_l17_7_1_, 
       schoollist0_.NAME        AS name18_7_1_, 
       schoollist0_.number      AS number19_7_1_ 
FROM   school schoollist0_ 
WHERE  schoollist0_.district_id = ? 

....

如上所示,SELECT FROM school重复了数百次,尽管将schoolList配置为延迟加载。

版本:

springBootVersion = '1.4.2.RELEASE'  
hibernate-core:5.0.11
'org.springframework.boot:spring-boot-starter-data-jpa'
'org.springframework.boot:spring-boot-starter-data-rest'
'org.springframework.boot:spring-boot-starter-web'
'org.springframework.boot:spring-boot-starter-actuator'
'org.springframework.boot:spring-boot-starter-hateoas'
'org.springframework.boot:spring-boot-starter-security'

3 个答案:

答案 0 :(得分:1)

在尝试获取所有可用的学校时,看起来您的代码甚至无法使用@JsonIgnore字段district,您应该收到类似的内容:

  

com.fasterxml.jackson.databind.exc.InvalidDefinitionException:没有   为类找到序列化器   org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer而且没有   发现创建BeanSerializer的属性(以避免异常,   禁用SerializationFeature.FAIL_ON_EMPTY_BEANS)(通过引用   链:   的java.util.ArrayList [0] - &GT; com.mberazouski.stackoverflow.springboothibernate.model.School [&#34;区&#34;] - &GT; com.mberazouski.stackoverflow.springboothibernate.model.District _ _ $$ jvstad5_0 [ &#34;处理&#34;])     在   com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)   〜[杰克逊 - 数据绑定-2.9.6.jar:2.9.6]

但是如果你要添加这个注释 - 一切都应该按预期开始工作。所以应该添加的唯一变化应该是:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "district_id")
@JsonIgnore
private District district;

跟踪结果将是:

  

2018-06-17 17:27:56.431 DEBUG 25024 --- [nio-8080-exec-1]   org.hibernate.SQL:选择school0_.id为   id1_3_,school0_.closed为closed2_3_,school0_.district_id为   district8_3_,school0_.enrollment as enrollme3_3_,school0_.grade_high   作为grade_hi4_3_,school0_.grade_low作为grade_lo5_3_,school0_.name为   name6_3_,school0_.number as number7_3_ from school school0 _

enter image description here

与此相反,如果你要删除懒惰:

@ManyToOne
@JoinColumn(name = "district_id")
private District district;

我们将获得完整的结果: enter image description here

用于测试的控制器:

import com.mberazouski.stackoverflow.springboothibernate.model.School;
import com.mberazouski.stackoverflow.springboothibernate.repository.SchoolRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class SchoolController {
    @Autowired
    SchoolRepository schoolRepository;

    @GetMapping("/schools")
    public List<School> getAllSchools() {
        return schoolRepository.findAll();
    }
}

希望建议的更改能解决您的问题。

祝你好运。

答案 1 :(得分:1)

最后想出来了......为了简单起见,我最初发布这个问题并没有包含所有代码。不幸的是,错误的代码不是我最初发布的。

发生了什么

我为School对象创建了Projection,并将投影映射到SchoolRepository,如下面的代码所示。我思想投影仅在REST请求中明确指定时应用(即:/schools?projection=schoolExcerpt)但显然,投影一直在应用。由于某些原因,District对象将SchoolProjection应用于每个相关学校 - &gt;即使存在@JsonIgore注释,也会导致SQL查询单独从每个学校获取数据。

我如何解决问题

通过简单地移除投影,我能够检索所有Districts的列表,而无需进行数千次调用来解析每个关联的学校对象。

@RepositoryRestResource(excerptProjection = SchoolProjection.class)  //removing this line solves my problems
public interface SchoolRepository extends CrudRepository<School, Long>{    

}

PS

我实际上甚至不需要@JsonIgnore注释... HATEOAS足够智能,不包含相关对象 - 而是包含指向相关对象的链接。

答案 2 :(得分:0)

在您的情况下,您必须为区域服务调用创建另一个VIEW,或者您想尝试@JSONIGNORE schoolList属性以使LazyLoad生效(我猜你的表示层是JSON) 。

当你正在进行区域服务调用时,它正在返回带有lazyload ofcourse的District对象,但是一旦你的POJO到JSON转换由控制器处理,它就会调用School getter然后School对象的另一种方法,用于创建完整的数据表示,使hibernate也加载学校对象。

<强>更新

github中为您添加了示例代码以供参考。

您可以选择以下三个选项:

  • @JsonIgnore - 将完全忽略任何序列化/反序列化的属性。不确定这是不是你真正想要它。
  • @JsonManagedReference & @JsonManagedReference - 由于hibernate / table交叉引用结构,将避免json递归/ stackoverflow问题。
  • @JsonIgnoreProperties - 避免参考实体的json递归问题的另一种方法。

注意:FetchType.LAZY是默认设置,因此未明确说明。

快速参考的代码段:

@Getter
@Setter
@Entity
public class District {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private Integer number;

    private String  name;
    private String  type;
    private int     enrollment;
    private Date    updated;

    @Embedded
    private ContactInfo contactInfo;

    @OneToMany(mappedBy = "district")
    //@JsonManagedReference //to avoid JSON recursion solution 1
    //@JsonIgnoreProperties("district") //to avoid JSON recursion solution 2
    @JsonIgnore
    private Set<School> schools;

}


@Getter
@Setter
@Entity
public class School {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique=true, nullable = false)
    private Long number;

    @Column(nullable = false)
    private String name;

    private boolean closed;

    @Embedded
    private ContactInfo contactInfo;

    private String gradeLow;
    private String gradeHigh;
    private int enrollment;

    @ManyToOne
    @JoinColumn(name = "district_id", nullable = false)
    //@JsonBackReference //to avoid JSON recursion solution 1
    //@JsonIgnoreProperties("schools") //to avoid JSON recursion solution 2
    @JsonIgnore
    private District district;

}