指定是否延迟加载 Spring 数据

Specifying whether or not to lazily load with Spring Data

我在一个实体中有一个延迟获取类型的集合。我正在使用 Spring 数据 (JpaRepository) 来访问实体。

@Entity
public class Parent{
@Id
private Long id;

    @OneToMany(mappedBy = "parentId", fetch = FetchType.LAZY)
    private Set<Child> children;
}

我想在服务中使用两个功能 class,目前的实现如下:

  1. "children" 获取父项时应为 null

    public Parent getParent(Long parentId){
        return repo.findOne(parentId);
    }
    
  2. "children"取父时要填:

     public Parent getParentWithChildren(Long parentId){
         Parent p = repo.findOne(parentId);
         Hibernate.initialize(p.children);
         return p;
    }
    

从 RestController 返回 "Parent" 实体时,抛出以下异常:

@RequestMapping("/parent/{parentId}")
public Parent getParent(@PathVariable("parentId") Long id)
{
    Parent p= parentService.getParent(id);//ok till here
    return p;//error thrown when converting to JSON
}

org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: failed to lazily initialize a collection of role: com.entity.Parent.children, could not initialize proxy - no Session (through reference chain: com.entity.Parent["children"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.entity.Parent.children, could not initialize proxy - no Session (through reference chain: com.entity.Parent["children"])

RestController 应该 return 一个 ParentDTO 而不是 Parent 实体。 ParentDTO 可以在事务服务方法中填充。

抛出异常是因为 JSON 序列化程序要求所有属性都已初始化。因此,所有需要 return a Parent 的 REST 控制器都必须先初始化属性:

@RequestMapping("/parent/{parentId}")
public Parent getParent(@PathVariable("parentId") Long id) {
    return parentService.getParentWithChildren(id);
}

getParentWithChildren服务方法在运行事务内部,提交事务时关联的Hibernate Session关闭。这意味着您必须在 Hibernate Session 仍处于打开状态时(在 Service 方法内)初始化所有属性。

您还可以使用 Spring Data entity graph 支持:

@Entity
@NamedEntityGraphs(@NamedEntityGraph(name = "Parent.children", attributeNodes = @NamedAttributeNode("children")))
public class Parent{
@Id
private Long id;

    @OneToMany(mappedBy = "parentId", fetch = FetchType.LAZY)
    private Set<Child> children;
}

并且 getParentWithChildren 方法变为:

@Repository
public interface ParentRepository extends CrudRepository<Parent, Long> {

    @EntityGraph(value = "Parent.children", type = EntityGraphType.LOAD)
    Parent getParentWithChildren(Long parentId);
}

因此,您甚至不需要实施:

  1. 得到Parent
  2. 得到ParentWithChildren

这些方法可以由 Spring 数据提供。

首先,您没有向我们展示 Child Java class:我希望 属性 被称为 parentId 而不是 parent:

public class Child {
    @ManyToOne
    private Parent parentId;
}

解决方案 1:您的代码实际上是正确的,只是您必须使用第二层 DTOs(简单的 POJO classes)将您的域层传输到 client/browser.如果你不这样做,在你解决延迟异常之后,你会遇到从 Parent 到 Child 的循环依赖问题,并且 JSON 编组器(Jackson)将尝试编码a Child,然后是 Parent,然后是 children,然后是 Parent,依此类推。 DTO 的一个例子是:

public class ParentDto {
    private Long id;
    private String prop1;

    public ParentDto(Parent parent) {
            this.id = parent.id;
            this.prop1 = parent.prop1;
            //...other properties
    }

    //here come all getters for the properties defined above.
}

解决方案 2:为您的 public 属性 Parent.getChildren() 使用 @JsonIgnore,这样 Jackson 在编组Parent 实例。

如果您希望根据用例允许不同的 JSON 表示同一域模型,那么您可以查看以下内容,无需 DTO 即可实现:

https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

或者,另请参阅以下

中的 'Projections in Spring Data REST' 部分

https://spring.io/blog/2014/05/21/what-s-new-in-spring-data-dijkstra#projections-in-spring-data-rest