做一对一关系懒
在这个应用程序中,我们正在开发,我们注意到一个看法特别慢。 我对该视图进行了剖析,并注意到有一个由hibernate执行的查询,即使数据库中只有两个对象要获取,也需要10秒。 所有OneToMany
和ManyToMany
关系都是懒惰的,所以这不是问题。 在检查正在执行的实际SQL时,我注意到查询中有超过80个连接。
进一步检查问题,我注意到问题是由OneToOne
和ManyToOne
实体类之间的深层次关系造成的。 所以,我想,我只是让他们偷懒,这应该解决问题。 但注释@OneToOne(fetch=FetchType.LAZY)
或@ManyToOne(fetch=FetchType.LAZY)
似乎不工作。 要么我得到一个exception,或者他们实际上并没有被代理对象取代,因此懒惰。
任何想法,我将如何得到这个工作? 请注意,我不使用persistence.xml
来定义关系或configuration细节,一切都在java代码中完成。
首先,对KLE的答案做一些说明:
-
无约束(可空)的一对一关联是唯一不能在没有字节码检测的情况下进行代理的关联。 原因是拥有者实体必须知道关联属性是否应该包含一个代理对象或NULL,并且由于一对一地通过共享的PK来映射,所以它不能确定通过查看其基表的列,所以它无论如何,必须急于提出代理毫无意义。 这里有一个更详细的解释。
-
多对一的关联(显然是一对多关系)不会遇到这个问题。 所有者实体可以轻松地检查自己的FK(在一对多的情况下,初始创build空集合代理并根据需要填充),所以关联可以是懒惰的。
-
用一对多来替代一对一的做法几乎不是一个好主意。 你可以用唯一的多对一replace它,但是还有其他的(可能更好的)选项。
罗布H.有一个有效的观点,但是你可能无法实现它取决于你的模型(例如,如果你的一对一的关联是空的)。
现在,就原来的问题而言:
一) @ManyToOne(fetch=FetchType.LAZY)
应该工作得很好。 你确定它没有被覆盖在查询本身? 可以在HQL中指定join fetch
并且/或者通过Criteria API显式设置提取模式,其优先于类标注。 如果情况并非如此,并且仍然存在问题,请发布您的课程,查询和生成的SQL以获得更多的点对点交谈。
B) @OneToOne
更棘手。 如果它绝对不是可以空的,请和Rob H.的build议一致,并指定它:
@OneToOne(optional = false, fetch = FetchType.LAZY)
否则,如果您可以更改数据库(将外键列添加到所有者表),请将其映射为“已join”:
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name="other_entity_fk") public OtherEntity getOther()
和其他实体:
@OneToOne(mappedBy = "other") public OwnerEntity getOwner()
如果你不能这样做(并且不能忍受提取)字节码检测是你唯一的select。 我必须同意CPerkins ,但是 – 如果你有80! 由于热切OneToOne协会join,你有更大的问题,然后这个:-)
要获得惰性加载工作在可空的一对一映射,你需要让hibernate 编译时间检测,并添加一个@LazyToOne(value = LazyToOneOption.NO_PROXY)
到一对一的关系。
示例映射:
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name="other_entity_fk") @LazyToOne(value = LazyToOneOption.NO_PROXY) public OtherEntity getOther()
示例Ant构build文件扩展名(用于执行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>
在Hibernate中抛弃XToOnes的基本思想是在大多数情况下它们不是懒惰的。
原因之一是,当Hibernate必须决定放置一个代理(带有id)或一个null时,
无论如何 , 它必须查看另一张表join。 访问数据库中其他表的成本很高,所以不妨在那个时候获取该表的数据(非惰性行为),而不是在稍后的请求中获取该数据,同一张桌子。
编辑:有关详细信息,请参阅ChssPly76的答案 。 这个不太准确和详细,没有什么可提供的。 感谢ChssPly76。
这是一直在为我工作(没有仪器):
而不是在两边使用@OneToOne
,我使用@OneToMany
在关系的反向部分(一个与mappedBy
)。 这使得该属性成为一个集合(下面的例子中的List
),但是我把它翻译成getter中的一个项目,使得它对客户端是透明的。
这个设置是懒惰的,也就是说,只有在调用getPrevious()
或getNext()
才会产生select – 每个调用只有一个select。
表结构:
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`);
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); } }
在原生Hibernate XML映射中,可以通过声明约束属性设置为true 的一对一映射来完成此操作。 我不确定Hibernate / JPA的注解是什么样的,而且快速search这个文档没有提供任何答案,但希望能够让你继续前进。
正如ChssPly76已经完全解释的那样,Hibernate的代理并不能帮助不受约束的(可空的)一对一关联,但是这里有一个技巧来避免设置仪表。 这个想法是愚弄Hibernate,我们想要使用的实体类已经被检测过了:你在源代码中手工testing它。 这很容易! 我已经使用CGLib作为字节码提供者来实现它,它可以工作(确保在HBM中configurationlazy =“no-proxy”和fetch =“select”,而不是“join”)。
我认为这是一个很好的替代实际 (我的意思是自动)仪器,当你只有一个一对一的可空关系,你想懒惰。 主要的缺点是解决scheme取决于你使用的字节码提供者,所以要准确地评论你的类,因为将来你可能不得不改变字节码提供者。 当然,由于技术上的原因,你也在修改你的模型bean,这是不好的。
- 如何在Hibernate 4和Spring中使用注释来定义不同types的关系?
- 如何将inheritance策略与JPA注释和Hibernate混合?
- @OneToMany和@ElementCollection之间的区别?
- 避免对未获取的惰性对象进行Jackson序列化
- 查询指定的联接抓取,但抓取的关联的所有者不在select列表中
- javax.transaction.Transactional vs org.springframework.transaction.annotation.Transactional
- 使用额外列映射多对多关联表
- Hibernate:一对一延迟加载,可选= false
- 在Hibernate Validator 4.1+中,@NotNull,@NotEmpty和@NotBlank有什么区别?