I have this exception while changing parent for entity (@OneToMany relationship).
Entity parent update - org.hibernate.HibernateException: identifier of an instance of {Entity} was altered from 1 to 2
This exception occurs and can be reproduce only for service running in Kubernetes after some time. I mean that it isn't reproduced from the very begging of container life and some number of updates completed successfully.
The method that does the update on the entities looks like this:
@Transactional
@Override
public Optional<EntityT> update(EntityT entity) {
entity.setIsConfirmed(true);
return getRepository().findById(entity.getId())
.map(entityToUpdate -> updateEntity(entity, entityToUpdate));
}
private EntityT updateEntity(EntityT entity, EntityT entityToUpdate) {
modelMapper.map(entity, entityToUpdate);
getParentRepository().ifPresent(parentRepository ->
entity.getParent().ifPresent(parentEntity ->
parentRepository.findById(parentEntity.getId()).ifPresent(entityToUpdate::setParent))
);
entityToUpdate.setVersionTs(getCurrentTime());
return getRepository().save(entityToUpdate);
}
Spring boot version - 2.1.2 Hibernate 5.3.7 also try 5.4.1 - the same result.
Also set spring jpa properties to
spring:
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
generate-ddl: true
hibernate:
ddl-auto: update
properties:
hibernate:
jdbc:
batch_size: 100
flushMode: "ALWAYS"
order_inserts: true
order_updates: true
Also tried different images for container open-jdk8 / oracle-jdk8
Could anybody advice some solution?
Thanks in advice.
First of all, the updateEntity
uses the redundant save-anti-pattern:
private EntityT updateEntity(EntityT entity, EntityT entityToUpdate) {
modelMapper.map(entity, entityToUpdate);
getParentRepository().ifPresent(parentRepository ->
entity.getParent().ifPresent(parentEntity ->
parentRepository.findById(parentEntity.getId()).ifPresent(entityToUpdate::setParent))
);
entityToUpdate.setVersionTs(getCurrentTime());
return getRepository().save(entityToUpdate);
}
Second, this part here is not needed:
getParentRepository().ifPresent(parentRepository ->
entity.getParent().ifPresent(parentEntity ->
parentRepository.findById(parentEntity.getId()).ifPresent(entityToUpdate::setParent))
);
The fact that you use both entity
and entityToUpdate
is a code smell which is probably related to using the lambdas that are also not needed.
So, you can probably reduce all your code to this:
@Transactional
@Override
public Optional<EntityT> update(EntityT entity) {
entity.setIsConfirmed(true);
entity.setVersionTs(getCurrentTime());
return getRepository().save(entity);
}
The main issue was in mapper. Instead of parent replacing it change only id for fetched parent. Then we replace parent but fetched parent remains in cache (with new id) and Hibernate after some time try to flush this changes into DB.