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

2022-08-31 09:21:48

我遇到过一种情况(我认为这很奇怪,但可能很正常),我使用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()方法,那么您可能会遇到与我类似的情况,但上述例外。


答案 1

当我不需要访问数据库状态时,我通常使用getReference方法(我的意思是getter方法)。只是为了改变状态(我的意思是设置者方法)。如您所知,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。

问候


答案 2

假设您有一个父实体和一个子实体,如下图所示:PostPostComment

enter image description here

如果在尝试设置关联时调用:find@ManyToOnepost

PostComment comment = new PostComment();
comment.setReview("Just awesome!");
 
Post post = entityManager.find(Post.class, 1L);
comment.setPost(post);
 
entityManager.persist(comment);

休眠将执行以下语句:

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)

与 不同,only 返回一个只设置了标识符的实体 Proxy。如果访问代理,只要 EntityManager 仍处于打开状态,就会触发关联的 SQL 语句。findgetReference

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

加载代理时,需要注意的是,如果在 EntityManager 关闭后尝试访问代理引用,则可能会引发 LazyInitializationException


推荐