1

我一直在 appengine 上遇到 jpa @version 问题(在 eclipse 中测试,sdk 最新版本,DataNucleus v2 - AKA 3.1.1),所以我编写了一个测试 servlet 来尝试以简单的形式重现该问题。结果的行为比原始代码更加怪异,在我看来它好像是一个错误,但我希望有几个眼球可以检查一下我没有做一些愚蠢的事情。

设置非常简单:一个单例主 @Entity 类,其中包含一个子 @Entity 列表的 OneToMany 映射(尽管在所描述的测试中我只创建了一个)。这里:

@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 并提交所有内容。然后,每次使用新的 EntityManager 时,我都会获取MainEntity 的两个分离实例(加载了 subs 的内容),修改它们的内容字段,然后分别将它们合并()回来。

我期望看到的是第一次合并通过 OK,第二次合并抛出异常。

实际发生的情况是两个合并都通过 OK(因此后备存储具有内容字段的第二个版本),但版本字段在 SubEntity 上仍然为 1,但在 MainEntity 上为 3。(!)我不明白为什么 main 更新了它的版本号,为什么它没有抛出异常,因为 MainEntity 正在使用不正确的版本号进行更新,为什么子实体没有导致锁定异常,以及更普遍的情况。

那么,我是否误解了某些内容或似乎存在错误?

非常感谢您的任何评论。

乔纳森。

这是主要代码的业务部分(非常轻微地清理过):

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

我会说这可能是加载“subs”字段中的一个错误。在找到“main”之后(在提交/关闭之前),检查它是否在 main 和 subs 上设置了版本。您也可以使用JDOHelper.getVersion(obj)因为这将显示对象的版本,而不是字段值。我没有看到 GAE 插件中的任何代码在所有情况下都可以肯定地设置版本。

如果JDOHelper.getVersion(sub)or在 txn 内(分离前)返回 null,请在http://code.google.com/p/datanucleus-appengine/issues/listJDOHelper.getVersion(main)将其报告为错误

于 2012-11-28T16:02:30.240 回答