We are trying to save many child in a short amount of time and hibernate keep giving OptimisticLockException. Here a simple exemple of that case:
University
id
name
audit_version
Student
id
name
university_id
audit_version
Where university_id can be null.
The java object look like:
@Entity
@Table(name = "university")
@DynamicUpdate
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class University {
@Id
@SequenceGenerator(name = "university_id_sequence_generator", sequenceName = "university_id_sequence", allocationSize = 1)
@GeneratedValue(strategy = SEQUENCE, generator = "university_id_sequence_generator")
@EqualsAndHashCode.Exclude
private Long id;
@Column(name = "name")
private String name;
@Version
@Column(name = "audit_version")
@EqualsAndHashCode.Exclude
private Long auditVersion;
@OptimisticLock(excluded = true)
@OneToMany(mappedBy = "student")
@ToString.Exclude
private List<Student> student;
}
@Entity
@Table(name = "student")
@DynamicUpdate
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class Student {
@Id
@SequenceGenerator(name = "student_id_sequence_generator", sequenceName = "student_id_sequence", allocationSize = 1)
@GeneratedValue(strategy = SEQUENCE, generator = "student_id_sequence_generator")
@EqualsAndHashCode.Exclude
private Long id;
@Column(name = "name")
private String name;
@Version
@Column(name = "audit_version")
@EqualsAndHashCode.Exclude
private Long auditVersion;
@OptimisticLock(excluded = true)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "university_id")
@ToString.Exclude
private University university;
}
It seem when we assign university and then save Student, if we do more than 4 in a short amount of time we will get the OptimisticLockException. It seem hibernate is creating update version on the University table even though the University didn't change at the db level.
UPDATE: code that save the student
Optional<University> universityInDB = universidyRepository.findById(universtityId);
universityInDB.ifPresent(university -> student.setUniversity(university);
Optional<Student> optionalExistingStudent = studentRepository.findById(student);
if (optionalExistingStudent.isPresent()) {
Student existingStudent = optionalExistingStudent.get();
if (!student.equals(existingStudent)) {
copyContentProperties(student, existingStudent);
studentToReturn = studentRepository.save(existingStudent);
} else {
studentToReturn = existingStudent;
}
} else {
studentToReturn = studentRepository.save(student);
}
private static final String[] IGNORE_PROPERTIES = {"id", "createdOn", "updatedOn", "auditVersion"};
public void copyContentProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, Arrays.asList(IGNORE_PROPERTIES)));
}
We tried the following
@OptimisticLock(excluded = true)
Doesn't work, still give the optimistic lock exception.
@JoinColumn(name = "university_id", updatable=false)
only work on a update since we don't save on the update
@JoinColumn(name = "university_id", insertable=false)
work but don't save the relation and university_id is always null
Change the Cascade behaviour.
The only one value that seem to made sense was Cascade.DETACH
, but give a org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing.
Other solution we though of but are not sure what to pick
After the 409 the client must retry his post. for a object sent via the queue the queue will retry that entry later. We don't want our client to manage this error
It's not clean since when the entry come from the queue we already doing it but might be the best solution so far.
This might be fine if there are not a big number of relation, but we have case that might go in the 100 even in the 1000, which will make the object to big to be sent on a queue or via a Rest call.
Our whole db is currently in optimisticLocking and we managed to prevent these case of optimisticLocking so far, we don't want to change our whole locking strategy just because of this case. Maybe force pessimistic locking for that subset of the model but I haven't look if it can be done.
It does NOT need it unless you need it. Do this:
University universityProxy = universidyRepository.getOne(universityId);
student.setUniversity(universityProxy);
In order to assign a University
you don't have to load a University
entity into the context. Because technically, you just need to save a student record with a proper foreign key (university_id
). So when you have a university_id
, you can create a Hibernate proxy using the repository method getOne()
.
@OneToMany
mapping @OneToMany(mappedBy = "student") // should be (mappedBy = "university")
@ToString.Exclude
private List<Student> student;
add()
or remove()
, or clear()
private List<Student> student; // should be ... = new ArrayList<>();
*overall some places are not clear, like studentRepository.findById(student);
. So if you want to have a correct answer it's better to be clear in your question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With