Java 如何使 JPA OneToOne 关系变得懒惰

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1444227/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 12:21:06  来源:igfitidea点击:

How can I make a JPA OneToOne relation lazy

javahibernatejpalazy-loadingone-to-one

提问by Kim L

In this application we are developing, we noticed that a view was particularly slow. I profiled the view and noticed that there was one query executed by hibernate which took 10 seconds even if there only were two object in the database to fetch. All OneToManyand ManyToManyrelations were lazy so that wasn't the problem. When inspecting the actual SQL being executed, I noticed that there were over 80 joins in the query.

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

Further inspecting the issue, I noticed that the problem was caused by the deep hierarchy of OneToOneand ManyToOnerelations between entity classes. So, I thought, I'll just make them fetched lazy, that should solve the problem. But annotating either @OneToOne(fetch=FetchType.LAZY)or @ManyToOne(fetch=FetchType.LAZY)doesn't seem to work. Either I get an exception or then they are not actually replaced with a proxy object and thus being lazy.

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

Any ideas how I'll get this to work? Note that I do not use the persistence.xmlto define relations or configuration details, everything is done in java code.

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

采纳答案by ChssPly76

First off, some clarifications to KLE's answer:

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

  1. Unconstrained (nullable) one-to-one association is the only one that can not be proxied without bytecode instrumentation. The reason for this is that owner entity MUST know whether association property should contain a proxy object or NULL and it can't determine that by looking at its base table's columns due to one-to-one normally being mapped via shared PK, so it has to be eagerly fetched anyway making proxy pointless. Here's a more detailedexplanation.

  2. many-to-one associations (and one-to-many, obviously) do not suffer from this issue. Owner entity can easily check its own FK (and in case of one-to-many, empty collection proxy is created initially and populated on demand), so the association can be lazy.

  3. Replacing one-to-one with one-to-many is pretty much never a good idea. You can replace it with unique many-to-one but there are other (possibly better) options.

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

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

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

Rob H.has a valid point, however you may not be able to implement it depending on your model (e.g. if your one-to-one association isnullable).

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

Now, as far as original question goes:

现在,就原始问题而言:

A) @ManyToOne(fetch=FetchType.LAZY)should work just fine. Are you sure it's not being overwritten in the query itself? It's possible to specify join fetchin HQL and / or explicitly set fetch mode via Criteria API which would take precedence over class annotation. If that's not the case and you're still having problems, please post your classes, query and resulting SQL for more to-the-point conversation.

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

B) @OneToOneis trickier. If it's definitely not nullable, go with Rob H.'s suggestion and specify it as such:

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

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

Otherwise, if you can change your database (add a foreign key column to owner table), do so and map it as "joined":

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

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

and in OtherEntity:

在其他实体中:

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

If you can't do that (and can't live with eager fetching) bytecode instrumentation is your only option. I have to agree with CPerkins, however - if you have 80!!!joins due to eager OneToOne associations, you've got bigger problems then this :-)

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

回答by Rob H

In native Hibernate XML mappings, you can accomplish this by declaring a one-to-onemapping with the constrainedattribute set to true. I am not sure what the Hibernate/JPA annotation equivalent of that is, and a quick search of the doc provided no answer, but hopefully that gives you a lead to go on.

在本机 Hibernate XML 映射中,您可以通过声明一对一映射并将受约束的属性设置为 true 来实现此目的。我不确定对应的 Hibernate/JPA 注释是什么,快速搜索文档没有提供答案,但希望这能给你一个继续下去的线索。

回答by KLE

The basic idea behing the XToOnes in Hibernate is that they are not lazy in most case.

Hibernate 中 XToOnes 的基本思想是它们在大多数情况下并不懒惰。

One reason is that, when Hibernate have to decide to put a proxy (with the id) or a null,
it has to look into the other table anywayto join. The cost of accessing the other table in the database is significant, so it might as well fetch the data for that table at that moment (non-lazy behaviour), instead of fetching that in a later request that would require a second access to the same table.

一个原因是,当 Hibernate 必须决定放置一个代理(带有 id)或一个 null 时,
它无论如何都必须查看另一个表才能加入。访问数据库中另一个表的成本很高,因此最好在那个时刻获取该表的数据(非惰性行为),而不是在稍后需要再次访问该表的请求中获取该数据同一张桌子。

Edited: for details, please refer to ChssPly76 's answer. This one is less accurate and detailed, it has nothing to offer. Thanks ChssPly76.

编辑:详情请参考ChssPly76的回答。这个不太准确和详细,它没有什么可提供的。感谢 ChssPly76。

回答by Kdeveloper

To get lazy loading working on nullable one-to-one mappings you need to let hibernate do compile time instrumentationand add a @LazyToOne(value = LazyToOneOption.NO_PROXY)to the one-to-one relation.

要在可空的一对一映射上进行延迟加载,您需要让 hibernate 进行编译时检测并将 a 添加@LazyToOne(value = LazyToOneOption.NO_PROXY)到一对一关系中。

Example Mapping:

示例映射:

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

Example Ant Build file extension (for doing the Hibernate compile time instrumentation):

示例 Ant 构建文件扩展名(用于执行 Hibernate 编译时检测):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>

回答by Pino

As already perfectly explained by ChssPly76, Hibernate's proxies don't help with unconstrained (nullable) one-to-one associations, BUT there is a trick explained hereto avoid to set up instrumentation. The idea is to fool Hibernate that the entity class which we want to use has been already instrumented: you instrument it manually in the source code. It's easy! I've implemented it with CGLib as bytecode provider and it works (ensure that you configure lazy="no-proxy" and fetch="select", not "join", in your HBM).

正如 ChssPly76 已经完美解释的那样,Hibernate 的代理对无约束(可为空的)一对一关联没有帮助,但是这里解释一个技巧来避免设置检测。这个想法是欺骗 Hibernate 我们想要使用的实体类已经被检测:你在源代码中手动检测它。这很简单!我已经使用 CGLib 作为字节码提供程序实现了它并且它可以工作(确保您在 HBM 中配置了 lazy="no-proxy" 和 fetch="select",而不是“join”)。

I think this is a good alternative to real(I mean automatic) instrumentation when you have just one one-to-one nullable relation that you want to make lazy. The main drawback is that the solution depends on the bytecode provider you are using, so comment your class accurately because you could have to change the bytecode provider in the future; of course, you are also modifying your model bean for a technical reason and this is not fine.

我认为这是一个很好的替代真实(我的意思是自动)检测的方法,当您只有一对一的可空关系时,您想使其变得懒惰。主要缺点是解决方案取决于您使用的字节码提供程序,因此请准确注释您的类,因为您将来可能必须更改字节码提供程序;当然,出于技术原因,您也在修改模型 bean,这并不好。

回答by acdcjunior

Here's something that has been working for me (without instrumentation):

这是对我有用的东西(没有仪器):

Instead of using @OneToOneon both sides, I use @OneToManyin the inverse part of the relationship (the one with mappedBy). That makes the property a collection (Listin the example below), but I translate it into an item in the getter, making it transparent to the clients.

我不是@OneToOne在双方都使用,而是@OneToMany在关系的反部分使用(带有 的那个mappedBy)。这使该属性成为一个集合(List在下面的示例中),但我将其转换为 getter 中的一个项目,使其对客户端透明。

This setup works lazily, that is, the selects are only made when getPrevious()or getNext()are called - and only oneselect for each call.

这种设置是惰性的,也就是说,选择只在被调用getPrevious()或被getNext()调用时进行 - 并且每次调用只有一个选择。

The table structure:

表结构:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

The class:

班上:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}

回答by Toumi

This question is quite old, but with Hibernate 5.1.10, there are some new better comfortable solution.

这个问题已经很老了,但是在 Hibernate 5.1.10 中,有一些新的更舒适的解决方案。

Lazy loading works except for the parent side of a @OneToOne association. This is because Hibernate has no other way of knowing whether to assign a null or a Proxy to this variable. More details you can find in this article

除了@OneToOne 关联的父端外,延迟加载也有效。这是因为 Hibernate 没有其他方法知道是为这个变量分配一个 null 还是一个 Proxy。您可以在本文中找到更多详细信息

  • You can activate lazy loading bytecode enhancement
  • Or, you can just remove the parent side and use the client side with @MapsId as explained in the article above. This way, you will find that you don't really need the parent side since the child shares the same id with the parent so you can easily fetch the child by knowing the parent id .
  • 您可以激活延迟加载字节码增强
  • 或者,您可以删除父端并使用带有 @MapsId 的客户端,如上面文章中所述。这样,您会发现您并不真正需要父端,因为子代与父代共享相同的 id,因此您可以通过知道父代 id 轻松获取子代。

回答by Vlad Mihalcea

As I explained in this article, unless you are using Bytecode Enhancement, you cannot fetch lazily the parent-side @OneToOneassociation.

正如我在本文中所解释的,除非您使用Bytecode Enhancement,否则您无法懒惰地获取父端@OneToOne关联。

However, most often, you don't even need the parent-side association if you use @MapsIdon the client side:

但是,大多数情况下,如果您@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
}

With @MapsId, the idproperty in the child table serves as both Primary Key and Foreign Key to the parent table Primary Key.

使用@MapsIdid子表中的属性作为父表主键的主键和外键。

So, if you have a reference to the parent Postentity, you can easily fetch the child entity using the parent entity identifier:

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

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

This way, you won't have N+1 query issuesthat could be caused by the mappedBy@OneToOneassociation on the parent side.

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

回答by Stefan

If the relation must not be bidirectional then an @ElementCollection might be easier than using a lazy One2Many collection.

如果关系不能是双向的,那么 @ElementCollection 可能比使用惰性 One2Many 集合更容易。