如何使 JPA OneToOne 关系变得懒惰

新手上路,请多包涵

在我们正在开发的这个应用程序中,我们注意到视图特别慢。我分析了视图并注意到休眠执行的一个查询花费了 10 秒,即使数据库中只有两个对象要获取也是如此。所有 OneToManyManyToMany 关系都很懒惰,所以这不是问题所在。在检查正在执行的实际 SQL 时,我注意到查询中有超过 80 个连接。

进一步检查问题,我注意到问题是由 OneToOneManyToOne 实体类之间的深层关系引起的。所以,我想,我会让他们变得懒惰,这应该可以解决问题。但是注释 @OneToOne(fetch=FetchType.LAZY)@ManyToOne(fetch=FetchType.LAZY) 似乎不起作用。要么我得到一个异常,要么它们实际上没有被代理对象替换,因此变得懒惰。

任何想法如何让这个工作?请注意,我不使用 persistence.xml 来定义关系或配置细节,一切都是在 java 代码中完成的。

原文由 KCL 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 953
2 个回答

首先,对 KLE 的回答做一些澄清:

  1. 不受约束的(可空的)一对一关联是唯一一种在没有字节码检测的情况下无法代理的关联。这样做的原因是所有者实体必须知道关联属性是否应该包含代理对象或 NULL,并且由于通常通过共享 PK 一对一映射,它无法通过查看其基表的列来确定,因此它无论如何都必须急切地获取代理毫无意义。这里有 更详细 的解释。

  2. 多对一关联(当然还有一对多)不会遇到这个问题。所有者实体可以轻松检查自己的 FK(在一对多的情况下,最初创建空集合代理并按需填充),因此关联可以是惰性的。

  3. 用一对多代替一对一几乎不是一个好主意。您可以将其替换为独特的多对一,但还有其他(可能更好)选项。

Rob H. 有一个有效的观点,但是您可能无法根据您的模型实现它(例如,如果您的一对一关联 可以为 空)。

现在,就最初的问题而言:

A) @ManyToOne(fetch=FetchType.LAZY) 应该工作得很好。您确定它没有在查询本身中被覆盖吗?可以在 HQL 中指定 join fetch 和/或通过 Criteria API 显式设置获取模式,这将优先于类注释。如果不是这种情况并且您仍然遇到问题,请发布您的类、查询和生成的 SQL 以进行更切题的对话。

B) @OneToOne 比较棘手。如果它绝对不可为空,请采纳 Rob H. 的建议并将其指定为:

 @OneToOne(optional = false, fetch = FetchType.LAZY)

否则,如果您可以更改数据库(将外键列添加到所有者表),请这样做并将其映射为“已加入”:

 @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

在其他实体中:

 @OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

如果您不能这样做(并且不能忍受急切的获取),字节码检测是您唯一的选择。但是,我必须同意 CPerkins 的观点——如果你有 80 个!!! 由于急切的 OneToOne 协会而加入,你遇到了比这更大的问题:-)

原文由 ChssPly76 发布,翻译遵循 CC BY-SA 4.0 许可协议

除非您正在使用字节码增强,否则您不能延迟获取父端 @OneToOne 关联。

但是,大多数情况下,如果您在子端使用 @MapsId ,您甚至不需要父端关联:

 @Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

使用 @MapsId ,子表中的 id 属性同时作为父表主键的主键和外键。

因此,如果您有对父实体 Post 实体的引用,则可以使用父实体标识符轻松获取子实体:

 PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);

这样,您就不会遇到可能由父端的 mappedBy @OneToOne 关联引起的 N+1 查询问题。

原文由 Vlad Mihalcea 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题