我正在尝试实现基于 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 类的对象实际上是树的核心,它们之间具有一对多的关系。Player 与 Rating 对象具有一对多的关系(假设游戏类型很少)。这是一个可以解决的直接问题:Range 和 Rating 对象之间有一对多的关系会很好,但 GAE 不支持多重父级。因此,Ranges 通过在 rating 对象中保存 parent range 键与 Ratings 具有手动管理的关系。出于同样的原因,Tree 无法管理与 Range 的一对一关系。它还使用存储在树对象中的根节点键手动处理。真正的问题发生在将第一个 Rating 插入树时,
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
我究竟做错了什么?