我在一个小型Java游戏服务器上工作...为了在另一个线程中更新和保存游戏,我被迫深度克隆了我的某些实体。否则会发生内部休眠异常:"ConcurrentModificationException" when updating my entities
所以我的流程目前看起来像这样:
在简单的类上工作正常,但是关系方面存在很大的问题。
当我深度克隆我的“块”实体时(请参见下文),其集合(inChunk)也将被深克隆。 我使用该深度克隆的实体,并将其传递给“ session.update”。
在更新期间,块始终插入其集合子级。它永远不会更新它们。 因为我每分钟重复一次此更新过程(请参见上文),所以在第二个更新周期中会导致“重复输入”异常。
// Run the database operation for updating the entities async in a new thread, return updated entities once done
return CompletableFuture.runAsync(() -> {
var session = database.openSession();
session.beginTransaction();
try {
// Save entities
for (var entity: entities)
session.update(entity);
session.flush();
session.clear();
session.getTransaction().commit();
} catch (Exception e){
var messageComposer = new ExceptionMessageComposer(e);
GameExtension.getInstance().trace("Update : "+messageComposer.toString());
session.getTransaction().rollback();
}
session.close();
}).thenApply(v -> entities);
@Entity
@Table(name = "chunk", uniqueConstraints = {@UniqueConstraint(columnNames={"x", "y"})}, indexes = {@Index(columnList = "x,y")})
@Access(value = AccessType.FIELD)
@SelectBeforeUpdate(false)
public class Chunk extends HibernateComponent{
public int x;
public int y;
public Date createdOn;
@OneToMany(fetch = FetchType.EAGER)
@JoinTable(name = "chunk_identity", joinColumns = @JoinColumn(name = "identity_id"), inverseJoinColumns = @JoinColumn(name = "id"), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@Fetch(FetchMode.JOIN)
@BatchSize(size = 50)
public Set<Identity> inChunk = new LinkedHashSet<>();
@Transient
public Set<ChunkLoader> loadedBy = new LinkedHashSet<>();
public Chunk() {}
public Chunk(int x, int y, Date createdOn) {
this.x = x;
this.y = y;
this.createdOn = createdOn;
}
}
/**
* Represents a ID of a {@link com.artemis.Entity} which is unique for each entity and mostly the database id
*/
@Entity
@Table(name = "identity")
@Access(AccessType.FIELD)
@SQLInsert(sql = "insert into identity(tag, typeID, id) values(?,?,?) ON DUPLICATE KEY UPDATE id = VALUES(id), tag = values(tag), typeID = values(typeID)")
@SelectBeforeUpdate(value = false)
public class Identity extends Component {
@Id public long id;
public String tag;
public String typeID;
public Identity() {}
public Identity(long id, String tag, String typeID) {
this.id = id;
this.tag = tag;
this.typeID = typeID;
}
}
为什么冬眠总是在不检查孩子的情况下总是插入我的孩子?我该怎么做才能防止/解决此问题?
他们都没有工作
我需要克隆实体,否则会导致内部休眠异常,请参见上面的链接。
我正在使用名为“ DeppClone” https://github.com/kostaskougios/cloning的库在另一个线程中克隆我的实体。
如果需要更多信息,请写评论。这是一个复杂的问题,很难一概而论,但是我希望我已经正确地描述了它。
答案 0 :(得分:1)
您应该考虑实现两个主要更改:
使用#1,您不必再处理ConcurrentModificationException。修改实体仅涉及一个组件或服务。
第二个组件或服务仅在单独的上下文中读取实体。所有实体都必须保持不变,以免发生实施事故。
两者都需要使您进入#2的合同或接口(一般意义上,不是Java接口)。一旦发生更改,您的只读上下文中的任何提速技巧都必须刷新缓存或重新读取/合并更改。根据{{3}},您必须牺牲三者之一,具体取决于您的首选一致性策略。由于没有关于写入,实体和读取约束的数量的提示,因此我无法提出更详细的信息。
如果我现在必须实现它,至少有3个模块(Java 11 / Jigsaw):
读取器模块还可以在启动时读取数据库,并使用写入器产生的事件。这些事件将是某种命令,它们会在读取器模块的内存中发生更改,而不是从数据库中重新读取。因此,您可以通过简单的内存缓存以及一些事件建模完全放弃Hibernate。通过使用BlockingQueues和ConcurrentHashMap(作为缓存),这仍然可以在单个JVM中工作。简单的JDBC足以引导您的模型。
如果通过深度复制和一些Thread.start那样容易,那么其他人也会这样做。由于存在很多有关并发持久性的模型,策略和模式,因此我建议重构。