我一直遇到关于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);
}
答案 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