使OneToOne关系变得懒惰

Kim*_*m L 203 java hibernate jpa

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

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

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

Chs*_*y76 206

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

  1. 无约束(可空)一对一关联是唯一一个没有字节码检测就无法代理的关联.这样做的原因是所有者实体必须知道关联属性是否应该包含代理对象或NULL并且由于通常通过共享PK进行一对一映射而无法通过查看其基表的列来确定它,因此它不管怎么说,必须要急切地让代理变得毫无意义.这是一个更详细的解释.

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

  3. 用一对多替换一对一绝不是一个好主意.您可以使用唯一的多对一替换它,但还有其他(可能更好)的选项.

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

现在,就原始问题而言:

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

B)@OneToOne比较棘手.如果它绝对不可为空,请与Rob H.的建议一起使用并指定为:

@OneToOne(optional = false, fetch = FetchType.LAZY)
Run Code Online (Sandbox Code Playgroud)

否则,如果您可以更改数据库(将外键列添加到所有者表),请执行此操作并将其映射为"已连接":

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()
Run Code Online (Sandbox Code Playgroud)

在OtherEntity中:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()
Run Code Online (Sandbox Code Playgroud)

如果你不能这样做(并且不能忍受急切的提取)字节码检测是你唯一的选择.我不得不同意CPerkins - 如果你有80!由于渴望OneToOne协会加入,你有更大的问题,然后这个:-)

  • optional = false,对我不起作用。@OneToOne(fetch = FetchType.LAZY,mappedBy = "fundSeries", optional = false) private FundSeriesDetailEntity fundSeriesDetail; (3认同)

Kde*_*per 19

要使延迟加载处理可空的一对一映射,您需要让hibernate执行编译时间检测并添加@LazyToOne(value = LazyToOneOption.NO_PROXY)到一对一关系.

示例映射:

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

示例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>
Run Code Online (Sandbox Code Playgroud)

  • 为什么`LazyToOneOption.NO_PROXY`而不是'LazyToOneOption.PROXY`? (2认同)

Vla*_*cea 14

除非您使用字节码增强,否则您不能懒惰地获取父端@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
}
Run Code Online (Sandbox Code Playgroud)

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

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

PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);
Run Code Online (Sandbox Code Playgroud)

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

  • 这样我们就不能再从父级到子级级联操作了:/ (4认同)

KLE*_*KLE 10

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

一个原因是,当Hibernate必须决定放置一个代理(带有id)或null时,
它必须查看另一个表,无论如何要加入.访问数据库中其他表的成本很高,因此它当时也可以获取该表的数据(非延迟行为),而不是在以后需要第二次访问该表的请求中获取该数据.同桌.

编辑:有关详细信息,请参阅ChssPly76的答案.这个不太精确和详细,没有什么可提供的.谢谢ChssPly76.


acd*_*ior 8

这里有一些对我有用的东西(没有仪器):

@OneToOne我没有@OneToMany在两边使用,而是在关系的反面部分使用(与之相对mappedBy).这使得属性成为集合(List在下面的示例中),但我将其转换为getter中的项目,使其对客户端透明.

这种设置工作懒惰,也就是选择只取得时getPrevious()或者getNext()被称为-只有一个选择为每个呼叫.

表结构:

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`);
Run Code Online (Sandbox Code Playgroud)

班级:

@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); }

}
Run Code Online (Sandbox Code Playgroud)


Rob*_*b H 5

在本机Hibernate XML映射中,您可以通过将约束属性设置为true 来声明一对一映射来实现此目的.我不确定Hibernate/JPA注释与之相当的是什么,并且快速搜索文档没有提供任何答案,但希望这能让你继续前进.

  • +1是一个很好的建议; 不幸的是,它并不总是适用,因为域模型实际上可能需要可空性.通过注释映射这个的正确方法是`@OneToOne(optional = false,fetch = FetchMode.LAZY)` (4认同)