Search code examples
javajpaenumsjpa-2.0enumerated-types

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


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?


Solution

  • 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
    }