web-dev-qa-db-ja.com

JPAとJTA:エンティティの永続化とカスケードされた子エンティティのマージ

次のエンティティクラスと双方向の1対多の関係があります。

0または1クライアント<-> 0以上の製品注文

クライアントエンティティを永続化するとき、関連付けられた製品注文エンティティも永続化する必要があります(「親」クライアントへの外部キーが更新されている可能性があるため)。

もちろん、必要なCASCADEオプションはすべてクライアント側で設定されます。ただし、このシナリオのように、既存の製品注文を参照しているときに、新しく作成されたクライアントが初めて永続化される場合は機能しません。

  1. 商品注文「1」が作成され、保持されます。正常に動作します。
  2. クライアント「2」が作成され、製品注文「1」がその製品注文リストに追加されます。その後、永続化されます。動作しません。

いくつかの方法を試しましたが、どれも期待した結果を示しませんでした。以下の結果を参照してください。ここですべての関連する質問を読みましたが、それらは私を助けませんでした。 GlassFish 3.1.2のApache Derby(JavaDB)インメモリDBのトランザクションタイプとして、EclipseLink 2.3.0、純粋なJPA 2.0アノテーション、およびJTAを使用しています。エンティティの関係は、JSF GUIによって管理されます。オブジェクトレベルの関係管理は(永続化は別として)機能し、JUnitテストでテストしました。

アプローチ1)「デフォルト」(NetBeansクラステンプレートに基づく)

クライアント:

@Entity
public class Client implements Serializable, ParentEntity {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToMany(mappedBy = "client", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH},
            fetch= FetchType.LAZY)
    private List<ProductOrder> orders = new ArrayList<>();

    // other fields, getters and setters
}

ProductOrder:

@Entity
public class ProductOrder implements Serializable, ChildEntity {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne // owning side
    private Client client;

    // other fields, getters and setters
}

Generic Persistence Facade:

// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
    getEntityManager().persist(entity);
}

// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
    getEntityManager().merge(entity);
}

結果:

create()はすぐにこの例外をスローします。

警告:EJB ClientFacadeメソッドの呼び出し中にシステム例外が発生しましたpublic void javaee6test.beans.AbstractFacade.create(Java.lang.Object)javax.ejb.EJBException:Transaction aborted ...

原因:javax.transaction.RollbackException:ロールバック対象としてマークされたトランザクション。 ...

発生原因:例外[EclipseLink-4002](Eclipse Persistence Services-2.3.0.v20110604-r9504):org.Eclipse.persistence.exceptions.DatabaseException内部例外:Java.sql.SQLIntegrityConstraintViolationException:ステートメントが中止されたため'PRODUCTORDER'で定義された 'SQL120513133540930'によって識別される一意または主キー制約または一意のインデックスに重複したキー値が発生しました。エラーコード:-1呼び出し:INSERT INTO PRODUCTORDER(ID、CLIENT_ID)VALUES(?、?)bind => [2パラメータバインド]クエリ:InsertObjectQuery(javaee6test.model.ProductOrder [id = 1])...

原因:Java.sql.SQLIntegrityConstraintViolationException:「PRO-DUCTORDER」で定義された「SQL120513133540930」で識別される一意または主キー制約または一意のインデックスで重複するキー値が発生したため、ステートメントが中止されました。 ...

原因:org.Apache.derby.client.am.SqlException:一意または主キーの制約、または 'SQL120513133540930'で定義された 'SQL120513133540930'で識別される一意のインデックスで重複するキー値が発生するため、ステートメントが中止されましたPRODUCTOR-DER '。

この例外を理解できません。 edit()は正常に動作します。しかし、作成時にクライアントに製品注文を追加したいので、これでは不十分です。

アプローチ2)merge()のみ

Generic Persistence Facadeへの変更:

// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
    getEntityManager().merge(entity);
}

// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
    getEntityManager().merge(entity);
}

結果:

Create()では、EclipseLink Loggingの出力は次のように述べています。

罰金:INSERT INTO CLIENT(ID、NAME、ADDRESS_ID)VALUES(?、?、?)bind => [3つのパラメーターがバインドされています]

ただし、製品注文表には「更新」はありません。したがって、関係は確立されません。一方、edit()は正常に動作します。

アプローチ3)両方のエンティティタイプのID GenerationType.IDENTITY

クライアントと製品の両方の注文クラスの変更:

...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...

結果:

Create()では、EclipseLink Loggingの出力は次のように述べています。

罰金:INSERT INTO CLIENT(NAME、ADDRESS_ID)VALUES(?、?)bind => [2つのパラメーターがバインドされています]

罰金:値IDENTITY_VAL_LOCAL()

ファイン:INSERT INTO PRODUCTORDER(ORDERDATE、CLIENT_ID)VALUES(?、?)bind => [バインドされた2つのパラメーター]

罰金:値IDENTITY_VAL_LOCAL()

したがって、クライアントのリストに追加された製品注文との関係を確立する代わりに、新しい製品注文エンティティが作成されて永続化(!)され、そのエンティティとの関係が確立されます。ここでも同じですが、edit()は正常に機能します。

アプローチ4)アプローチ(2)と(3)の組み合わせ

結果:アプローチ(2)と同じ。

私の質問は:上記のシナリオを実現する方法はありますか? JPAを使い続けたい(ベンダー固有のソリューションではない)。

19
SputNick

こんにちは、今日同じ問題がありました。このメールをopenJPAメーリングリストにお願いします。

こんにちは。同じエンティティでの参照の挿入と更新に問題があります。

別のオブジェクト(Person)への参照を持つ新しいオブジェクト(Exam)を挿入しようとしていますが、同時にPersonオブジェクトの属性(birthDate)を更新します。 CascadeTypeをALLに設定しても、更新は行われません。これが機能する唯一の方法は、永続化を行い、その後マージ操作を行うことです。これは正常ですか?何かを変更する必要がありますか?

ユーザーが更新したいオブジェクト(Examの子オブジェクト)の数がわからないため、Personオブジェクトでマージを使用する「手動更新」のアイデアは好きではありません。

エンティティ:

public class Exam{  
   @ManyToOne(cascade= CascadeType.ALL)
   @JoinColumn(name = "person_id")
   public Person person;
......
}

public class Person{
    private Date birthDate;
   @OneToMany(mappedBy = "person")
    private List<Exam> exams
.......
}

public class SomeClass{
   public void someMethod(){
      exam = new Exam()
      person.setBirthDate(new Date());
      exam.setPerson(person); 
      someEJB.saveExam(exam);
   }
}

public class someEJB(){

   public void saveExam(Exam exam){
        ejbContext.getUserTransaction().begin();
        em.persist(exam);
        //THIS WORKS
        em.merge(exam.getPerson());
        ejbContext.getUserTransaction().commit();       
   }

}

すべての子オブジェクトにMERGEメソッドを使用する必要がありますか?

そして答えはこれでした:

問題は試験が新しいものであるように見えますが、Personが存在し、persist操作をカスケードすると既存のエンティティが無視されます。これは期待どおりに機能していると思います。

関係がCascadeType.ALLに設定されている限り、em.persist(exam);を常に変更できます。 em.merge(exam);へ。これにより、新しい試験の永続化が処理され、マージコールがユーザーにカスケードされます。

ありがとう、リック


これがお役に立てば幸いです。

7
maxtorzito

@SputNick、以下の手順で解決しました。コードスニペットを使用して、私が行ったことを説明します。

Client entity-@ OneToManyProductOrder entity-@ ManyToOneがあります。

_// Called when pressing "save" on the "edit..." 
public void edit(T entity) {
 getEntityManager().merge(entity);}
_

保存と更新を柔軟に行えるため、永続化のため。

作成するにはクライアントと対応する製品の注文を使用します

_client.set..(some property to be saved);

productOrder.set..(some property to be saved);
_

//クライアントのproductOrderを設定します

_List<ProductOrder> order = new ArrayList<>();
_

client.setOrders(order); //これはリストが渡されることを期待していることに注意してください

// productOrderのクライアントを設定します

_productOrder.setClient(productOrder);

.edit(client) or .edit(productOrder)
_

どちらの方法でも、変更内容で両方のエンティティテーブルを更新できることに注意してください。

遅くても、誰かを助けることを願っています

1
Fredrick Aponyo

関係の両側を設定していることを確認します。注文をクライアントに追加するだけでなく、注文のクライアントも設定する必要があります。また、変更した両方のオブジェクトをマージする必要があります。クライアントをマージするだけの場合、注文のクライアントはマージされません(ただし、カスケードによりマージされます)。

永続化は機能しません。永続化は永続化されるオブジェクトが永続化コンテキストに対して正しい必要があるためです。つまり、デタッチされたオブジェクトを参照せず、管理対象オブジェクトを参照する必要があります。

あなたの問題はあなたがオブジェクトを切り離すことに起因します。オブジェクトを切り離さなかった場合、同じ問題は発生しません。通常、JPAでは、EntityManagerを作成し、オブジェクトを検索/クエリし、編集/永続化し、コミットを呼び出します。マージは必要ありません。

0
James

ProductOrderエンティティで@Joincolumnアノテーションを使用します。以下を参照してください。

   @Entity
   public class ProductOrder implements Serializable, ChildEntity {
   private static final long serialVersionUID = 1L;

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long id;

   @ManyToOne // owning side
   @JoinColumn(name = "PRODUCTORDER_ID", referencedColumnName = "ID")
   //write your database column names into name and referencedColumnName attributes.
   private Client client;

   // other fields, getters and setters
   }