Custom Scalar Primary Key Type

The project I’m currently on would like to use custom scalar primary key types per entity class, basically a wrapper around a long. The reason for this is to have more fidelity in the domain model so you can pass around ids as types that are associated with the respective entity class, rather than just long. Something similar to this.

@Entity
public class SampleEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  private IdValue id;

  public static class IdValue extends AbstractIdValue {

    IdValue(Long value) {
      super(value);
    }

  }

}

In the past we have used @EmbeddedId but this was heavy on annotations and broke with Hibernate 7. Currently we’re using @IdClass. The challenge here is to get it working with sequence based id generation. We are facing two issues, related to the IntegralDataTypeHolder. It only works on Long, Integer and Short and the value is then passed from the generator to the persister.

Our current solution is to subclass SequenceStyleGenerator and override #buildSequenceStructure to use a long type and override generate to do the wrapping there. This is not ideal as we depend on implementation details of SequenceStyleGenerator .

We would need a conversion between the sequence values and the attribute values. A custom IntegralDataTypeHolder would be a way to achieve this. Alternatively the sequence generator would not have to work on the field type but the “logical type” and either in the save event listener or the persister a conversion would have to happen, i.e. through JavaType#wrap.

Things that we tried but did not get working:

  • AttributeConverter, not applied to @Id. We would have to have one per entity class, that that can be done using an annotation processor.
  • custom AbstractClassJavaType, this has the same issue with IdentifierGenerator. Also JavaType#wrap(X, WrapperOptions) doesn’t give us the concrete type of the field, so again we would have to have one per entity class.

The custom AbstractClassJavaType is working with the IDENTITY generator but to my understanding we would the ability to do batch inserts with this generator.

Let’s start with this topic. What broke exactly? Please show what you tried or what worked before and broke with ORM 7. Ideally, you would provide us with a reproducer based on our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.

We added support for generated values in embedded ids with ORM 6.4, so this should work just fine.

FWIW, IntegralDataTypeHolder was removed in ORM 8, but regardless, the problem you’re facing isn’t that IntegralDataTypeHolder only supports these integral types, but rather that you seem to be trying to generate values other than integral data types through that contract. That’s not the intention of that contract. The IdentifierGenerator must take care of the wrapping of the integral value into the model component.

That is by design. If you want, you can implement EnhancedUserType for every id wrapper and apply that.

You can do that, but then you need a custom SequenceGenerator, but it seems you don’t want to implement a type per entity anyway.

So, ultimately, the most convenient solution to your problem is probably to stick to embeddable ids, because these are handled via CompositeNestedGeneratedValueGenerator, delegating to nested generators for component elements, which is exactly what you seem to be looking for.

Alright, I’ll be working on this.

Alrigh, we’ll try this and report back.

@beikov you are correct, the following is working and doing more or less what we want

@Entity
public class TestEntity {

  @EmbeddedId
  private PK id;

  //...

  @Embeddable
  public static class PK implements Serializable {

    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    public PK() {
      super();
    }

    public Long getId() {
      return id;
    }
  }

}

I managed to create a reproducer, we used to put @GeneratedValue on the getter and combine it with @Access(PROPERTY). Note we had to instantiate the PK class directly otherwise we would get an NPE.

See e74f697e7f1ba5768ef3379a6bf24105661432a4.

The code works with 6.6.53.Final but fails with 7.4.2.Final. Is this considered a bug?

jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [NULL not allowed for column "ID"; SQL statement:
insert into EntityWithEmbeddedPK (id) values (?) [23502-240]] [insert into EntityWithEmbeddedPK (id) values (?)]]
	at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:73)
	....
	... 1 more
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement:
insert into EntityWithEmbeddedPK (id) values (?) [23502-240]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:520)
	a....
	... 23 more

Yes, that is probably a bug. Please create a Jira ticket and attach a reproducer.

Here’s the bug HHH-20634, I have a lead on the underlying issue, I’ll have a got at a fix.