Java 何时对JPA使用EntityManager.find()与EntityManager.getReference()
我遇到了一种情况(我认为这很奇怪,但可能很正常),我使用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返回一个代理对象,该对象使用一个强大的功能,称为自动脏检查。假设如下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
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);
}
}
}