javahibernatespring-data-jpa

How to customize JPA Sequence generator on Entity with @MappedSuperclass ancestor


How can I customise the sequence generator for each descendant when the generation strategy is defined on the ancestor?

I get an InvalidDataAccessResourceUsageException when JPA tries to get the next ID before inserting a new record.

If I create a sequence in the database with the name of the table and the default increment of 50, I do not get any errors. This is not an acceptable solution to the DBA team b/c it does not meet the naming standards.

The default generator working leads me to believe that the @SequenceGenerator on the child is not being recognized. I tried various combinations of @Access(AccessType.FIELD) and @Access(AccessType.PROPERTY) on both parent and child but none recognized my custom SequenceGenerator attributes.

The DBA team wants me to "just" copy/paste the common attributes and code to every descendant. Certainly not DRY!

Update

I've tried various combinations of the generator argument for the abstract base class and name argument for the descendant class; only ancestor, only descendant, matching string, non-matching string. Still errors out.

This answer for another question says it can be done but several comments say it can't be done.

Update 2

I created a simple project that demonstrates the error / failure of naming sequence generators in the descendant class. The repo is on GitHub at this link.

Any suggestions on how to get this to work?

Using Spring Boot 3.4.4

Console log:

org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet [ERROR: relation "descendant_one_id_seq" does not exist Position: 16] [select nextval('descendant_one_id_seq')]; SQL [select nextval('descendant_one_id_seq')]

at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:277) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:241) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:560) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:343) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165)

Ancestor

@MappedSuperclass
public abstract class AbstractDatabaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "entity_generator")
    protected Long id;
    // other common attributes

    // common methods to all descendants
}

Descendant

@Entity
@Table(name = "descendant_one")
public class DescendantOne extends AbstractDatabaseEntity  {
    @SequenceGenerator(name = "entity_generator", sequenceName = "descendant_one_id_seq", allocationSize = 1)
    public Long getId() {
        return id;
    }
}

Descendant

@Entity
@Table(name = "descendant_two")
public class DescendantTwo extends AbstractDatabaseEntity  {
    @SequenceGenerator(name = "entity_generator", sequenceName = "descendant_two_id_seq", allocationSize = 1)
    public Long getId() {
        return id;
    }
}

Test class demonstrating usage

@ServiceTest
class SequenceServiceTest {
    @Autowired
    DescendantOneRepository repository;
    @Autowired
    SequenceService cut;

    @BeforeEach
    void setUp() {
        assertThat(cut).isNotNull();
        assertThat(repository).isNotNull();
    }

    @Test
//    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    void createDescendantOne() {
        var result = cut.createDescendantOne();
        assertThat(result).isNotNull();
    }
}


Solution

  • The solution is to move the @SequenceGenerator to the class. The credit for the solution goes to the great IntelliJ plugin JPA Buddy.

    @SequenceGenerator(name = "entity_generator", sequenceName = "descendant_one_id_seq", allocationSize = 1)
    public class DescendantOne extends AbstractDatabaseEntity {
        @Version
        private Integer version;
        private String name;
    }
    

    There is a warning in the console as below. Per hibernate devs in this GitHub issue from Mar, 2021, the warnings can be ignored since I am using @MappedSuperclass.

    HHH000069: Duplicate generator name entity_generator