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

进一步检查这个问题,我注意到这个问题是由实体类之间一对一和多对一关系的深层层次结构引起的。所以,我想,我只要把它们设为lazy,就能解决问题了。但是注释@OneToOne(fetch=FetchType.LAZY)或@ManyToOne(fetch=FetchType.LAZY)似乎都不起作用。要么我得到一个异常,要么它们实际上没有被代理对象替换,从而变得懒惰。

你知道我要怎么做吗?注意,我没有使用persistence.xml来定义关系或配置细节,一切都是在java代码中完成的。


当前回答

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

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 detailed explanation. 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. 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.

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

现在,就最初的问题而言

A) @ManyToOne(fetch=FetchType.LAZY)应该可以正常工作。您确定它没有在查询本身中被覆盖吗?可以在HQL中指定连接获取和/或通过Criteria API显式设置获取模式,这将优先于类注释。如果不是这样,你仍然有问题,请发布你的类,查询和结果的SQL进行更多的重点对话。

B) @一对一比较棘手。如果它绝对不是空的,那就用Rob H吧。的建议,并具体说明如下:

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

否则,如果你可以改变你的数据库(添加一个外键列到所有者表),这样做,并将其映射为“joined”:

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

在OtherEntity中:

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

如果您不能这样做(并且不能接受快速抓取),字节码插装就是惟一的选择。然而,我不得不同意CPerkins的观点——如果你有80个!!由于渴望一对一的联系,你有更大的问题:-)

其他回答

在原生Hibernate XML映射中,可以通过将受约束的属性设置为true来声明一个一对一的映射来实现这一点。我不确定Hibernate/JPA注释等价于它是什么,快速搜索文档也没有提供答案,但希望这能给您一个线索。

为了让惰性加载工作在可为空的一对一映射上,您需要让hibernate进行编译时插装,并向一对一关系添加@LazyToOne(value = LazyToOneOption.NO_PROXY)。

示例映射:

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

示例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>

除非您正在使用字节码增强,否则您不能惰性地获取父端@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()
);

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

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

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

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 detailed explanation. 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. 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.

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

现在,就最初的问题而言

A) @ManyToOne(fetch=FetchType.LAZY)应该可以正常工作。您确定它没有在查询本身中被覆盖吗?可以在HQL中指定连接获取和/或通过Criteria API显式设置获取模式,这将优先于类注释。如果不是这样,你仍然有问题,请发布你的类,查询和结果的SQL进行更多的重点对话。

B) @一对一比较棘手。如果它绝对不是空的,那就用Rob H吧。的建议,并具体说明如下:

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

否则,如果你可以改变你的数据库(添加一个外键列到所有者表),这样做,并将其映射为“joined”:

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

在OtherEntity中:

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

如果您不能这样做(并且不能接受快速抓取),字节码插装就是惟一的选择。然而,我不得不同意CPerkins的观点——如果你有80个!!由于渴望一对一的联系,你有更大的问题:-)