【问题标题】:Specifying whether or not to lazily load with Spring Data指定是否延迟加载 Spring Data
【发布时间】:2015-05-07 21:51:12
【问题描述】:

我在实体中有一个惰性获取类型集合。我正在使用 Spring Data (JpaRepository) 来访问实体。

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

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

我想要服务类中的两个函数,当前实现如下:

  1. “children”在获取父级时应该为空

    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 返回“父”实体时,抛出以下异常:

@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: 无法写入内容:未能延迟初始化集合 角色:com.entity.Parent.children,无法初始化代理 - 否 会话(通过引用链:com.entity.Parent["children"]); 嵌套异常是 com.fasterxml.jackson.databind.JsonMappingException: 懒惰失败 初始化角色集合:com.entity.Parent.children,不能 初始化代理 - 无会话(通过引用链: com.entity.Parent["children"])

【问题讨论】:

    标签: java spring hibernate jpa spring-data


    【解决方案1】:

    RestController 应该返回 ParentDTO 而不是 Parent 实体。 ParentDTO 可以填充到事务服务方法中。

    【讨论】:

    • 我们的实际实体远比我编写的示例代码复杂和庞大。 ParentDTO 对我们来说是太多的维护开销。
    • 对于实体,在将它们转换为 json 时可能会遇到循环依赖问题。
    • 嗯.. 所以我要么必须创建 DTO 要么完全删除孩子。循环依赖已通过注释解决。
    • JsonManagedReference 和 JsonBackReference 用于解决循环依赖问题
    • /parent/{parentId} 的调用者是否需要 Parent 实体的全部复杂性(相当大)?如果不是,那么绝对应该首选 DTO。
    【解决方案2】:

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

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

    getParentWithChildrenService 方法在事务内部运行,并在提交事务时关闭关联的 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. getParent
    2. getParentWithChildren

    这些方法可以由 Spring Data 提供。

    【讨论】:

    • 嗨,但我不想在 getParent() 方法中获得父母的孩子。我希望孩子们是空的。
    • 这不是 JPA 的工作方式,您不应该将集合设置为 null。您可以获取 Parant 并将其转换为没有子级的 DTO。
    • 知道了。在我们的案例中,获取集合是一个沉重的负担,而管理 DTO 也是一个沉重的负担。所以我们可能会采用 Alan Hay 的解决方案。
    • 好的。我很高兴你找到了预期的答案。干杯!
    【解决方案3】:

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

    public class Child {
        @ManyToOne
        private Parent parentId;
    }
    

    解决方案 1:您的代码实际上是正确的,只是您必须使用第二层 DTOs(简单的 POJO 类)将您的域层传输到客户端/浏览器。如果你不这样做,在你解决你的延迟异常之后,你会遇到从父级到子级的循环依赖问题,并且 JSON 编组器(杰克逊)将尝试编码 Child,然后是 Parent,然后再次是它的孩子,然后是他们的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:将 @JsonIgnore 用于您的公共属性 Parent.getChildren(),以便 Jackson 在编组 Parent 实例时不会尝试对子代进行编码。

    【讨论】:

    • 我曾经使用过 JsonManagedReference 和 JsonBackReference 来管理循环依赖的问题。 DTO 是唯一的选择吗?我们的实际实体非常庞大和复杂,维护 DTO 会导致巨大的开销。没有其他解决办法吗?
    • 我添加了另一个解决方案(使用@JsonIgnore)。
    • 并非如此,因为在一种方法中我们想要孩子,而从另一种方法中我们不需要。我认为 DTO 是唯一的出路。
    • 好吧,我没有在你的问题中看到那部分。在这种情况下,很明显您需要一个 DTO/Web 层(由您决定浏览器获取的内容)。
    • 随着我们的进步,我们可能会添加 DTO,但现在我们将使用 Alan Hay 的解决方案。
    【解决方案4】:

    如果您希望根据用例允许同一域模型的不同 JSON 表示,那么您可以查看以下内容,这将允许您在不需要 DTO 的情况下这样做:

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

    或者,另请参阅下面的“Spring Data REST 中的投影”部分

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

    【讨论】:

    • 我认为链接 #1 在我们的案例中是完美的。谢谢。
    猜你喜欢
    • 2018-08-05
    • 2017-09-14
    • 2018-11-26
    • 1970-01-01
    • 1970-01-01
    • 2013-11-02
    • 2022-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多