简体   繁体   English

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

[英]When to use EntityManager.find() vs EntityManager.getReference() with JPA

I have come across a situation (which I think is weird but is possibly quite normal) where I use the EntityManager.getReference(LObj.getClass(), LObj.getId()) to get a database entity and then pass the returned object to be persisted in another table.我遇到了一种情况(我认为这很奇怪但可能很正常),我使用 EntityManager.getReference(LObj.getClass(), LObj.getId()) 来获取数据库实体,然后将返回的对象传递给被持久化到另一个表中。

So basically the flow was like this:所以基本上流程是这样的:

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);
    ...
  }
}

I was getting the following exception "java.lang.IllegalArgumentException: Unknown entity: com.my.persistence.L$$EnhancerByCGLIB$$3e7987d0"我收到以下异常“java.lang.IllegalArgumentException:未知实体:com.my.persistence.L$$EnhancerByCGLIB$$3e7987d0”

After looking into it for a while, I finally figured out that it was because I was using the EntityManager.getReference() method that I was getting the above exception as the method was returning a proxy.在研究了一段时间后,我终于发现这是因为我正在使用 EntityManager.getReference() 方法,当该方法返回代理时,我收到了上述异常。

This makes me wonder, when is it advisable to use the EntityManager.getReference() method instead of the EntityManager.find() method ?这让我想知道,什么时候最好使用 EntityManager.getReference() 方法而不是 EntityManager.find() 方法

EntityManager.getReference() throws an EntityNotFoundException if it cant find the entity being searched for which is very convenient in itself.如果 EntityManager.getReference() 找不到正在搜索的实体,它会抛出一个 EntityNotFoundException ,这本身就很方便。 EntityManager.find() method merely returns null if it cant find the entity. EntityManager.find() 方法仅在找不到实体时返回 null。

With regards to transaction boundaries, sounds to me like you would need to use the find() method before passing the newly found entity to a new transaction.关于事务边界,在我看来,您需要在将新找到的实体传递给新事务之前使用 find() 方法。 If you use the getReference() method then you would probably end up in a situation similar to mine with the above exception.如果您使用 getReference() 方法,那么您可能会遇到与我类似的情况,但有上述例外情况。

I usually use getReference method when i do not need to access database state (I mean getter method).当我不需要访问数据库状态时,我通常使用getReference方法(我的意思是 getter 方法)。 Just to change state (I mean setter method).只是为了改变状态(我的意思是 setter 方法)。 As you should know, getReference returns a proxy object which uses a powerful feature called automatic dirty checking.您应该知道,getReference 返回一个代理对象,该对象使用称为自动脏检查的强大功能。 Suppose the following假设以下

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);
    }

}

If i call find method, JPA provider, behind the scenes, will call如果我调用find方法,幕后的 JPA 提供者将调用

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

If i call getReference method, JPA provider, behind the scenes, will call如果我调用getReference方法,幕后的 JPA 提供者将调用

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

And you know why ???你知道为什么吗???

When you call getReference, you will get a proxy object.当您调用 getReference 时,您将获得一个代理对象。 Something like this one (JPA provider takes care of implementing this proxy)像这样的东西(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;
    }

}

So before transaction commit, JPA provider will see stateChanged flag in order to update OR NOT person entity.所以在事务提交之前,JPA 提供者将看到 stateChanged 标志以更新 OR NOT 人实体。 If no rows is updated after update statement, JPA provider will throw EntityNotFoundException according to JPA specification.如果在更新语句后没有更新行,JPA 提供程序将根据 JPA 规范抛出 EntityNotFoundException。

regards,问候,

Assuming you have a parent Post entity and a child PostComment as illustrated in the following diagram:假设您有一个父Post实体和一个子PostComment ,如下图所示:

在此处输入图片说明

If you call find when you try to set the @ManyToOne post association:如果在尝试设置@ManyToOne post关联时调用find

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

Hibernate will execute the following statements: 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)

The SELECT query is useless this time because we don't need the Post entity to be fetched.这次 SELECT 查询没有用,因为我们不需要获取 Post 实体。 We only want to set the underlying post_id Foreign Key column.我们只想设置底层的 post_id 外键列。

Now, if you use getReference instead:现在,如果您改用getReference

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

This time, Hibernate will issue just the INSERT statement:这一次,Hibernate 将只发出 INSERT 语句:

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

Unlike find , the getReference only returns an entity Proxy which only has the identifier set.find不同, getReference只返回一个只有标识符集的实体代理。 If you access the Proxy, the associated SQL statement will be triggered as long as the EntityManager is still open.如果访问了 Proxy,只要 EntityManager 仍然打开,就会触发关联的 SQL 语句。

However, in this case, we don't need to access the entity Proxy.但是,在这种情况下,我们不需要访问实体代理。 We only want to propagate the Foreign Key to the underlying table record so loading a Proxy is sufficient for this use case.我们只想将外键传播到基础表记录,因此对于此用例加载代理就足够了。

When loading a Proxy, you need to be aware that a LazyInitializationException can be thrown if you try to access the Proxy reference after the EntityManager is closed.在加载 Proxy 时,您需要注意,如果您在 EntityManager 关闭后尝试访问 Proxy 引用,则可能会抛出LazyInitializationException

This makes me wonder, when is it advisable to use the EntityManager.getReference() method instead of the EntityManager.find() method?这让我想知道,什么时候最好使用 EntityManager.getReference() 方法而不是 EntityManager.find() 方法?

EntityManager.getReference() is really an error prone method and there is really very few cases where a client code needs to use it. EntityManager.getReference()确实是一个容易出错的方法,并且客户端代码需要使用它的情况确实很少。
Personally, I never needed to use it.就个人而言,我从来不需要使用它。

EntityManager.getReference() and EntityManager.find() : no difference in terms of overhead EntityManager.getReference() 和 EntityManager.find() :在开销方面没有区别

I disagree with the accepted answer and particularly :我不同意接受的答案,特别是:

If i call find method, JPA provider, behind the scenes, will call如果我调用find方法,幕后的 JPA 提供者将调用

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

If i call getReference method, JPA provider, behind the scenes, will call如果我调用getReference方法,幕后的 JPA 提供者将调用

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

It is not the behavior that I get with Hibernate 5 and the javadoc of getReference() doesn't say such a thing :这不是我在 Hibernate 5 中得到的行为, getReference()的 javadoc 没有说这样的话:

Get an instance, whose state may be lazily fetched.获取一个实例,其状态可能会被延迟获取。 If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed.如果请求的实例在数据库中不存在,则在第一次访问实例状态时抛出 EntityNotFoundException。 (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open. (当调用 getReference 时,持久性提供程序运行时被允许抛出 EntityNotFoundException。)应用程序不应期望实例状态在分离时可用,除非它在实体管理器打开时被应用程序访问。

EntityManager.getReference() spares a query to retrieve the entity in two cases : EntityManager.getReference()在两种情况下通过查询来检索实体:

1) if the entity is stored in the Persistence context, that is the first level cache. 1) 如果实体存储在 Persistence 上下文中,那就是一级缓存。
And this behavior is not specific to EntityManager.getReference() , EntityManager.find() will also spare a query to retrieve the entity if the entity is stored in the Persistence context.并且此行为并非特定于EntityManager.getReference() ,如果实体存储在持久性上下文中, EntityManager.find()还将节省查询以检索实体。

You can check the first point with any example.您可以使用任何示例检查第一点。
You can also rely on the actual Hibernate implementation.您还可以依赖实际的 Hibernate 实现。
Indeed, EntityManager.getReference() relies on the createProxyIfNecessary() method of the org.hibernate.event.internal.DefaultLoadEventListener class to load the entity.实际上, EntityManager.getReference()依赖于org.hibernate.event.internal.DefaultLoadEventListener类的createProxyIfNecessary()方法来加载实体。
Here is its implementation :这是它的实现:

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;
}

The interesting part is :有趣的部分是:

Object existing = persistenceContext.getEntity( keyToLoad );

2) If we don't effectively manipulate the entity, echoing to the lazily fetched of the javadoc. 2)如果我们没有有效地操作实体,就会与javadoc的懒惰获取相呼应。
Indeed, to ensure the effective loading of the entity, invoking a method on it is required.实际上,为了确保实体的有效加载,需要对其调用方法。
So the gain would be related to a scenario where we want to load a entity without having the need to use it ?那么增益将与我们想要加载一个实体而不需要使用它的场景有关? In the frame of applications, this need is really uncommon and in addition the getReference() behavior is also very misleading if you read the next part.在应用程序框架中,这种需求确实不常见,此外,如果您阅读下一部分, getReference()行为也非常具有误导性。

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

In terms of overhead, getReference() is not better than find() as discussed in the previous point.就开销而言, getReference()并不比上一点中讨论的find()好。
So why use the one or the other ?那么为什么要使用一个或另一个呢?

Invoking getReference() may return a lazily fetched entity.调用getReference()可能会返回一个延迟获取的实体。
Here, the lazy fetching doesn't refer to relationships of the entity but the entity itself.在这里,延迟获取不是指实体的关系,而是实体本身。
It means that if we invoke getReference() and then the Persistence context is closed, the entity may be never loaded and so the result is really unpredictable.这意味着如果我们调用getReference()然后关闭 Persistence 上下文,则实体可能永远不会加载,因此结果真的不可预测。 For example if the proxy object is serialized, you could get a null reference as serialized result or if a method is invoked on the proxy object, an exception such as LazyInitializationException is thrown.例如,如果代理对象被序列化,您可能会得到一个null引用作为序列化结果,或者如果在代理对象上调用一个方法,则会抛出异常,例如LazyInitializationException

It means that the throw of EntityNotFoundException that is the main reason to use getReference() to handle an instance that does not exist in the database as an error situation may be never performed while the entity is not existing.这意味着EntityNotFoundException的抛出是使用getReference()处理数据库中不存在的实例的主要原因,因为当实体不存在时,可能永远不会执行错误情况。

EntityManager.find() doesn't have the ambition of throwing EntityNotFoundException if the entity is not found.如果未找到实体, EntityManager.find()没有抛出EntityNotFoundException的野心。 Its behavior is both simple and clear.它的行为既简单又清晰。 You will never have surprise as it returns always a loaded entity or null (if the entity is not found) but never an entity under the shape of a proxy that may not be effectively loaded.您永远不会感到惊讶,因为它始终返回已加载的实体或null (如果未找到该实体),但永远不会返回可能无法有效加载的代理形状下的实体。
So EntityManager.find() should be favored in the very most of cases.所以EntityManager.find()在大多数情况下应该受到青睐。

Because a reference is 'managed', but not hydrated, it can also allow you to remove an entity by ID, without needing to load it into memory first.因为引用是“托管”的,但不是水合的,它也可以允许您通过 ID 删除实体,而无需先将其加载到内存中。

As you can't remove an unmanaged entity, it's just plain silly to load all fields using find(...) or createQuery(...), only to immediately delete it.由于您无法删除非托管实体,因此使用 find(...) 或 createQuery(...) 加载所有字段只是为了立即将其删除,这是非常愚蠢的。

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);

I disagree with the selected answer, and as davidxxx correctly pointed out, getReference does not provide such behaviour of dynamic updations without select.我不同意所选的答案,正如 davidxxx 正确指出的那样,getReference 不提供这种没有选择的动态更新行为。 I asked a question concerning the validity of this answer, see here - cannot update without issuing select on using setter after getReference() of hibernate JPA .我问了一个有关此答案有效性的问题,请参见此处 - 在休眠 JPA 的 getReference() 之后使用 setter 时不发出选择,则无法更新

I quite honestly haven't seen anybody who's actually used that functionality.老实说,我还没有看到任何人真正使用过该功能。 ANYWHERE.任何地方。 And i don't understand why it's so upvoted.我不明白为什么它如此受欢迎。

Now first of all, no matter what you call on a hibernate proxy object, a setter or a getter, an SQL is fired and the object is loaded.现在首先,无论您对休眠代理对象、setter 或 getter 调用什么,都会触发 SQL 并加载对象。

But then i thought, so what if JPA getReference() proxy doesn't provide that functionality.但后来我想,如果 JPA getReference() 代理不提供该功能怎么办。 I can just write my own proxy.我可以编写自己的代理。

Now, we can all argue that selects on primary keys are as fast as a query can get and it's not really something to go to great lengths to avoid.现在,我们都可以争辩说,主键上的选择与查询可以获得的速度一样快,而且实际上并不是要竭尽全力避免。 But for those of us who can't handle it due to one reason or another, below is an implementation of such a proxy.但是对于我们这些由于某种原因无法处理它的人来说,下面是这种代理的实现。 But before i you see the implementation, see it's usage and how simple it is to use.但是在您看到实现之前,请先了解它的用法以及使用起来有多简单。

USAGE用法

Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);

And this would fire the following query -这将触发以下查询 -

UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;

and even if you want to insert, you can still do PersistenceService.save(new Order("a", 2));即使你想插入,你仍然可以做 PersistenceService.save(new Order("a", 2)); and it would fire an insert as it should.它会按它应该的方式触发插入。

IMPLEMENTATION执行

Add this to your pom.xml -将此添加到您的 pom.xml -

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

Make this class to create dynamic proxy -让这个类创建动态代理 -

@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());
    }
}

Make an interface with all the methods -为所有方法制作一个接口 -

public interface EnhancedProxy {
    public String getJPQLUpdate();
    public HashMap<String, Object> getModifiedFields();
}

Now, make an interceptor which will allow you to implement these methods on your proxy -现在,制作一个拦截器,它允许您在代理上实现这些方法 -

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!");
}
}

And the exception class -和异常类 -

public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
    super(message);
}

A service to save using this proxy -使用此代理保存的服务 -

@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);
    }

}
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在Spring Boot中使用JPA的entityManager.getReference? - How do I use JPA's entityManager.getReference in Spring Boot? EntityManager.find()和EntityManger.getReference()之间有什么区别? - What is the difference between EntityManager.find() and EntityManger.getReference()? JPA继承entitymanager.find产生ClassCastException - JPA Inheritance entitymanager.find produces ClassCastException 当id(primay键)大于1时,如何获取EntityManager.getReference? - How can I get EntityManager.getReference when the id(primay keys) are more than 1? JPA 2(EclipseLink)尝试使用UUID作为主键EntityManager.find()始终抛出异常(Database is PostgreSQL) - JPA 2 (EclipseLink) Trying to use UUID as primary key EntityManager.find() always throws exception (Database is PostgreSQL) jpa-springentitymanager.find超时不起作用 - jpa-spring entitymanager.find timeout not working 调用entityManager.find()是否需要EntityTransaction? - Does calling entityManager.find() require an EntityTransaction? EntityManager.find方法导致MySQLIntegrityConstraintViolationException - EntityManager.find method leading to MySQLIntegrityConstraintViolationException EntityManager.find 返回之前删除的实体 - EntityManager.find returns previously deleted entity Jpa 2.0 - EntityManager.find(SomeEntity.class,PK)需要将Descriminator值填入key - Jpa 2.0 - EntityManager.find (SomeEntity.class,PK) need to fill Descriminator value to key
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM