jakartaee / persistence

https://jakartaee.github.io/persistence/
Other
205 stars 59 forks source link

Support @Convert specification on id attributes #207

Open reda-alaoui opened 5 years ago

reda-alaoui commented 5 years ago

We want to map an id attribute to an immutable type.

class FooId implements Serializable {
    private final int value;

    private FooId(int value) {
      this.value = value;
    }

    public static FooId of(int value) {
      return new FooId(value);
    }

    public int value() {
      return value;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      FooId fooId = (FooId) o;
      return value == fooId.value;
    }

    @Override
    public int hashCode() {
      return Objects.hash(value);
    }
  }

class Converter implements AttributeConverter<FooId, Integer> {

    @Override
    public Integer convertToDatabaseColumn(FooId attribute) {
      return Optional.ofNullable(attribute).map(FooId::value).orElse(null);
    }

    @Override
    public FooId convertToEntityAttribute(Integer dbData) {
      return Optional.ofNullable(dbData).map(FooId::of).orElse(null);
    }
  }

@Entity
class Foo {
   @Id
    @Convert(converter = Converter.class)
    @Column(definition = "integer")
    private FooId id;
}

Hibernate 5 fails to convert the id to the immutable type.

Reading the JPA 2.1 spec, we found this:

The Convert annotation should not be used to specify conversion of the following: Id attributes (including the attributes of embedded ids and derived identities), version attributes, relationship attributes, and attributes explicitly annotated (or designated via XML) as Enumerated or Temporal. Applications that specify such conversions will not be portable

Would it be possible to reconsider this to make convert on id attributes a use case part of the next version of JPA?

andyjefferson commented 5 years ago

There are two separate things here.

  1. Support specification of @Convert on an "id" attribute.
  2. Support use of @Convert when an attribute is generated, and define what is the expected behaviour in that case; if it is generated in the datastore, or if it is generated in Java.

They are distinct, so you should separate this issue out into 2. There should be a separate issue in this tracker for supporting @GeneratedValue on non-@Id attributes.

reda-alaoui commented 5 years ago

Hi @andyjefferson ,

In fact, Hibernate 5 fails in any case. For the non generated id case, Hibernate auto ddl was creating a column of type bytes to serialize (java native serialization) the immutable object. I edited my first message to reflect this.

Anyway, I will create a separate issue for the generated value case.

Rabz2210 commented 4 years ago

@andyjefferson @reda-alaoui Do we have any support for this now?. I see the issue is quite old. I have been trying @convert of @Id attribute whose values is @Generate by the datastore. it fails to work.

reda-alaoui commented 4 years ago

@andyjefferson isn’t the current issue opened on the jakarta jpa specification project?

gavinking commented 1 year ago

Similar to #208.

BearKid commented 1 year ago

In order to write clean and readable code, I try to use wrapped Id object and avoid primitive String/Integer Id, but as reda-alaoui said, the code below doesn't work.

@Entity
class ExampleEntity {
    @Id
    @Column(name= "fd_id")
    @Convert(converter = ExampleIdConverter.class)
    private ExampleId id;

    @Value(staticConstructor = "of")
    public static class ExampleId implements Serializable {
        String idAsString;
    }
}

I'm currently using @EmbeddedId to achieve wrapped Id.

@Entity
@Table(name = "example_table")
public class ExampleEntity {
    @EmbeddedId
    @AttributeOverride(name = "idAsString", column = @Column(name = "fd_id"))
    private ExampleId id;

    @Embeddable
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @Getter
    @EqualsAndHashCode
    @AllArgsConstructor(staticName = "of")
    public static class ExampleId implements Serializable {
        private String idAsString;
    }
}

But I think this is less easy to use than the @ Convert & AttributeConverter proposal. In order for the @EmbeddedId solution to work, those are required:

  1. @EmbeddedId instead of @ Id
  2. @AttributeOverride. I need it because the ExampleId may be used in other Entities where the attribute names mapped to the table columns are not always the same. However filling in name = "idAsString" is a maintenance burden, which requires me to keep an eye on whether the value matches the attribute name in the ExampleId. It leaves an opportunity to make a mistake.
  3. Adding a public/protected no-arg constructor to the wrapped Id class, which I do not need, but JPA requires it.

Hi @reda-alaoui , as of now, do you know of any viable ways for implementing single-attribute wrapper ID? Looking forward to hearing about others' experiences with this.

reda-alaoui commented 1 year ago

@BearKid , I have been using primitive id since. I don't know how things evolved in between.

gavinking commented 1 year ago

As far as I know, no product implements this (since it's actually pretty hard to implement) so it's not something we plan to add to the spec at this time.

thiagochaves commented 8 months ago

I wanted to point out that in Hibernate 5.6.15.Final it is possible to use @Convert on a @Id attribute. I have been doing that for quite some time, exactly to map immutable wrappers to my ids. Now that I'm upgrading Hibernate to version 6.4 is that I found out that it no longer works and was "forbidden" by the JPA specs.

beikov commented 8 months ago

If you think you found a bug or want to request a feature in Hibernate ORM, please do that through the Hibernate ORM community channels. This is not the proper place for that sort of discussion.

robmv commented 4 months ago

As far as I know, no product implements this (since it's actually pretty hard to implement) so it's not something we plan to add to the spec at this time.

Revisiting my code in order to see if I can remove a Hibernate Type that is a duplicate implementation of another JPA converter, and find that JPA still doesn't allow Convert on Id fields. My use case is for accessing legacy tables that use CHAR(N) columns as PK, the idea is to strip the ending spaces of the Id at read time and avoid needing to strip the id on every usage site.

@gavinking, You are right that no other implementation allows Convert on Id fields, but Hibernate allows Type on Id fields without any problem.

gavinking commented 4 months ago

@gavinking, You are right that no other implementation allows Convert on Id fields, but Hibernate allows Type on Id fields without any problem.

Correct, and the reason is because a Type tells us much more about the semantics of the thing than an AttributeConverter does.