与@OneToMany在google appengine上的jpa @version:appengine bug或用法错误?

时间:2012-11-27 23:13:34

标签: google-app-engine jpa datanucleus

我一直遇到关于appengine的jpa @version(在eclipse中测试,sdk最新版本,DataNucleus的第2版 - AKA 3.1.1)的问题,所以我编写了一个测试servlet来尝试重现问题。简单的形式。结果表现得比原始代码更加奇特,它看起来好像它是一个bug,但是我想要一些眼球来检查我没有做过愚蠢的事情。

设置非常简单:一个单独的主@Entity类,它包含一个子@Entitys列表的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);
}

1 个答案:

答案 0 :(得分:0)

我说这可能是加载&#34; subs&#34;领域。在查找&#34; main&#34;之后,检查它是否在主要和潜艇上设置了版本。 (在提交/关闭之前)。您也可以使用 JDOHelper.getVersion(obj),因为这将显示对象的版本,而不是字段值。我没有在GAE插件中看到任何代码,以确保在所有情况下都设置了版本。

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

中的错误