Describtion
Im working on a little java game server... in order to update and save the game in another thread, im forced to deep clone some of my entities. Otherwhise a internal hibernate exception occurs : "ConcurrentModificationException" when updating my entities
So my flow currently looks like this :
- Mark game entities to update
- Pass those entities into another thread
- Clone those entities
- Call "session.update" on the cloned entities
- Repeat in one minute
It works fine with simple classes, but theres a huge problem with relations.
The problem
When i deep clone my "chunk" entity ( see below ), its collection ( inChunk ) also gets deep cloned. I use that deep cloned entity and pass it to "session.update".
During the update, the chunk ALWAYS inserts its collection childs. It never updates them. Because i repeat this update process every minute ( see above ), it results in a "Duplicate Entry" exception at the second update cycle.
// 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;
}
}
Question
Why the hell does hibernate always inserts my childs without any checking if those had already been inserted ? And what can i do to prevent/fix this ?
Things i have tried
- Removing "@SelectBeforeUpdate" annotations
- Adding cascading to the "Chunk.InChunk" relation with "All" or "Merge"
- Run a "session.merge" instead of "session.update" which results in the same duplicate entry exception, simply ignoring the fact that its children are already inserted.
None of them worked
Disclaimer
I need to clone the entities, otherwhise it results in an internal hibernate exception, see the link above.
Im using a library called "DeppClone" https://github.com/kostaskougios/cloning for cloning my entities in an other thread.
If more informations are required, please write a comment. Its a complex issue and hard to generalize, but i hope that i have described it properly.