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

Java 何时对JPA使用EntityManager.find()与EntityManager.getReference(),java,hibernate,jpa,jakarta-ee,persistence,Java,Hibernate,Jpa,Jakarta Ee,Persistence,我遇到了一种情况(我认为这很奇怪,但可能很正常),我使用EntityManager.getReference(LObj.getClass(),LObj.getId())获取一个数据库实体,然后将返回的对象传递给另一个表以持久化 所以基本上,流程是这样的: class TFacade{ createT(FObj, AObj) { T TObj = new T(); TObj.setF(FObj); TObj.setA(AObj); ... Entity

我遇到了一种情况(我认为这很奇怪,但可能很正常),我使用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); ... } } 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\u NEW 班卡{ 编辑部(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:Unknown entity:com.my.persistence.L$$enhancerbyglib$$3e7987d0”

在研究了一段时间后,我终于发现,由于我使用的是EntityManager.getReference()方法,所以当该方法返回代理时,我得到了上述异常

这让我想知道,什么时候应该使用EntityManager.getReference()方法而不是EntityManager.find()方法

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

关于事务边界,在我看来,在将新找到的实体传递给新事务之前,需要使用find()方法。如果使用getReference()方法,则可能会遇到与我类似的情况,但有上述例外。

我通常在不需要访问数据库状态时使用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 = ?
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
如果我调用getReference方法,JPA提供者将在幕后调用

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
你知道为什么吗

调用getReference时,将得到一个代理对象。类似于此(JPA提供者负责实现此代理)

所以在事务提交之前,JPA提供者将看到stateChanged标志,以便更新或不更新person实体。若update语句之后并没有更新行,JPA提供程序将根据JPA规范抛出EntityNotFoundException


关于,

因为引用是“托管的”,但不是水合的,所以它还允许您按ID删除实体,而无需先将其加载到内存中

由于无法删除非托管实体,因此使用find(…)或createQuery(…)加载所有字段只是为了立即删除它,这显然是愚蠢的

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
这使我想知道,什么时候使用 方法而不是 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 = ?
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
如果我调用getReference方法,幕后的JPA提供者将 召唤

这不是我在Hibernate 5中得到的行为,
getReference()
的javadoc没有这样说:

获取一个实例,该实例的状态可能是延迟获取的。如有要求, 实例在数据库中不存在,EntityNotFoundException 在第一次访问实例状态时引发。(持久性 允许提供程序运行时引发EntityNotFoundException 当调用getReference时。)应用程序不应期望 实例状态将在分离时可用,除非 实体管理器打开时由应用程序访问

EntityManager.getReference()
在两种情况下备用一个查询来检索实体:

1) 如果实体存储在持久性上下文中,则 一级缓存。
并且此行为不是特定于
EntityManager.getReference()
EntityManager.find()
如果实体存储在持久性上下文中,则还将保留一个查询来检索该实体

您可以用任何示例检查第一点。
您还可以依赖于实际的Hibernate实现。
实际上,
EntityManager.getReference()
依赖于
org.hibernate.event.internal.DefaultLoadEventListener类的
CreateProxyIfEssential()
方法来加载实体。
以下是它的实施:

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.find()而不是EntityManager.getReference()呢 就开销而言,
getReference()
并不比
Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);
UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
@SuppressWarnings("unchecked")
public class ProxyHandler {

public static <T> T getReference(Class<T> classType, Object id) {
    if (!classType.isAnnotationPresent(Entity.class)) {
        throw new ProxyInstantiationException("This is not an entity!");
    }

    try {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classType);
        enhancer.setCallback(new ProxyMethodInterceptor(classType, id));
        enhancer.setInterfaces((new Class<?>[]{EnhancedProxy.class}));
        return (T) enhancer.create();
    } catch (Exception e) {
        throw new ProxyInstantiationException("Error creating proxy, cause :" + e.getCause());
    }
}
public interface EnhancedProxy {
    public String getJPQLUpdate();
    public HashMap<String, Object> getModifiedFields();
}
import com.anil.app.exception.ProxyInstantiationException;
import javafx.util.Pair;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Anil Kumar
*/
public class ProxyMethodInterceptor implements MethodInterceptor, EnhancedProxy {

private Object target;
private Object proxy;
private Class classType;
private Pair<String, Object> primaryKey;
private static HashSet<String> enhancedMethods;

ProxyMethodInterceptor(Class classType, Object id) throws IllegalAccessException, InstantiationException {
    this.classType = classType;
    this.target = classType.newInstance();
    this.primaryKey = new Pair<>(getPrimaryKeyField().getName(), id);
}

static {
    enhancedMethods = new HashSet<>();
    for (Method method : EnhancedProxy.class.getDeclaredMethods()) {
        enhancedMethods.add(method.getName());
    }
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    //intercept enhanced methods
    if (enhancedMethods.contains(method.getName())) {
        this.proxy = obj;
        return method.invoke(this, args);
    }
    //else invoke super class method
    else
        return proxy.invokeSuper(obj, args);
}

@Override
public HashMap<String, Object> getModifiedFields() {
    HashMap<String, Object> modifiedFields = new HashMap<>();
    try {
        for (Field field : classType.getDeclaredFields()) {

            field.setAccessible(true);

            Object initialValue = field.get(target);
            Object finalValue = field.get(proxy);

            //put if modified
            if (!Objects.equals(initialValue, finalValue)) {
                modifiedFields.put(field.getName(), finalValue);
            }
        }
    } catch (Exception e) {
        return null;
    }
    return modifiedFields;
}

@Override
public String getJPQLUpdate() {
    HashMap<String, Object> modifiedFields = getModifiedFields();
    if (modifiedFields == null || modifiedFields.isEmpty()) {
        return null;
    }
    StringBuilder fieldsToSet = new StringBuilder();
    for (String field : modifiedFields.keySet()) {
        fieldsToSet.append(field).append(" = :").append(field).append(" and ");
    }
    fieldsToSet.setLength(fieldsToSet.length() - 4);
    return "UPDATE "
            + classType.getSimpleName()
            + " SET "
            + fieldsToSet
            + "WHERE "
            + primaryKey.getKey() + " = " + primaryKey.getValue();
}

private Field getPrimaryKeyField() throws ProxyInstantiationException {
    for (Field field : classType.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(Id.class))
            return field;
    }
    throw new ProxyInstantiationException("Entity class doesn't have a primary key!");
}
}
public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
    super(message);
}
@Service
public class PersistenceService {

@PersistenceContext
private EntityManager em;

@Transactional
private void save(Object entity) {
    // update entity for proxies
    if (entity instanceof EnhancedProxy) {
        EnhancedProxy proxy = (EnhancedProxy) entity;
        Query updateQuery = em.createQuery(proxy.getJPQLUpdate());
        for (Entry<String, Object> entry : proxy.getModifiedFields().entrySet()) {
            updateQuery.setParameter(entry.getKey(), entry.getValue());
        }
        updateQuery.executeUpdate();
    // insert otherwise
    } else {
        em.persist(entity);
    }

}
}