@Entity
public class Entity {
@Id
private Long idnr;
@Id
private LocalDateTime timestamp;
// Getters/setters omitted
}
I want to use a custom JPA converter for the LocalDateTime field (due to a bug in handling timezone by the default converter). This works as expected when @Id is removed from the LocalDateTime field, but the converter is ignored when the Id is present.
I tried both an auto-applied and an explicit (@Convert(converter=Converter.class) converter.
How can I get the converter working on a field with @Id
Try to use an Embeddable for the composite id. Defining multiple @Id is not a very good idea since the entity itself is both the entity and the identifier, and you need to implement Serializable amd provide equals and hashCode. These requirements are not needed for EmbeddedId.
According to the JPA 2.2 spec (10.6) this is indeed intended behaviour:
“Note that Id attributes, version attributes, relationship attributes, and attributes explicitly annotated as
Enumerated or Temporal (or designated as such via XML) will not be converted.”
In 3.8 the following can be found:
“The conversion of all basic types is supported except for the following: Id attributes (including the
attributes of embedded ids and derived identities), version attributes, relationship attributes, and
attributes explicitly annotated as Enumerated or Temporal or designated as such in the XML
descriptor. Auto-apply converters will not be applied to such attributes, and applications that apply converters to such attributes through use of the Convert annotation will not be portable.”
So the converter not working for an @Id annotated field is desired behaviour. But the fact that the conversion seems to work on a field in an embedded id might not be.
However, I have found another solution for my problem, i.e. using a Hibernate user type. A user type can be applied to an @Id annotated field.
Had the same question this week and found a solution that works, which is based on this question on Stackoverflow. It’s essentially based on using an @IdClass; i.e., having a composite primary key (which also applies to the example posted above). The trick is to place the @Convert annotation (and also the @Column annotation) on the field in the id class, not the field annotated with @Id:
// minimum example
@Entity
@IdClass(SomeEntity.PrimaryKey.class)
public class SomeEntity
{
@Id
private MyEnum type;
@Id
private String entityId;
// ...
public static final class PrimaryKey
{
@Column(name = "\"type\"", nullable = false, updatable = false)
// According to JPA, @Convert should not be used on @Id field (SomeEntity.type), see Section 11.1.10.
// Hibernate ignores the annotation if done so. It works, however, when placed on the corresponding
// field of an @IdClass, see also https://stackoverflow.com/q/44069361/3955765
@Convert(converter = MyEnumConverter.class)
private MyEnum type;
@Column(name = "entity_id", nullable = false, updatable = false)
private String entityId;
// ...
}
}
MyEnum is a custom enumeration. MyEnumConverter is an AttributeConverter that converts to/from a character (i.e., values of MyEnum are encoded by a character in the database). This also works when Hibernate creates the schema; that is, the type of the database field named type is correctly set to a character (not an integer as is the default for enumeration types).
Tested with Hibernate 5.3.13.
I’m just not sure whether this is defined behavior or whether it’s working by chance. Maybe someone from the Hibernate team can comment on this?