We are seeing unique constraint violation on the primary key that is generated when using EclipseLink's HistoryPolicy on one of our data tables:
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorCustomizer;
import org.eclipse.persistence.history.HistoryPolicy;
public class DatasetProgressHistory implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) {
HistoryPolicy policy = new HistoryPolicy();
policy.addHistoryTableName("DATASET_PROGRESS_HISTORY");
policy.addStartFieldName("STATE_BEGIN");
policy.addEndFieldName("STATE_END");
descriptor.setHistoryPolicy(policy);
}
}
import org.eclipse.persistence.annotations.Customizer;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Customizer(DatasetProgressHistory.class)
@Table(name = "DATASET_PROGRESS")
public class DatasetProgress implements Serializable {
@Id
[...]
private long id;
[...]
}
The primary key consists of an ID column (from the source table) and the STATE_BEGIN
column.
If we merge/persist changes in a quick fashion (e.g. in unit tests or through simultaneous web requests) we often run into the primary key violation errors.
What's the best way to prevent these errors? The only solution I can think of is adding a synchronize block on the database methods merge/persist. Is there another, more elegant way to solve this?
I managed to fix the problem by switching to useDatabaseTime()
which provides micro-seconds precision compared to milli-seconds precision from (default) useLocalTime()
.
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorCustomizer;
import org.eclipse.persistence.history.HistoryPolicy;
public class DatasetProgressHistory implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) {
HistoryPolicy policy = new HistoryPolicy();
policy.addHistoryTableName("DATASET_PROGRESS_HISTORY");
policy.addStartFieldName("STATE_BEGIN");
policy.addEndFieldName("STATE_END");
policy.useDatabaseTime(); // higher precision than default useLocalTime (microseconds vs milliseconds), avoids unique key violations
descriptor.setHistoryPolicy(policy);
}
}