禁用 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 是 一个 Hibernate 代理 - 应该是这样。 null
当我调用 getChildren()
时,Hibernate 正确地初始化了集合,但也(毫无根据地)获取了每个 Child
对象的 grandchildren
集合。
有人能解释一下为什么会发生这种递归提取吗?有没有办法禁用它?
我做了更多的挖掘,这就是我想出的:它似乎与 Hibernate 映射 @OneToMany
的方式有关,具体取决于目标集合是 List
还是 Set
。
private final RootRepo repo;
如果集合是Set
s
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 -> );
);
);
然而,List
s
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<Child> children
永远不会为空。你是如何加载这些实例的?
@AlanHay 确实不是 null
,我说错了。
所以解决方案是拥有一个没有子集合的等号和哈希码?
【参考方案1】:
我很惊讶 Root 的孩子实际上是空的。
它在您的情况下的工作方式(请仔细检查子项是否实际设置为 null)是当您访问 getChildren() 时(例如通过调用 size() 来获取该集合)来自数据库及其所有急切的依赖项。
所有惰性依赖项(在此特定情况下为孙子项)都被实例化为代理对象,但不应在数据库上为这些对象执行 sql 查询(请检查)。
另外
它从来没有发生在我身上,只是需要记住的一点点。根据 JPA,延迟加载功能只是对持久性提供者的提示。即使您将 fetchType 设置为 LAZY 或通常您希望默认情况下延迟加载集合依赖项(这可以在配置会话工厂时完成),实现仍可能决定执行 EAGER fetch:
定义从数据库中获取数据的策略。渴望 策略是对持久性提供程序运行时的要求,它 必须急切地获取数据。 LAZY 策略是对 持久性提供程序运行时,应在以下情况下延迟获取数据 它首先被访问。允许实现急切地获取 已指定 LAZY 策略提示的数据。
【讨论】:
确实不是null
,我说错了。我还更新了我的原始问题,提供了一些关于查询和执行时间的信息。
是的.. 现在它是有道理的.. 它已经为 Set 初始化了,因为当您添加到 Set 时,java 调用 equals 和 hashCode 方法(它不是 List 的情况)..你那里有很好的案例..以上是关于禁用 Hibernate 的递归获取的主要内容,如果未能解决你的问题,请参考以下文章