3

I want to use Java records as embeddable objects with JPA. For example I want to wrap the ID in a record to make it typesafe:

@Entity
public class DemoEntity {

    @EmbeddedId
    private Id id = new Id(UUID.randomUUID());

    @Embeddable
    public static record Id(@Basic UUID value) implements Serializable {}
}

But If I try to persist it with Hibernate 5.4.32 I get the following error:

org.hibernate.InstantiationException: No default constructor for entity:  : com.example.demo.DemoEntity$Id
    at org.hibernate.tuple.PojoInstantiator.instantiate(PojoInstantiator.java:85) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.tuple.component.AbstractComponentTuplizer.instantiate(AbstractComponentTuplizer.java:84) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
...

So it looks like Hibernate would treat the record Id like an entity, although it is an @Embeddable.

The same happens with non-id fields and @Embedded:

@Embedded
private Thing thing = new Thing("example");

@Embeddable
public static record Thing(@Basic String value) implements Serializable {}

Is there a way to use @Embeddable records with JPA/Hibernate?

deamon
  • 89,107
  • 111
  • 320
  • 448

2 Answers2

2

Java records with a single field can be used for custom ID types or any other value object with AttributeConverters.

In the entity class the ID type is used with @Id as usual:

@Entity
public class DemoEntity {

    @Id
    private Id id = new Id(UUID.randomUUID());

    public static record Id(UUID value) implements Serializable {}
}

Note that the record Id doesn't have any annotation.

The converter makes it possible to use records:

@Converter(autoApply = true)
public class DemoEntityIdConverter implements AttributeConverter<DemoEntity.Id, String> {
  
    @Override
    public String convertToDatabaseColumn(DemoEntity.Id id) {
        return id.value().toString();
    }

    @Override
    public DemoEntity.Id convertToEntityAttribute(String s) {
        return new DemoEntity.Id(UUID.fromString(s));
    }
}

Don't forget to set autoApply = true to have this converter applied automatically (without referencing it explicitly on the respective field).

Records with more than one field could be mapped with a Hibernate UserType, but that is a bit cumbersome.

deamon
  • 89,107
  • 111
  • 320
  • 448
  • Yeah it's a shame about the lack of support for multi-field records; they'd be really handy as `@EmbeddedId` for example. Ideally Hibernate would be smart enough to use the synthetic constructor for those, rather than attempt to set the (final) fields one-by-one through reflection, which results in `java.lang.IllegalAccessException: Can not set final ... field`. Maybe in a future release? – sxc731 Feb 20 '22 at 09:41
  • 1
    Note that Hibernate 6.2 [introduced support for Java records](https://in.relation.to/2023/03/30/orm-62-final/). – twobiers Jun 20 '23 at 07:20
0

Entity or embeddable, in any case the record class wouldn't be suitable here because entities and their fields, including embeddable ones, are modifiable. The only exception would be for Id fields, but that doesn't seem like an important enough case to make this functionality for.

One of the Hibernate developers explains this here

Sebastiaan van den Broek
  • 5,818
  • 7
  • 40
  • 73
  • The article explains only why records wouldn't work for entities. My thought was that an embeddable `record` would be like any other immutable type (String, int, ...) and could therefore be used for fields mapped by Hibernate. However the reality says the opposite ... – deamon Jul 22 '21 at 21:45