1

appengineのjpa@versionで問題が発生しているため(Eclipse、sdk最新バージョン、DataNucleusのv2-AKA 3.1.1でテスト)、問題を簡単な形式で再現するためのテストサーブレットを作成しました。結果は元のコードよりもさらに奇妙に動作し、バグのように見えますが、私が何か馬鹿げたことをしていないことを確認するために、いくつかの目玉が必要です。

セットアップは非常に簡単です。サブ@エンティティのリストのOneToManyマッピングを含むシングルトンメイン@Entityクラスです(ただし、説明したテストでは1つしか作成しません)。ここ:

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

    protected static Key singletonKey =
         KeyFactory.createKey(MainEntity.class.getSimpleName(), 1);

    // Primary Key
    @Id
    protected Key id = singletonKey;

    // Use optimistic locking
    @Version
    protected long version;

    // Ref to sub entity, LAZY to be explicit
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval=true)
    protected List<SubEntity> subs = new ArrayList<SubEntity>();
}


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

    // Primary Key
    @Id
    protected Key id;

    // Use optimistic locking
    @Version
    protected long version;

    // Get variable part of key
    public String getKey() { return id.getName(); }

    // Set variable part of key
    public void setKey(String key) {
            id = KeyFactory.createKey(MainEntity.singletonKey,
                                      SubEntity.class.getSimpleName(), key);
    }

    // Pretend contents
    protected int contents;

    // modifier for contents
    public void incContent(int step) { contents += step; }
}

したがって、私が行うことは、MainEntityのインスタンスとSubEntityのインスタンスを作成し、SubEntityをMainEntity.subsにadd()して、すべてをコミットすることです。次に、新しいEntityManagerを使用するたびに、MainEntityの2つのデタッチされたインスタンス(サブのコンテンツがロードされている)をフェッチし、両方のコンテンツフィールドを変更してから、別々にmerge()して戻します。

私が期待しているのは、最初のマージがOKを通過し、2番目が例外をスローすることです。

実際に発生するのは、両方のマージがOKを通過することです(したがって、バッキングストアにはコンテンツフィールドの2番目のバージョンがあります)が、バージョンフィールドはサブエンティティでは1ですが、メインエンティティでは3です。(!)メインがバージョン番号を更新している理由、メインエンティティが誤ったバージョン番号で更新されているために例外自体をスローしていない理由、サブエンティティがロック例外を引き起こしていない理由がわかりません、より一般的には何が起こっているのか。

それで、私は何かを誤解しましたか、それともバグがあるように見えますか?

コメントありがとうございます。

ジョナサン。

メインコードのビジネスビットは次のとおりです(ごくわずかにクリーンアップされています)。

private static final EntityManagerFactory emf =
    Persistence.createEntityManagerFactory("general_use");

private void doG(PrintWriter out) {
    // First make sure db is empty
    resetDb(out);

    // Create default contents
    initDb(out, 1);

    // Get the main entity, detached
    MainEntity m1 = fetch(out);

    // Get another version
    MainEntity m2 = fetch(out);

    // Modify both versions of the sub entity in the detached copies
    m1.subs.get(0).incContent(2);
    m2.subs.get(0).incContent(3);

    // Merge them back into db
    merge(out, m1);

    // See what's in the db
    fetch(out);

    // Merging the second one should cause an OptimisticLockException
    try {
        merge(out, m2);
        out.format("We should have gotten an exception here.");
    } catch (Throwable th) {
        out.format("We got an exception here: %s, %s", th, th.getCause());
    }

    // Look at db
    fetch(out);

}


// This gets the MainEntity from the db by its key
private MainEntity fetch(PrintWriter out) {
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    MainEntity m = em.find(MainEntity.class, MainEntity.singletonKey);
    m.toString();    // Make sure that nothing is lazily fetched.
    // MainEntity has a toString() defined on it that uses all the fields
    // in itself and the contents of subs, so this loads everything
    em.getTransaction().commit();
    em.close();
    out.format("Fetch completed, detached state: %s", m);
    return m;
}

// This merges a detached MainEntity and its related sub entity into the db
private void merge(PrintWriter out, MainEntity mp) {
    MainEntity m;
    out.format("Merge commenced, detached state: %s", mp);
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    m = em.merge(mp);
    out.format("Merge pre commit, attached state: %s", m);
    // as above, m.toString() in the format in fact loads everything
    em.getTransaction().commit();
    em.close();
    out.format("Merge completed, detached state: %s", m);
}


// This creates the singleton MainEntity and a number of SubEntitys with keys "Init db #n" which it points to
private void initDb(PrintWriter out, int noSubCopies) {
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    MainEntity m = new MainEntity();
    em.persist(m);
    em.getTransaction().commit();
    em.close();

    em = emf.createEntityManager();
    em.getTransaction().begin();
    m = em.find(MainEntity.class, m.id);
    for (int i = 0; i < noSubCopies; i++) {
        SubEntity s = new SubEntity();
        s.setKey("Init db #" + i);
        em.persist(s);
        m.subs.add(s);
    }
    m.toString();
    em.getTransaction().commit();
    em.close();
    out.format("InitDb completed, detached state: %s", m);
}
4

1 に答える 1

0

「サブ」フィールドの読み込みのバグである可能性が高いと思います。「メイン」の検索を行った後(コミット/クローズの前)、メインとサブにバージョンが設定されていることを確認します。フィールド値ではなく、オブジェクトのバージョンが表示されるため、JDOHelper.getVersion(obj)も使用できます。GAE プラグインには、すべての状況で確実にバージョンを設定するためのコードがありません。

JDOHelper.getVersion(sub)またはJDOHelper.getVersion(main)が txn 内にある場合 (切り離す前) に null を返す場合は、http://code.google.com/p/datanucleus-appengine/issues/list でバグとして報告してください

于 2012-11-28T16:02:30.240 に答える