5

User と UserGroup という典型的な 2 つのエンティティを持つアプリケーションを作成しています。後者には、前者のインスタンスが 1 つ以上含まれる場合があります。そのために次の(多かれ少なかれ)マッピングがあります:

ユーザー:

public class User {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne(cascade = {CascadeType.MERGE})
    @JoinColumn(name="GROUP_ID")
    private UserGroup group;

    public UserGroup getGroup() {
        return group;
    }

    public void setGroup(UserGroup group) {
        this.group = group;
    }
}

ユーザー・グループ:

public class UserGroup {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class)
    private Set<User> users;

    public void setUsers(Set<User> users) {
        this.users = users;
    }

}

これで、これらのエンティティ (UserDao と UserGroupDao) ごとに個別の DAO クラスができました。私のすべての DAO には、次のように @PersistenceContext アノテーションを使用して EntityManager が挿入されています。

@Transactional
public class SomeDao<T> {

   private Class<T> persistentClass;

   @PersistenceContext
   private EntityManager em;

   public T findById(long id) {
       return em.find(persistentClass, id);
   }

   public void save(T entity) {
       em.persist(entity);
   }
}

このレイアウトを使用して、新しいユーザーを作成し、既存のユーザー グループに割り当てます。私はこのようにします:

UserGroup ug = userGroupDao.findById(1);

User u = new User();
u.setName("john");
u.setGroup(ug);

userDao.save(u);

残念ながら、次の例外が発生します。

オブジェクトが保存されていない一時インスタンスを参照しています - フラッシュする前に一時インスタンスを保存します: xyzmodel.User.group -> xyzmodel.UserGroup

私はそれを調査しましたが、各DAOインスタンスに異なるentityManagerが割り当てられており(各DAOのエンティティマネージャーへの参照が異なることを確認しました)、ユーザーのentityManagerが渡されたUserGroupインスタンスを管理していないために発生すると思います。

ユーザーに割り当てられたユーザー グループを UserDAO のエンティティ マネージャーにマージしようとしました。これには 2 つの問題があります。

  • それでも機能しません-エンティティマネージャーは既存のユーザーグループを上書きしようとしており、例外が発生します(明らかに)
  • うまくいったとしても、関連するエンティティごとにマージコードを書くことになります

説明されているケースは、検索と永続化の両方が同じエンティティ マネージャーを使用して行われる場合に機能します。これは質問を指します:

  • 私のデザインは壊れていますか?この回答で推奨されているものとかなり似ていると思います。すべての DAO に対して単一の EntityManager が存在する必要がありますか (Web はそうではないと主張しています)。
  • または、グループの割り当ては DAO 内で行う必要がありますか? この場合、DAO に多くのコードを書くことになります。
  • DAO を削除する必要がありますか? はいの場合、データ アクセスを適切に処理するにはどうすればよいですか?
  • 他の解決策はありますか?

Spring をコンテナーとして使用し、Hibernate を JPA 実装として使用しています。

4

3 に答える 3

3

EntityManager の異なるインスタンスは、Spring では正常です。存在する場合、現在トランザクションにあるエンティティ マネージャを動的に使用するプロキシを作成します。それ以外の場合は、新しいものが作成されます。

問題は、トランザクションが短すぎることです。ユーザー グループの取得はトランザクションで実行されます (findByIdメソッドが暗黙的に であるため@Transactional)。しかしその後、トランザクションがコミットされ、グループは切り離されます。新しいユーザーを保存すると、ユーザーが切り離されたエンティティを参照するために失敗する新しいトランザクションが作成されます。

これを解決する方法 (および一般的にそのようなことを行う方法) は、単一のトランザクションで操作全体を行うメソッドを作成することです。そのメソッドをサービス クラスで作成し (Spring で管理されるコンポーネントはどれでも機能します)、@Transactional同様にアノテーションを付けます。

于 2012-09-24T21:26:52.620 に答える
3

Spring はわかりませんが、JPA の問題は、Usera への参照を持つ a を永続化していることですがUserGroup、JPA は を と考えていUserGroupますtransient

transientJPA エンティティーが存在できるライフサイクル状態の 1 つです。これは、new オペレーターで作成されたばかりで、まだ永続化されていない (永続的な ID がまだない) ことを意味します。

DAO 経由でインスタンスを取得するため、UserGroup何か問題があるようです。あなたのインスタンスは であってはなりませんがtransientdetached. インスタンスの ID をUserGroupDAO から受け取った直後に出力できますか? そして、おそらくfindById実装も表示しますか?

リレーションでカスケードを永続化しないgroupため、エンティティが実際に分離されている場合、これは通常機能するはずです。新しいエンティティがなければ、JPA は FK を正しく設定する方法がありUserGroupません。ここではインスタンスの Id が必要ですが、(一見) 存在しないからです。

マージは、切り離されたエンティティを「上書き」してはなりません。ここで取得している例外は何ですか?

私は、すべてを 1 つのトランザクションにまとめなければならないことについて、ここで他の人が与えた回答に部分的にしか同意しません。UserGroupはい、インスタンスは引き続き「接続」されるため、これは確かに便利かもしれませんが、必要ではありません。JPA は、他の新しいエンティティーまたは別のトランザクションで取得された既存の (切り離された) エンティティーへの参照を使用して、新しいエンティティーを完全に永続化できます。たとえば、JPA カスケードの永続化と分離されたエンティティへの参照は、PersistentObjectException をスローします。なんで?

于 2012-09-24T22:36:13.727 に答える
1

方法はわかりませんが、これを解決することができました。ユーザーを割り当てようとしていたユーザー グループには、データベースに NULL バージョン フィールド(@Version で注釈が付けられたフィールド) がありました。このテーブルを使用していた GWT RequestFactory をテストしていたときに、これが問題であることがわかりました。フィールドを 1 に設定すると、すべてが機能し始めました (トランザクション処理の変更は必要ありませんでした)。

NULL バージョン フィールドが本当に問題の原因である場合、これは私がこれまでに受け取った中で最も誤解を招く例外メッセージの 1 つです。

于 2012-09-25T20:32:49.263 に答える