In JPA2 and Java 10, how do I map the value of an enum to an @Enumerated column?

132 Views Asked by At

I'm using Java 10. If I define an enum like this

public enum MyEnum {
  NAME1("value 1"),
  NAME2("value 2"),
    ...
}

and then I have a JPA-mapped database column that is defined by the enum

  @Enumerated(value = EnumType.STRING)
  private MyEnum status;

What else do I need to do to map the value of the enum (e.g. "value 2") to the column "status" instead of the name, which is getting mapped currently?

1

There are 1 best solutions below

2
zforgo On BEST ANSWER

If JPA2 really means JPA 2.1 then AttributeConverter is the perfect solution.

A class that implements this interface can be used to convert entity attribute state into database column representation and back again. Note that the X and Y types may be the same Java type.

In this case there is an entity

@Entity
@Table(name = "account")
public class Account {

    @Id
    private Long id;

    // Custom converter registered manually
    @Convert(converter = AccountTypeConverter.class)
    private AccountType type;

    private Double balance;

    // Getters and setters omitted for brevity
}

and an enumerated field AccountType

public enum AccountType {

    DEBIT("D"),

    CREDIT("C");
    private final String shortValue;

    private AccountType(String value) {
        this.shortValue = value;
    }

    public String getShortValue() {
        return shortValue;
    }

    private static final Function<String, AccountType> byShortValue = s ->
            Arrays.stream(values())
                    .filter(e -> e.getShortValue().equals(s))
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("Invalid account type: " + s));

    public static AccountType fromShortValue(String shortValue) {
        return Optional.ofNullable(shortValue)
                .map(byShortValue)
                .orElse(null);
    }
}

Finally, the instructed AccountTypeConverter should be like

public class AccountTypeConverter implements AttributeConverter<AccountType, String> {

    @Override
    public String convertToDatabaseColumn(AccountType attribute) {
        return Optional.ofNullable(attribute)
                .map(AccountType::getShortValue)
                .orElse(null);
    }

    @Override
    public AccountType convertToEntityAttribute(String dbData) {
        return AccountType.fromShortValue(dbData);
    }
}

When opening an Account

doInJPA(emFactory, em -> {
    var debit = new Account()
            .setId(1L)
            .setBalance(10_000d)
            .setType(AccountType.DEBIT);
    var credit = new Account()
            .setId(2L)
            .setBalance(30_000d)
            .setType(AccountType.CREDIT);
    em.persist(debit);
    em.persist(credit);
});

the generated SQL statements are like this

INSERT INTO account 
    (balance, type, id) 
VALUES (
    10000,
    'D',
    1
);

INSERT INTO account 
    (balance, type, id) 
VALUES (
    30000,
    'C',
    2
);

The previous example shows how to implement and register an attribute converter to a field of an enity. If you have multiple entities with the same type of enumerated field and all occurrences should be converted, then it is possible to auto-register the converter.

Auto-registered AttributeConverter

@Converter(autoApply = true)
public class AccountTypeConverter implements AttributeConverter<AccountType, String> {

    // implementation is same
}

In this case annotating the entity field is no longer necessary.

@Entity
@Table(name = "account")
public class Account {

    @Id
    private Long id;

    // Previously registered converter applied automatically
    private AccountType type;

    private Double balance;

    // Getters and setters omitted for brevity
}