为了账号安全,请及时绑定邮箱和手机立即绑定

何时在JPA中使用EntityManager.find()与EntityManager.get

/ 猿问

何时在JPA中使用EntityManager.find()与EntityManager.get

森栏 2019-10-15 14:13:13

我遇到了一种情况(我认为这很奇怪,但可能很正常),在这种情况下,我使用EntityManager.getReference(LObj.getClass(),LObj.getId())获取数据库实体,然后将返回的对象传递给保留在另一个表中。


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


TFacade类{


  createT(FObj,AObj){

    T TObj =新的T();

    TObj.setF(FObj);

    TObj.setA(AObj);

    ...

    EntityManager.persist(TObj);

    ...

    L LObj = A.getL();

    FObj.setL(LObj);

    FFacade.editF(FObj);

  }

}


@ TransactionAttributeType.REQUIRES_NEW

FFacade类{


  editF(FObj){

    L LObj = FObj.getL();

    LObj = EntityManager.getReference(LObj.getClass(),LObj.getId());

    ...

    EntityManager.merge(FObj);

    ...

    FLHFacade.create(FObj,LObj);

  }

}


@ TransactionAttributeType.REQUIRED

FLHFacade类{


  createFLH(FObj,LObj){

    FLH FLHObj =新的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()方法,则可能会遇到类似于我的情况,但有上述例外。


查看完整描述

3 回答

?
慕少森

当我不需要访问数据库状态时,我通常使用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);

    }


}

如果我调用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 = ?

而且你知道为什么吗?


调用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;

    }


}

因此,在事务提交之前,JPA提供者将看到stateChanged标志以便更新OR NOT人实体。如果在更新语句后没有行被更新,则JPA提供程序将根据JPA规范引发EntityNotFoundException。


问候,


查看完整回答
反对 回复 2019-10-15
?
郎朗坤

正如我在本文中所解释的,假设您具有父Post实体和子实体,PostComment如下图所示:


//img.mukewang.com/5da563a70001677c13650366.jpg

如果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。有关处理的更多详细信息LazyInitializationException,请查看本文。


查看完整回答
反对 回复 2019-10-15
?
交互式爱情

这使我感到奇怪,什么时候才建议使用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(),在大多数情况下都应受到青睐。


查看完整回答
反对 回复 2019-10-15

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信