禁用 Hibernate 的递归获取

Posted

技术标签:

【中文标题】禁用 Hibernate 的递归获取【英文标题】:Disabling Hibernate's recursive fetch 【发布时间】:2017-06-26 06:57:16 【问题描述】:

当前堆栈:

Spring Boot 1.5.1 Spring Data JPA 1.11.0 休眠核心 5.2.6

假设我们有以下@Entity 结构

@Entity
class Root 

    @Id
    private Long id;

    @OneToMany
    @JoinColumn(name = "root_id")
    private Set<Child> children


@Entity
class Child 

    @Id
    private Long id;

    @OneToMany
    @JoinColumn(name = "child_id")
    private Set<Grandchild> grandchildren;


@Entity
class Grandchild 

    @Id
    private Long id;

当我查询所有/特定的 Root 对象时,Hibernate 仅从相应的表中选择,结果对象的 children Set 是 null 一个 Hibernate 代理 - 应该是这样。

当我调用 getChildren() 时,Hibernate 正确地初始化了集合,但也(毫无根据地)获取了每个 Child 对象的 grandchildren 集合。

有人能解释一下为什么会发生这种递归提取吗?有没有办法禁用它?


我做了更多的挖掘,这就是我想出的:它似乎与 Hibernate 映射 @OneToMany 的方式有关,具体取决于目标集合是 List 还是 Set

private final RootRepo repo;

如果集合是Sets

public void test() 
    List<Root> all = repo.findAll(); // SELECT root0_.* FROM root root0_

    all.forEach(root -> 
        System.out.println(root.getChildren() == null); // false
        System.out.println(Hibernate.isInitialized(root.getChildren())); // false

        root.getChildren().forEach(child -> 
            // SELECT child0_.* FROM children child0_
            // SELECT grandchild0_.* FROM grandchildren grandchild0_
            System.out.println(child.getGrandchildren() == null); // false
            System.out.println(Hibernate.isInitialized(child.getGrandchildren())); // true

            child.getGrandChildren().forEach(grandchild -> );
        );
    );

然而Lists

public void test() 
    List<Root> all = repo.findAll(); // SELECT root0_.* FROM root root0_

    all.forEach(root -> 
        System.out.println(root.getChildren() == null); // false
        System.out.println(Hibernate.isInitialized(root.getChildren())); // false

        root.getChildren().forEach(child -> 
            // SELECT child0_.* FROM children child0_
            System.out.println(child.getGrandchildren() == null); // false
            System.out.println(Hibernate.isInitialized(child.getGrandchildren())); // false

            child.getGrandChildren().forEach(grandchild -> 
                // SELECT grandchild0_.* FROM grandchildren grandchild0_
            );
        );
    );


我是一个可以证明的白痴。

我正在使用 Lombok 为我的 POJO 生成 getter/setter 等,它的 @EqualsAndHashCode 注释的默认实现会生成这两种方法,同时考虑到每个字段......包括子集合。

【问题讨论】:

即使这是默认行为,您也应该在 @OneToMany 注释中明确添加值 fetch = FetchType.LAZY。这可能有助于避免这种递归获取 实际上是否有对数据库的调用.. 还是将集合创建为没有任何调用的代理? private Set&lt;Child&gt; children 永远不会为空。你是如何加载这些实例的? @AlanHay 确实不是 null,我说错了。 所以解决方案是拥有一个没有子集合的等号和哈希码? 【参考方案1】:

我很惊讶 Root 的孩子实际上是空的。

它在您的情况下的工作方式(请仔细检查子项是否实际设置为 null)是当您访问 getChildren() 时(例如通过调用 size() 来获取该集合)来自数据库及其所有急切的依赖项。

所有惰性依赖项(在此特定情况下为孙子项)都被实例化为代理对象,但不应在数据库上为这些对象执行 sql 查询(请检查)。

另外

它从来没有发生在我身上,只是需要记住的一点点。根据 JPA,延迟加载功能只是对持久性提供者的提示。即使您将 fetchType 设置为 LAZY 或通常您希望默认情况下延迟加载集合依赖项(这可以在配置会话工厂时完成),实现仍可能决定执行 EAGER fetch:

定义从数据库中获取数据的策略。渴望 策略是对持久性提供程序运行时的要求,它 必须急切地获取数据。 LAZY 策略是对 持久性提供程序运行时,应在以下情况下延迟获取数据 它首先被访问。允许实现急切地获取 已指定 LAZY 策略提示的数据。

【讨论】:

确实不是null,我说错了。我还更新了我的原始问题,提供了一些关于查询和执行时间的信息。 是的.. 现在它是有道理的.. 它已经为 Set 初始化了,因为当您添加到 Set 时,java 调用 equals 和 hashCode 方法(它不是 List 的情况)..你那里有很好的案例..

以上是关于禁用 Hibernate 的递归获取的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate入门-----Hiberna核心文件详解

求教第一个hibernae程序的配置文件hibernate.cfg.xml出错了,求帮忙解决,如下

Hibernae

Hibernaate 详解

hibernate

hibernate ThreadLocal