gen*_*ray 5 java spring multithreading hibernate jpa
我正在开发一个小型 Java 游戏服务器……为了在另一个线程中更新和保存游戏,我被迫深度克隆了我的一些实体。否则会发生内部休眠异常:更新我的实体时出现“ConcurrentModificationException”
所以我的流程目前看起来像这样:
它适用于简单的类,但关系存在巨大问题。
当我深度克隆我的“块”实体(见下文)时,它的集合( 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;
}
}
Run Code Online (Sandbox Code Playgroud)
为什么 hibernate 总是插入我的孩子而不检查那些是否已经插入?我能做些什么来防止/解决这个问题?
他们都没有工作
我需要克隆实体,否则会导致内部休眠异常,请参阅上面的链接。
我使用一个名为“DeppClone”的库https://github.com/kostaskougios/cloning在另一个线程中克隆我的实体。
如果需要更多信息,请写评论。这是一个复杂的问题,很难概括,但我希望我已经正确地描述了它。
小智 1
您应该考虑实现两个重大改变:
\n有了#1,你就不必再处理ConcurrentModificationException了。只有一个组件或服务涉及修改您的实体。
\n第二个组件或服务仅读取单独上下文中的实体。所有实体都必须是不可变的,以避免实施事故。
\n两者都需要契约或接口(一般意义上,不是 Java 接口),这会将您带到#2。一旦发生更改,只读上下文中的任何加速技巧都必须刷新缓存或重新读取/合并更改。根据CAP 定理,您必须牺牲三者之一,具体取决于您首选的一致性策略。由于没有关于写入次数、实体和读取限制的提示,我无法提出任何更详细的建议。
\n如果我现在必须实现它,那么至少有 3 个模块(Java 11/Jigsaw):
\n读取器模块还可以在启动时读取数据库并使用写入器产生的事件。这些事件将是某种命令,在读取器模块的内存中进行更改,而不是从数据库中重新读取。因此,您可以通过简单的内存缓存和一些事件建模来完全放弃 Hibernate。通过使用 BlockingQueues 和 ConcurrentHashMap(作为缓存),这仍然可以在单个 JVM 中工作。简单的 JDBC 就足以引导您的模型。
\n如果通过深度复制和一些 Thread.start 就那么容易的话,其他人也会这样做。由于有很多关于并发持久性的模型、策略和模式,我建议重构。
\n