JPA 的 fetch 策略究竟控制什么?我无法发现急切和懒惰之间的任何区别。在这两种情况下,JPA/Hibernate 都不会自动加入多对一关系。
示例:人员只有一个地址。一个地址可以属于很多人。 JPA 带注释的实体类如下所示:
@Entity
public class Person {
@Id
public Integer id;
public String name;
@ManyToOne(fetch=FetchType.LAZY or EAGER)
public Address address;
}
@Entity
public class Address {
@Id
public Integer id;
public String name;
}
如果我使用 JPA 查询:
select p from Person p where ...
JPA/Hibernate 生成一个 SQL 查询以从 Person 表中进行选择,然后为每个人生成一个不同的地址查询:
select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3
这对于大型结果集非常不利。如果有 1000 人,它会生成 1001 个查询(1 个来自 Person,1000 个来自 Address)。我知道这一点是因为我正在查看 MySQL 的查询日志。我的理解是,将地址的获取类型设置为 eager 会导致 JPA/Hibernate 自动使用连接进行查询。但是,无论获取类型如何,它仍然会为关系生成不同的查询。
只有当我明确告诉它加入时,它才会真正加入:
select p, a from Person p left join p.address a where ...
我在这里错过了什么吗?我现在必须手动编写每个查询的代码,以便它离开多对一关系。我在 MySQL 中使用 Hibernate 的 JPA 实现。
编辑:看起来(参见 Hibernate FAQ here 和 here)FetchType
不会影响 JPA 查询。所以就我而言,我已经明确告诉它加入。
JPA 没有提供任何关于映射注释以选择获取策略的规范。通常,可以通过以下任何一种方式获取相关实体
SELECT => 一次查询根实体 + 一次查询相关映射实体/每个根实体的集合 = (n+1) 次查询
SUBSELECT => 一个查询根实体 + 第二个查询相关映射实体/在第一个查询中检索到的所有根实体的集合 = 2 个查询
JOIN => 一个查询来获取根实体及其所有映射实体/集合 = 1 个查询
所以 SELECT
和 JOIN
是两个极端,而 SUBSELECT
介于两者之间。可以根据她/他的领域模型选择合适的策略。
默认情况下,JPA/EclipseLink 和 Hibernate 都使用 SELECT
。这可以通过使用覆盖:
@Fetch(FetchMode.JOIN)
@Fetch(FetchMode.SUBSELECT)
在休眠中。它还允许使用 @Fetch(FetchMode.SELECT)
显式设置 SELECT
模式,可以通过使用批量大小进行调整,例如 @BatchSize(size=10)
。
EclipseLink 中对应的注解是:
@JoinFetch
@BatchFetch
“MXC”是对的。 fetchType
只是指定何时应该解决关系。
要通过使用外连接来优化急切加载,您必须添加
@Fetch(FetchMode.JOIN)
到你的领域。这是一个特定于休眠的注释。
fetchType 属性控制在获取主实体时是否立即获取带注释的字段。它不一定规定如何构造 fetch 语句,实际的 sql 实现取决于您使用的提供程序 toplink/hibernate 等。
如果您设置 fetchType=EAGER
这意味着注释字段与实体中的其他字段同时填充其值。因此,如果您打开实体管理器检索您的人员对象,然后关闭实体管理器,随后执行 person.address 将不会导致引发延迟加载异常。
如果您设置 fetchType=LAZY
,则该字段仅在访问时才会填充。如果您已经关闭了实体管理器,那么如果您执行 person.address 将引发延迟加载异常。要加载字段,您需要使用 em.merge() 将实体放回 entitymangers 上下文,然后进行字段访问,然后关闭 entitymanager。
在构建包含客户订单集合的客户类时,您可能需要延迟加载。如果您在想要获取客户列表时检索了客户的每个订单,那么当您只查找客户姓名和联系方式时,这可能是一项昂贵的数据库操作。最好将数据库访问权限留到以后。
对于问题的第二部分 - 如何让休眠以生成优化的 SQL?
Hibernate 应该允许您提供有关如何构造最有效查询的提示,但我怀疑您的表构造有问题。表中是否建立了关系? Hibernate 可能已经决定一个简单的查询将比一个连接更快,尤其是在缺少索引等的情况下。
尝试:
select p from Person p left join FETCH p.address a where...
它对我来说与 JPA2/EclipseLink 类似,但似乎此功能存在于 JPA1 too 中:
如果您使用 EclipseLink 而不是 Hibernate,您可以通过“查询提示”优化您的查询。请参阅 Eclipse Wiki 中的这篇文章:EclipseLink/Examples/JPA/QueryOptimization。
有一章是关于“联读”的。
加入你可以做很多事情(使用eclipselink)
在 jpql 你可以做左连接提取
在命名查询中,您可以指定查询提示
在 TypedQuery 你可以说类似 query.setHint("eclipselink.join-fetch", "e.projects.milestones");
还有批量获取提示 query.setHint("eclipselink.batch", "e.address");
看
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
除了 Person 类有一个嵌入的键类之外,我确实遇到了这个问题。我自己的解决方案是将它们加入查询并删除
@Fetch(FetchMode.JOIN)
我的嵌入式 id 类:
@Embeddable
public class MessageRecipientId implements Serializable {
@ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
@JoinColumn(name="messageId")
private Message message;
private String governmentId;
public MessageRecipientId() {
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public String getGovernmentId() {
return governmentId;
}
public void setGovernmentId(String governmentId) {
this.governmentId = governmentId;
}
public MessageRecipientId(Message message, GovernmentId governmentId) {
this.message = message;
this.governmentId = governmentId.getValue();
}
}
我想到两件事。
首先,你确定你的地址是ManyToOne吗?这意味着多个人将拥有相同的地址。如果它是为其中之一编辑的,它将为所有它们进行编辑。这是你的意图吗? 99% 的时间地址是“私人的”(从某种意义上说,它们只属于一个人)。
其次,您是否对 Person 实体有任何其他急切的关系?如果我没记错的话,Hibernate 只能处理一个实体上的一个急切关系,但这可能是过时的信息。
我这么说是因为您对这应该如何工作的理解从我现在的位置来看基本上是正确的。
不定期副业成功案例分享