Accessing Envers Revisions in SpringBoot 3/Hibernate 6 using multiple Instances

384 views Asked by At

In SpringBoot 3 (Hibernate 6) there was a change in which sequence generators are now allocating more than a single increment. This also applys to Envers Revision Numbers.

Defaults for implicit sequence generators Implicit sequences, like the hibernate_sequence before, now respect the defaults of the JPA @SequenceGenerator annotation, which means that the sequences have an allocation size of 50. (https://docs.jboss.org/hibernate/orm/6.0/migration-guide/migration-guide.html)

When trying to access a previous revision of an entity the guide for Envers recomends to query the AuditReader with a Revision Number. Chapter 5 https://docs.jboss.org/envers/docs/ ... .setProjection(AuditEntity.revisionNumber().min())

The problem is now that I have multiple instances of a Service running. Each service allocates 50 sequenze numbers in advance. The result of this beeing that sometimes an Entity has a newer Revision entry with a lower revision number. I try to access the newest/oldest version of an Entity and sometimes it is simply the wrong one since the revision numbers are no longer in order with the "age" of an entry.

Is there a suggeste path for accessing specific Revision entries with multiple instances of a Spring Boot application in SpringBoot 3?

I played around using the revision timestamp in the revfinfo table to no longer use the revision number. I am running into problems since the timestamp is not part of an revison entrie and therefore not accessable with AuditEntity.getRevisionDate(). The revision timestamp is only existing in the revinfo table.

I also thought about changing the allocation_size back to 1.

I would be grateful if anyone nows what the intended way is to approch this in Spring Boot 3/Hibernate 6. Using multiple instances of a single service should not be unusuall.

1

There are 1 answers

0
GJohannes On

I found two solutions for this problem. Accessing the AuditTable and selecting it via Timestamp:

@Transactional
public Integer getNewestRevisionOfMyCustomEntity(Long myCustomEntityId) {
  Optional<DefaultRevisionEntity> newestRevision = reader.createQuery()
          .forRevisionsOfEntity(MyCustomEntity.class, false, false)
          .add(AuditEntity.id().eq(myCustomEntityId))
          .getResultList()
          .stream().map(element -> {
              if(element instanceof Object[]) {
                  Object defaultRevisionEntity = ((Object[]) element)[1];
                  if (defaultRevisionEntity instanceof DefaultRevisionEntity) {
                      return defaultRevisionEntity;
                  }
              }
              throw new IllegalStateException("Could not access DefaultRevisionEntity in AuditManager");
          }).max(Comparator.comparing(defaultRevisionEntity -> ((DefaultRevisionEntity) defaultRevisionEntity).getTimestamp()));
  return newestRevision.orElseThrow(IllegalStateException::new).getId();
}

or simply configure springBoot in envers legacy mode:

spring.jpa.properties.hibernate.id.db_structure_naming_strategy: legacy 

and the corresponding sql "resets"

ALTER SEQUENCE revinfo_seq RENAME TO hibernate_sequence;
ALTER SEQUENCE hibernate_sequence INCREMENT BY 1;