java - 我们如何更新深度克隆的实体?
问题描述
描述
我在一个小型 Java 游戏服务器上工作......为了在另一个线程中更新和保存游戏,我被迫深度克隆我的一些实体。否则会发生内部休眠异常:更新我的实体时出现“ConcurrentModificationException”
所以我的流程目前看起来像这样:
- 标记要更新的游戏实体
- 将这些实体传递到另一个线程
- 克隆那些实体
- 在克隆实体上调用“session.update”
- 一分钟后重复
它适用于简单的类,但关系存在巨大问题。
问题
当我深度克隆我的“块”实体(见下文)时,它的集合(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;
}
}
问题
为什么hibernate总是插入我的孩子而不检查那些是否已经插入?我能做些什么来防止/解决这个问题?
我尝试过的事情
- 删除“@SelectBeforeUpdate”注释
- 使用“All”或“Merge”将级联添加到“Chunk.InChunk”关系
- 运行“session.merge”而不是“session.update”,这会导致相同的重复条目异常,只是忽略其子项已插入的事实。
他们都没有工作
免责声明
我需要克隆实体,否则会导致内部休眠异常,请参阅上面的链接。
我使用一个名为“DeppClone”的库https://github.com/kostaskougios/cloning在另一个线程中克隆我的实体。
如果需要更多信息,请写评论。这是一个复杂的问题,很难一概而论,但我希望我已经正确描述了它。
解决方案
您应该考虑实现两个主要更改:
- 关注点分离(读写)
- 选择一致性原则
使用#1,您不必再处理 ConcurrentModificationException 了。只有一个组件或服务涉及修改您的实体。
第二个组件或服务仅读取单独上下文中的实体。所有实体都必须是不可变的,以免发生实施事故。
两者都需要一个合同或接口(一般意义上,不是 Java 接口),这会将您带到#2。一旦发生更改,您的只读上下文中的任何加速技巧都必须刷新缓存或重新读取/合并更改。根据CAP 定理,您必须牺牲三者之一,具体取决于您首选的一致性策略。由于没有关于写入次数、实体和读取约束的提示,我无法提出更详细的建议。
如果我现在必须实现它,那么至少有 3 个模块(Java 11/ Jigsaw):
- API 模块仅持有接口以强制例如跨以下两个模块的统一获取方法
- Writer-modules,用你所有的 Hibernate 魔法来编写实体和一种侦听器模式,所以下一个模块可以注册自己
- 具有 Hibernate 魔法的阅读器模块,用于读取实体并将它们提供给其他人(通过 REST、RCP 等),将自身注册到编写器以在更改时进行某种刷新。
读取器模块还可以在启动时读取数据库并使用写入器产生的事件。这些事件将是某种命令,在阅读器模块的内存中发生变化,而不是从数据库中重新读取。因此,您可以通过简单的内存缓存和一些事件建模来完全放弃 Hibernate。通过使用 BlockingQueues 和 ConcurrentHashMap(作为缓存),这仍然可以在单个 JVM 中工作。简单的 JDBC 就足以引导您的模型。
如果通过深度复制和一些 Thread.start 很容易,其他人会这样做。由于有很多关于并发持久性的模型、策略和模式,我建议重构。
推荐阅读
- java - Java:如果找不到正则表达式,请执行某些操作
- mysql - sql根据两列或多列的条件选择多行
- python - 我的脚本在单击链接时遇到错误
- opencv - OpenCV 实时颜色/形状检测
- python - 带有 group by 并匹配条件的 ffil
- jquery - 解决 jQuery UI 在动画时不考虑边距的问题
- sdk - 指纹 U Are U 4500 PowerBuilder
- php - Wordpress Memberships - 获取计划中对特定元键具有非空值的所有活动成员。还检索其他元数据
- c - 为什么递归函数出现在交换函数之前?
- apache-spark - Py4JJavaError:调用 o288.fit 时出错