我正在尝试实现基于Range对象的树结构构建的玩家排名,并在树的最低层附加了Rating对象。我知道有一个Java实现(从Python移植),但它使用低级数据存储API,我想使用JPA。以下是描述结构的代码段:
@Entity
public class Range
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
@ManyToOne(optional = false)
private Range parent;
// leaf node?
private boolean leaf;
// total number of rating objects in the node's subtree
private long count;
// child nodes
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Range> children;
// range [max, min]
private long max;
private long min;
@Version
private int version;
}
@Entity
public class Rating
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
// parent range
private Key range;
@ManyToOne(optional = false)
private Player player;
// rating value
private long value;
@Version
private int version;
}
@Entity
public class Player
{
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Key key;
// list of associated ratings for different game configurations (one per config)
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<Rating> ratings = new LinkedList<Rating>();
@Version
private int version;
}
然后有一个表示树本身的类(Tree),它保存根Range对象(此处未显示)。 正如您所看到的,Range类的对象实际上是树核,它们之间具有一对多的关系。玩家与评级对象有一对多的关系(假设游戏类型很少)。 这是一个直接的问题,可以解决:在Range和Rating对象之间建立一对多关系会很好,但是GAE不支持多个父级。因此,Ranges通过在评级对象中保留父范围键,与评级具有手动管理关系。出于同样的原因,Tree无法与Range建立一对一的关系。它也可以使用存储在树对象中的根节点键手动处理。 将第一个评级插入树时会发生真正的问题,直到现在才存在:
EntityTransaction tx = em.getTransaction();
// start transaction
tx.begin();
// create the tree
Tree tree = new Tree();
em.persist(tree);
// and its root range
Range root = new Range();
em.persist(root);
// we need range key to attach it to the tree
em.flush();
// attach root range to the tree
tree.setRoot(root);
// save the tree
em.persist(tree);
em.flush();
// ...
// we have parent range (tree root), player and score
Rating rating = new Rating();
// one-to-many relationship: Player--<>Rating
player.getRatings().add(rating);
rating.setPlayer(player);
// attach to the parent range
rating.setParent(range);
rating.setValue(score);
// use entity manager to persist the new rating
em.persist(rating);
tx.commit(); <------------ here it fails
以下是缩短的异常转储:
javax.persistence.RollbackException: Transaction failed to commit
at org.datanucleus.jpa.EntityTransactionImpl.commit(EntityTransactionImpl.java:118)
at org.datanucleus.store.appengine.jpa.DatastoreEntityTransactionImpl.commit(DatastoreEntityTransactionImpl.java:58)
...
Caused by: javax.persistence.OptimisticLockException: Some instances failed to flush successfully due to optimistic verification problems.
at org.datanucleus.jpa.NucleusJPAHelper.getJPAExceptionForNucleusException(NucleusJPAHelper.java:271)
at org.datanucleus.jpa.EntityTransactionImpl.commit(EntityTransactionImpl.java:116)
... 39 more
Caused by: org.datanucleus.exceptions.NucleusOptimisticException: Optimistic concurrency exception updating ferp.center.server.entity.Range with pk Range(10). The underlying entity had already been deleted.
at org.datanucleus.store.appengine.DatastorePersistenceHandler.newNucleusOptimisticException(DatastorePersistenceHandler.java:391)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.handleVersioningBeforeWrite(DatastorePersistenceHandler.java:412)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.updateObject(DatastorePersistenceHandler.java:574)
at org.datanucleus.state.JDOStateManagerImpl.flush(JDOStateManagerImpl.java:4576)
at org.datanucleus.ObjectManagerImpl.flushInternal(ObjectManagerImpl.java:2814)
at org.datanucleus.ObjectManagerImpl.flush(ObjectManagerImpl.java:2754)
at org.datanucleus.ObjectManagerImpl.preCommit(ObjectManagerImpl.java:2893)
at org.datanucleus.TransactionImpl.internalPreCommit(TransactionImpl.java:369)
at org.datanucleus.TransactionImpl.commit(TransactionImpl.java:256)
at org.datanucleus.jpa.EntityTransactionImpl.commit(EntityTransactionImpl.java:104)
... 39 more
我做错了什么?