CompositeUserType with a variable number of associated columns

I’m currently migrating a Hibernate 5.6 project to 6.6. One of the problems I’m dealing with, is a CompositeUserType implementation, that does not translate easily to 6.6.

The old implementation allowed for something like this:

@Columns(columns = {
  @Column(name = "caring_nursing", nullable = false),
  @Column(name = "caring_wg", nullable = false),
  @Column(name = "caring_home", nullable = false),
  @Column(name = "caring_inpatient", nullable = false)
})
@Type(type = "multi-boolean-column-enumset", parameters =
  @Parameter(name = "columnEnums", value = "NURSING,WG,HOME,INPATIENT"))
protected Set<Caring> caring = EnumSet.noneOf(Caring.class);

Basically I have 4 boolean typed columns which are mapped to an EnumSet. The dynamic parameter columnEnums tells me for each column to which enum value it belongs.

Hibernate 6.6 doesn’t support this construct anymore. An embeddable class has been introduced and that’s an acceptable solution for most use cases but in my case it is not. My CompositeUserType used to be able to handle a flexible number of columns. Unfortunately, Hibernate now requires me to present a class with attributes, which can vary in name and number.

In order to solve this problem, I have a number of questions:

  1. Is it possible to create an embeddable class with more attributes than are effectively assigned in the entity? So if the embeddable class contains booleans b1, b2, b3, b4, b5, b6 and for the example above I only provide @AttributeOverride for 4 of those. Is Hibernate okay with that or will it complain that I have not assigned the other 2?
  2. Are all-caps variables allowed in the embeddable class?
  3. Can I rely on the fact that DynamicParameterizedType#setParameterValues(Properties parameters) is invoked before CompositeUserType#embeddable() ?
  4. Is it possible to annotate the attributes in the embeddable class with something like @Convert(converter = YesNoConverter) ?
  5. or is there already a solution to my problem?

You could create an embeddable class for every such columnEnums combination that extend the EnumSet class to implement this. Then you don’t need to worry about CompositeUserType anymore.

I was afraid that would come as an answer :wink: but I really need the dynamic approach on this. There are way too many enumerations like this in my project and too many combinations. That’s not a viable solution.

My idea is to use byte-buddy to generate an embedded class. This would essentially lead to your suggested solution. For the example in my original post, it would be something like this:

public some.pkg.CaringEmbedded {
  @Converter(converter = YesNoConverter.class)
  protected Boolean NURSING;
  @Converter(converter = YesNoConverter.class)
  protected Boolean WG;
  @Converter(converter = YesNoConverter.class)
  protected Boolean HOME;
  @Converter(converter = YesNoConverter.class)
  protected Boolean INPATIENT;
}

I would expect to generate this embedded class in DynamicParameterizedType#setParameterValues(Properties parameters) but for that I need to have a yes/no to the questions in my previous post. Especially question 3 is essential in this regard.

As for question 4. I know I can set a default boolean converter but this boolean converter is actually an exception to the rule in the project

Is it possible to create an embeddable class with more attributes than are effectively assigned in the entity?

No

Are all-caps variables allowed in the embeddable class?

Yes

Can I rely on the fact that DynamicParameterizedType#setParameterValues(Properties parameters) is invoked before CompositeUserType#embeddable() ?

I think you can as I don’t see a reason why this would/should change, but since it’s not specified anywhere, we might change the order in a future release.

Is it possible to annotate the attributes in the embeddable class with something like @Convert(converter = YesNoConverter) ?

Of course. You can even annotate this on the use-site of the embedded by e.g. @Convert(attributeName = "attr1")

or is there already a solution to my problem?

I would go with the solution I already presented you since it’s the most portable and supported way.