3 回答
TA贡献1921条经验 获得超9个赞
正如我在本文中所解释的,假设您具有父Post实体和子实体,PostComment如下图所示:
如果find在尝试设置@ManyToOne post关联时致电:
PostComment comment = new PostComment();
comment.setReview("Just awesome!");
Post post = entityManager.find(Post.class, 1L);
comment.setPost(post);
entityManager.persist(comment);
Hibernate将执行以下语句:
SELECT p.id AS id1_0_0_,
p.title AS title2_0_0_
FROM post p
WHERE p.id = 1
INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)
这次SELECT查询没有用,因为我们不需要获取Post实体。我们只想设置基础的post_id外键列。
现在,如果您使用getReference:
PostComment comment = new PostComment();
comment.setReview("Just awesome!");
Post post = entityManager.getReference(Post.class, 1L);
comment.setPost(post);
entityManager.persist(comment);
这次,Hibernate将仅发出INSERT语句:
INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)
与不同find,getReference只会返回仅设置了标识符的实体Proxy。如果您访问代理,则只要EntityManager仍处于打开状态,就会触发关联的SQL语句。
但是,在这种情况下,我们不需要访问实体代理。我们只希望将外键传播到基础表记录,因此对于此用例而言,加载代理就足够了。
加载代理时,需要注意的是,如果在关闭EntityManager后尝试访问代理引用,则可能引发LazyInitializationException。
TA贡献1712条经验 获得超3个赞
这使我感到奇怪,什么时候才建议使用EntityManager.getReference()方法而不是EntityManager.find()方法?
EntityManager.getReference()这确实是一种容易出错的方法,而且实际上很少有客户端代码需要使用它的情况。
就个人而言,我从来不需要使用它。
EntityManager.getReference()和EntityManager.find():在开销方面没有差异
我不同意接受的答案,尤其是:
如果我调用find方法,那么JPA提供程序将在后台调用
SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
如果我调用getReference方法,那么JPA提供程序将在后台调用
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
这不是我在Hibernate 5中得到的行为,而javadoc getReference()却没有这样说:
获取一个实例,其状态可能会延迟获取。如果请求的实例在数据库中不存在,则在首次访问实例状态时将引发EntityNotFoundException。(调用getReference时,允许持久性提供程序运行时引发EntityNotFoundException。)应用程序不应期望实例状态在分离后可用,除非在打开实体管理器时由应用程序访问了实例状态。
EntityManager.getReference() 在两种情况下不使用查询来检索实体:
1)如果实体存储在Persistence上下文中,那就是第一级缓存。
并且此行为并非特定于EntityManager.getReference(),EntityManager.find()如果实体存储在Persistence上下文中, 还将节省查询以检索实体。
您可以使用任何示例检查第一点。
您还可以依赖实际的Hibernate实现。
确实,EntityManager.getReference()依靠类的createProxyIfNecessary()方法org.hibernate.event.internal.DefaultLoadEventListener来加载实体。
这是它的实现:
private Object createProxyIfNecessary(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadEventListener.LoadType options,
final PersistenceContext persistenceContext) {
Object existing = persistenceContext.getEntity( keyToLoad );
if ( existing != null ) {
// return existing object or initialized proxy (unless deleted)
if ( traceEnabled ) {
LOG.trace( "Entity found in session cache" );
}
if ( options.isCheckDeleted() ) {
EntityEntry entry = persistenceContext.getEntry( existing );
Status status = entry.getStatus();
if ( status == Status.DELETED || status == Status.GONE ) {
return null;
}
}
return existing;
}
if ( traceEnabled ) {
LOG.trace( "Creating new proxy for entity" );
}
// return new uninitialized proxy
Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
persistenceContext.addProxy( keyToLoad, proxy );
return proxy;
}
有趣的部分是:
Object existing = persistenceContext.getEntity( keyToLoad );
2)如果我们没有有效地操纵实体,则回显懒惰地获取的Javadoc。
实际上,为了确保实体的有效加载,需要在其上调用方法。
那么,这种收益是否与我们想加载一个实体而不需要使用它的情况有关?在应用程序框架中,这种需求确实很少见,此外,getReference()如果您阅读下一部分,其行为也很容易引起误解。
为什么比EntityManager.getReference()更偏爱EntityManager.find()
在开销方面,getReference()并不比find()上一点更好。
那么为什么要使用一个或另一个呢?
调用getReference()可能会返回延迟获取的实体。
在这里,延迟获取不是指实体的关系,而是指实体本身。
这意味着,如果我们调用getReference()然后关闭Persistence上下文,则该实体可能永远不会加载,因此结果实际上是不可预测的。例如,如果代理对象已序列化,则可以获取null引用作为序列化结果,或者如果在代理对象上调用方法,LazyInitializationException则会引发诸如之类的异常。
这意味着抛出该异常是用于处理数据库中不存在的实例的EntityNotFoundException主要原因,getReference()因为在实体不存在时可能永远不会执行错误情况。
EntityManager.find()EntityNotFoundException如果找不到该实体,则不会抛出该异常。它的行为既简单又清晰。您永远不会感到惊讶,因为它总是返回一个已加载的实体或null(如果未找到该实体),但永远不会返回未有效加载的代理形式的实体。
因此EntityManager.find(),在大多数情况下都应受到青睐。
添加回答
举报