私は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:Unknown entity:com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0」という例外が発生していました
しばらく調べてみると、EntityManager.getReference()メソッドを使用していたために、メソッドがプロキシを返しているときに上記の例外が発生していることがわかりました。
EntityManager.find()メソッドの代わりにEntityManager.getReference()メソッドを使用することをお勧めします?
EntityManager.getReference()は、それ自体で非常に便利な検索対象のエンティティが見つからない場合、EntityNotFoundExceptionをスローします。 EntityManager.find()メソッドは、エンティティを見つけることができない場合、単にnullを返します。
トランザクションの境界に関しては、新しく見つかったエンティティを新しいトランザクションに渡す前にfind()メソッドを使用する必要があるように思えます。 getReference()メソッドを使用すると、おそらく上記の例外を除いて、私のような状況になります。
データベースの状態にアクセスする必要がないときは、通常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 methodを呼び出すと、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プロバイダーはOR NOT個人エンティティを更新するためにstateChangedフラグを確認します。 updateステートメントの後に行が更新されない場合、JPAプロバイダーはJPA仕様に従ってEntityNotFoundExceptionをスローします。
よろしく、
この記事 で説明したように、次の図に示すように、親Post
エンティティと子PostComment
があると仮定します。
@ManyToOne
find
関連付けを設定しようとするときに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)
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)
find
とは異なり、getReference
は、識別子のみが設定されたエンティティプロキシのみを返します。プロキシにアクセスすると、EntityManagerが開いている限り、関連するSQLステートメントがトリガーされます。
ただし、この場合、エンティティプロキシにアクセスする必要はありません。外部キーを基礎となるテーブルレコードに伝播するだけなので、このユースケースではプロキシをロードするだけで十分です。
プロキシをロードする場合、EntityManagerを閉じた後にプロキシ参照にアクセスしようとすると、LazyInitializationExceptionがスローされる可能性があることに注意する必要があります。 LazyInitializationException
の処理の詳細については、 この記事 をご覧ください。
参照は「管理」されていますが、水和されていないため、最初にメモリにロードする必要なく、IDによってエンティティを削除することもできます。
管理されていないエンティティを削除することはできないため、find(...)またはcreateQuery(...)を使用してすべてのフィールドをロードし、ただそれをすぐに削除するのは単純に愚かなことです。
MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
これは、EntityManager.find()メソッドの代わりにEntityManager.getReference()メソッドを使用することが推奨されるのはいつですか?
EntityManager.getReference()
は実際にはエラーが発生しやすいメソッドであり、クライアントコードで使用する必要があるケースはほとんどありません。
個人的には、使用する必要はありませんでした。
私は受け入れられた答えに反対し、特に:
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で得られる動作ではなく、getReference()
のjavadocはそのようなことを言っていません:
インスタンスを取得します。このインスタンスの状態は遅延フェッチされます。要求されたインスタンスがデータベースに存在しない場合、インスタンスの状態に最初にアクセスしたときにEntityNotFoundExceptionがスローされます。 (永続化プロバイダーランタイムは、getReferenceが呼び出されたときにEntityNotFoundExceptionをスローすることを許可されています。)アプリケーションは、エンティティマネージャーが開いている間にアプリケーションからアクセスされない限り、分離時にインスタンス状態が利用可能になることを予期してはなりません。
EntityManager.getReference()
は、次の2つの場合にエンティティを取得するクエリを省略します。
1)エンティティが永続コンテキストに保存されている場合、それは一次キャッシュです。
この動作はEntityManager.getReference()
に固有のものではありません。エンティティがPersistenceコンテキストに保存されている場合、EntityManager.find()
もエンティティを取得するクエリを省きます。
どの例でも最初の点を確認できます。
実際のHibernate実装に依存することもできます。
実際、EntityManager.getReference()
はorg.hibernate.event.internal.DefaultLoadEventListener
クラスのcreateProxyIfNecessary()
メソッドに依存してエンティティをロードします。
その実装は次のとおりです。
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のlazily fetchedにエコーします。
実際、エンティティの効果的なロードを確実に行うには、エンティティのメソッドを呼び出す必要があります。
それで、ゲインは、エンティティを使用する必要なしにロードするシナリオに関連しますか?アプリケーションのフレームでは、この必要性は非常にまれであり、さらに次のパートを読むとgetReference()
動作も非常に誤解を招く可能性があります。
オーバーヘッドの観点では、getReference()
は前のポイントで説明したfind()
よりも優れていません。
では、どちらを使用するのですか?
getReference()
を呼び出すと、遅延フェッチされたエンティティが返される場合があります。
ここで、遅延フェッチはエンティティの関係ではなく、エンティティ自体を指します。
これは、getReference()
を呼び出した後、永続コンテキストが閉じられると、エンティティがロードされない可能性があるため、結果が実際に予測不能であることを意味します。たとえば、プロキシオブジェクトがシリアル化されている場合、シリアル化された結果としてnull
参照を取得できます。または、プロキシオブジェクトでメソッドが呼び出された場合、LazyInitializationException
などの例外がスローされます。
これは、getReference()
を使用してデータベースに存在しないインスタンスをエラー状態として処理する主な理由であるEntityNotFoundException
のスローは、エンティティが存在しない間は実行されない可能性があることを意味します。
EntityManager.find()
には、エンティティが見つからない場合にEntityNotFoundException
をスローするという野望がありません。その動作は単純で明確です。常にロードされたエンティティまたはnull
(エンティティが見つからない場合)を返すため、驚くことはありませんが、プロキシの形状の下にあるエンティティが効果的にロードされない可能性があります。
したがって、ほとんどの場合、EntityManager.find()
を優先する必要があります。
私は選択した答えに同意せず、davidxxxが正しく指摘しているように、getReferenceは動的な更新の動作を選択なしで提供しません。この回答の有効性について質問しました。こちらをご覧ください- hibernate JPA のgetReference()の後にセッターを使用してselectを発行しないと更新できません。
正直言って、実際にその機能を使った人は誰もいません。どこでも。そして、どうしてそんなに支持されているのか分かりません。
まず、Hibernateプロキシオブジェクト、セッター、またはゲッターで何を呼び出しても、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);
}
}
}