EntityManager.find()とEntityManager.getReference()をJPAで使用する場合


103

EntityManager.getReference(LObj.getClass()、LObj.getId())を使用してデータベースエンティティを取得し、返されたオブジェクトを別のテーブルに永続化されます。

つまり、基本的には次のような流れでした。

クラス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
クラス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.find()メソッドの代わりにEntityManager.getReference()メソッドを使用することが賢明かを疑問に思いますか?

EntityManager.getReference()は、検索対象のエンティティ自体が見つからない場合にEntityNotFoundExceptionをスローします。EntityManager.find()メソッドは、エンティティが見つからない場合は単にnullを返します。

トランザクション境界に関しては、新しく見つかったエンティティを新しいトランザクションに渡す前にfind()メソッドを使用する必要があるように思えます。getReference()メソッドを使用すると、上記の例外を除いて、おそらく私の場合と同じような状況になります。


忘れてしまいましたが、JPAプロバイダーとしてHibernateを使用しています。
SibzTer 2009年

回答:


152

データベースの状態にアクセスする必要がない場合は、通常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フラグを確認します。updateステートメントの後に行が更新されない場合、JPAプロバイダーは、JPA仕様に従ってEntityNotFoundExceptionをスローします。

よろしく、


4
EclipseLink 2.5.0を使用していますが、上記のクエリが正しくありません。それは、常に発行SELECTUPDATEのどの関係なく、find()/ getReference()Iの使用。さらに悪いことに、1つのエンティティの単一のフィールドを更新したいだけですが、SELECT非レイジー関係(新しいを発行SELECTS)をトラバースします。
Dejan Milosevic 2014

1
@Arthur Ronald getReferenceによって呼び出されたエンティティにVersionアノテーションがあるとどうなりますか?
David Hofmann 2016年

私は@DejanMilosevicと同じ問題があります。getReference()を介して取得したエンティティを削除すると、そのエンティティに対してSELECTが発行され、そのエンティティのすべてのLAZY関係を通過するため、(EclipseLink 2.5.0で)多くのSELECTSが発行されます。
ステファンAppercel

27

私はで説明したように、この記事あなたは親持っていると仮定すると、Postエンティティと子をPostComment次の図に示すように:

ここに画像の説明を入力してください

アソシエーション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)

今回は、Postエンティティをフェッチする必要がないため、SELECTクエリは役に立ちません。基になる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、はgetReferenceIDのみが設定されているエンティティプロキシのみを返します。プロキシにアクセスすると、EntityManagerがまだ開いている限り、関連するSQLステートメントがトリガーされます。

ただし、この場合、エンティティプロキシにアクセスする必要はありません。外部キーを基になるテーブルレコードに伝達するだけなので、この使用例ではプロキシをロードするだけで十分です。

プロキシをロードするとき、EntityManagerが閉じた後にプロキシ参照にアクセスしようとすると、LazyInitializationExceptionがスローされる可能性があることに注意する必要があります。の取り扱いについて詳しくはLazyInitializationExceptionこちらの記事をご覧ください。


1
Vladにこれを知らせてくれてありがとう!しかし、javadocによると、これは厄介なようです:「永続性プロバイダーランタイムは、getReferenceが呼び出されたときにEntityNotFoundExceptionをスローすることが許可されています」。これは、(少なくとも行の存在を確認するために)SELECTなしでは不可能です。したがって、最終的にはSELECTは実装に依存します。
adrhc

3
説明した使用例では、Hibernateがhibernate.jpa.compliance.proxy構成プロパティを提供しているため、JPAコンプライアンスまたはより優れたデータアクセスパフォーマンスのいずれかを選択できます。
Vlad Mihalcea

@VladMihalcea getReferencePKが設定されたモデルの新しいインスタンスを設定するだけで十分な場合、なぜ必要なのか。何が欠けていますか?
リラビ

これはHibernareaでのみサポートされており、トラバースした場合に関連付けをロードできません。
Vlad Mihalcea

8

参照は「管理」されていますが、ハイドレートされていないため、最初にメモリにロードする必要なしに、IDでエンティティを削除することもできます。

管理されていないエンティティを削除することはできないため、find(...)またはcreateQuery(...)を使用してすべてのフィールドをロードするのはばかげたことで、すぐに削除するだけです。

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

7

これは、いつEntityManager.find()メソッドの代わりにEntityManager.getReference()メソッドを使用することが賢明なのでしょうか?

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() 次の2つの場合にエンティティを取得するクエリを省略します。

1)エンティティが永続化コンテキストに格納されている場合、それは第1レベルのキャッシュです。
また、この動作はに固有のものEntityManager.getReference()で はなくEntityManager.find()、エンティティが永続コンテキストに格納されている場合、エンティティを取得するクエリも不要になります。

どんな例でも最初のポイントをチェックできます。
実際の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()から永続コンテキストを閉じると、エンティティがロードされない可能性があるため、結果は実際には予測できません。たとえば、プロキシオブジェクトがシリアル化されている場合、null参照をシリアル化された結果として取得できます。また、プロキシオブジェクトでメソッドが呼び出された場合、などの例外LazyInitializationExceptionがスローされます。

つまり、エンティティが存在しない間はエラー状況が実行されない可能性があるため、データベースに存在しないインスタンスを処理するEntityNotFoundExceptionために使用getReference()する主な理由は、そのスローです。

EntityManager.find()EntityNotFoundExceptionエンティティが見つからない場合にスローするという野心はありません。その動作は単純かつ明確です。常に読み込まれたエンティティ、またはnull(エンティティが見つからない場合)を返すため、驚きはありませんが、効果的に読み込まれないプロキシの形のエンティティは決してありません。
そのEntityManager.find()ため、ほとんどの場合に推奨されます。


受け入れられた応答+ Vlad Mihalceaの応答+ Vlad Mihalceaへの私のコメント(おそらく最後の+ではないかもしれません)と比較すると、あなたの理由は誤解を招くものです。
adrhc

1
Pro JPA2は次のように述べています。「getReference()を使用できる非常に特殊な状況では、find()は事実上すべてのケースで使用する必要があります」。
JL_SO 2018

この質問に賛成票を投じてください。この質問は、受け入れられた回答を補完する必要があるためです。また、私のテストでは、エンティティプロキシのプロパティを設定すると、受け入れられた回答の内容とは逆に、データベースからフェッチされることが示されました。Vladによって述べられたケースだけが私のテストに合格しました。
ジョアンFé

2

選択した回答に同意しません。davidxxxが正しく指摘したように、getReferenceは、選択なしの動的更新のそのような動作を提供しません。この回答の有効性について質問しました。ここを参照してください-hibernate JPAのgetReference()の後にセッターを使用してselectを発行しないと更新できません

正直に言って、実際にその機能を使用している人を見たことはありません。どこでも。そして、なぜそれがそんなに賛成されているのか理解できません。

まず最初に、休止状態のプロキシオブジェクト、セッター、ゲッターに何を呼び出しても、SQLが起動され、オブジェクトがロードされます。

しかし、私は考えたので、JPA 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;

挿入したい場合でも、PersistenceService.save(new Order( "a"、2));を実行できます。必要に応じて挿入を実行します。

実装

これをpom.xmlに追加します-

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

}
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.