何时使用EntityManager.find()与EntityManager.getReference()一起使用JPA

Sib*_*Ter 98 java persistence jpa java-ee

我遇到过一种情况(我觉得很奇怪,但可能很正常),我使用EntityManager.getReference(LObj.getClass(),LObj.getId())获取数据库实体,然后将返回的对象传递给坚持在另一张桌子上.

所以基本上流程是这样的:

class TFacade{

  createT(FObj, AObj) {
    T TObj = new T();
    TObj.setF(FObj);
    TObj.setA(AObj);
    ...
    EntityManager.persist(TObj);
    ...
    L LObj = A.getL();
    FObj.setL(LObj);
    FFacade.editF(FObj);
  }
}

@TransactionAttributeType.REQUIRES_NEW
class FFacade{

  editF(FObj){
    L LObj = FObj.getL();
    LObj = EntityManager.getReference(LObj.getClass(), LObj.getId());
    ...
    EntityManager.merge(FObj);
    ...
    FLHFacade.create(FObj, LObj);
  }
}

@TransactionAttributeType.REQUIRED
class FLHFacade{

  createFLH(FObj, LObj){
    FLH FLHObj = new FLH();
    FLHObj.setF(FObj);
    FLHObj.setL(LObj);
    ....
    EntityManager.persist(FLHObj);
    ...
  }
}

我得到以下异常"java.lang.IllegalArgumentException:未知实体:com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"

在查看了一段时间之后,我终于想通了,因为我正在使用EntityManager.getReference()方法,因为该方法正在返回代理,所以我得到了上述异常.

这让我想知道,何时建议使用EntityManager.getReference()方法而不是EntityManager.find()方法

如果EntityManager.getReference()无法找到正在搜索的实体本身非常方便,则会抛出EntityNotFoundException.如果EntityManager.find()方法无法找到实体,则它仅返回null.

关于事务边界,听起来像你需要在将新发现的实体传递给新事务之前使用find()方法.如果你使用getReference()方法,那么你可能会遇到类似我的情况,但有上述异常.

Art*_*ald 146

当我不需要访问数据库状态时,我通常使用getReference方法(我的意思是getter方法).只是改变状态(我的意思是setter方法).您应该知道,getReference返回一个代理对象,该对象使用称为自动脏检查的强大功能.假设如下

public class Person {

    private String name;
    private Integer age;

}


public class PersonServiceImpl implements PersonService {

    public void changeAge(Integer personId, Integer newAge) {
        Person person = em.getReference(Person.class, personId);

        // person is a proxy
        person.setAge(newAge);
    }

}
Run Code Online (Sandbox Code Playgroud)

如果我调用find方法,JPA提供商,在幕后,将调用

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
Run Code Online (Sandbox Code Playgroud)

如果我调用getReference方法,JPA提供者,在幕后,将调用

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
Run Code Online (Sandbox Code Playgroud)

你知道为什么???

当您调用getReference时,您将获得一个代理对象.像这样的东西(JPA提供商负责实现这个代理)

public class PersonProxy {

    // JPA provider sets up this field when you call getReference
    private Integer personId;

    private String query = "UPDATE PERSON SET ";

    private boolean stateChanged = false;

    public void setAge(Integer newAge) {
        stateChanged = true;

        query += query + "AGE = " + newAge;
    }

}
Run Code Online (Sandbox Code Playgroud)

因此,在事务提交之前,JPA提供程序将看到stateChanged标志以更新OR NOT person实体.如果在update语句之后没有更新行,则JPA提供程序将根据JPA规范抛出EntityNotFoundException.

问候,

  • 我正在使用EclipseLink 2.5.0,上面提到的查询不正确.它始终在`UPDATE`之前发出`SELECT`,无论我使用哪个`find()`/`getReference()`.更糟糕的是,`SELECT`遍历NON-LAZY关系(发出新的'SELECTS`),尽管我只想更新一个实体中的单个字段. (3认同)

Vla*_*cea 14

正如我在本文中解释的那样,假设您有一个父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);
Run Code Online (Sandbox Code Playgroud)

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

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

这次,Hibernate将只发出INSERT语句:

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)
Run Code Online (Sandbox Code Playgroud)

与之不同find,getReference唯一返回仅具有标识符集的实体代理.如果访问代理,只要EntityManager仍处于打开状态,就会触发关联的SQL语句.

但是,在这种情况下,我们不需要访问实体代理.我们只想将外键传播到基础表记录,因此加载代理就足以满足此用例.

加载代理时,您需要知道,如果在关闭EntityManager后尝试访问代理引用,则可能抛出LazyInitializationException.有关处理的更多详细信息LazyInitializationException,请查看此文章.

  • 对于您描述的用例,Hibernate提供了[`hibernate.jpa.compliance.proxy`配置属性](http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#configurations -jpa-compliance),因此您可以选择JPA合规性或更好的数据访问性能。 (2认同)
  • @rilaby 设置代理允许您稍后初始化它。设置一个只有 id 的空 POJO 是一种 hack,这恰好在 Hibernate 中有效。但是,稍后,您可能最终会查询非 id 属性并获取“null”而不是实际的属性值。 (2认同)

dav*_*xxx 10

这让我想知道,什么时候最好使用 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 = ?
Run Code Online (Sandbox Code Playgroud)

如果我调用getReference方法,幕后的 JPA 提供者将调用

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
Run Code Online (Sandbox Code Playgroud)

这不是我在 Hibernate 5 中得到的行为,并且 javadocgetReference()没有说这样的话:

获取一个实例,其状态可能会被延迟获取。如果请求的实例在数据库中不存在,则在第一次访问实例状态时抛出 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;
}
Run Code Online (Sandbox Code Playgroud)

有趣的部分是:

Object existing = persistenceContext.getEntity( keyToLoad );
Run Code Online (Sandbox Code Playgroud)

2)如果我们没有有效地操作实体,就会与javadoc的懒惰获取相呼应。
实际上,为了确保实体的有效加载,需要对其调用方法。
那么增益将与我们想要加载一个实体而不需要使用它的场景有关?在应用程序框架中,这种需求确实不常见,此外,getReference()如果您阅读下一部分,这种行为也非常具有误导性。

为什么偏爱 EntityManager.find() 而不是 EntityManager.getReference()

在开销方面,getReference()并不比find()上一点中讨论的要好。
那么为什么要使用一个或另一个呢?

调用getReference()可能会返回一个延迟获取的实体。
在这里,延迟获取不是指实体的关系,而是实体本身。
这意味着如果我们调用getReference()然后 Persistence 上下文关闭,则实体可能永远不会加载,因此结果真的不可预测。例如,如果代理对象被序列化,您可以获得null作为序列化结果的引用,或者如果在代理对象上调用了一个方法,LazyInitializationException则会引发诸如此类的异常。

这意味着抛出EntityNotFoundException它是getReference()用于处理数据库中不存在的实例的主要原因,因为在实体不存在时可能永远不会执行错误情况。

EntityManager.find()EntityNotFoundException如果找不到实体,则没有投掷的野心。它的行为既简单又清晰。您永远不会感到惊讶,因为它始终返回一个已加载的实体或null(如果未找到该实体),但永远不会返回可能无法有效加载的代理形状下的实体。
所以EntityManager.find()在大多数情况下应该受到青睐。


Ste*_*gin 8

因为引用是"托管的",但不是水合的,它还允许您通过ID删除实体,而无需先将其加载到内存中.

由于无法删除非托管实体,使用find(...)或createQuery(...)加载所有字段只是为了立即删除它.

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
Run Code Online (Sandbox Code Playgroud)