Подтвердить что ты не робот

Можно ли написать общий конвертер enum для JPA?

Я хотел написать конвертер для JPA, который хранит любое перечисление как UPPERCASE. Некоторые перечисления, с которыми мы сталкиваемся, еще не придерживаются соглашения о том, чтобы использовать только буквы с верхним регистром, поэтому до тех пор, пока они не будут рефакторированы, я все еще сохраняю будущее значение.

Что я получил до сих пор:

package student;

public enum StudentState {

    Started,
    Mentoring,
    Repeating,
    STUPID,
    GENIUS;
}

Я хочу, чтобы "Started" был сохранен как "НАЧАЛО" и т.д.

package student;

import jpa.EnumUppercaseConverter;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity
@Table(name = "STUDENTS")
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mId;

    @Column(name = "LAST_NAME", length = 35)
    private String mLastName;

    @Column(name = "FIRST_NAME", nullable = false, length = 35)
    private String mFirstName;

    @Column(name = "BIRTH_DATE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date mBirthDate;

    @Column(name = "STUDENT_STATE")
    @Enumerated(EnumType.STRING)
    @Convert(converter = EnumUppercaseConverter.class)
    private StudentState studentState;

}

конвертер в настоящее время выглядит следующим образом:

package jpa;


import javax.persistence.AttributeConverter;
import java.util.EnumSet;

public class EnumUppercaseConverter<E extends Enum<E>> implements AttributeConverter<E, String> {

    private Class<E> enumClass;

    @Override
    public String convertToDatabaseColumn(E e) {
        return e.name().toUpperCase();
    }

    @Override
    public E convertToEntityAttribute(String s) {
        // which enum is it?
        for (E en : EnumSet.allOf(enumClass)) {
            if (en.name().equalsIgnoreCase(s)) {
                return en;
            }
        }
        return null;
    }

}

что не будет работать, так это то, что я не знаю, что будет enumClass во время выполнения. И я не мог понять, как передать эту информацию в преобразователь в аннотации @Converter.

Итак, есть ли способ добавить параметры в конвертер или немного обмануть? Или есть другой способ?

Я использую EclipseLink 2.4.2

Спасибо!

4b9b3361

Ответ 1

Что вам нужно сделать, это написать базовый базовый класс, а затем расширить его для каждого типа перечисления, который вы хотите сохранить. Затем используйте расширенный тип в аннотации @Converter:

public abstract class GenericEnumUppercaseConverter<E extends Enum<E>> implements AttributeConverter<E, String> {
    ...
}

public FooConverter
    extends GenericEnumUppercaseConverter<Foo> 
    implements AttributeConverter<Foo, String> // See Bug HHH-8854
{
    public FooConverter() {
        super(Foo.class);
    }
}

где Foo - это перечисление, которое вы хотите обработать.

Альтернативой будет определение пользовательской аннотации, исправление провайдера JPA для распознавания этой аннотации. Таким образом, вы можете изучить тип поля при создании информации о сопоставлении и передать необходимый тип перечисления в чисто общий конвертер.

по теме:

Ответ 2

Мое решение этой проблемы выглядит аналогичным, а также использует средство конвертации JPA 2.1. Увы, общие типы в Java 8 не подтверждены, и поэтому нет простого способа избежать написания отдельного класса для каждого перечисления Java, которое вы хотите преобразовать в/из формата базы данных.

Тем не менее, вы можете уменьшить процесс записи класса конвертера enum в чистый шаблон. Компонентами этого решения являются:

  • Encodeable интерфейс; контракт для класса enum, который предоставляет доступ к токену String для каждой константы перечисления (также factory для получения константы перечисления для соответствующего токена)
  • AbstractEnumConverter класс; предоставляет общий код для перевода токенов в/из констант перечисления.
  • Переменные классы Java, реализующие интерфейс Encodeable
  • классы конверсий JPA, расширяющие класс AbstractEnumConverter

Интерфейс Encodeable прост и содержит статический метод factory, forToken(), для получения констант перечисления:

public interface Encodeable {

    String token();

    public static <E extends Enum<E> & Encodeable> E forToken(Class<E> cls, String tok) {
        final String t = tok.trim().toUpperCase();
        return Stream.of(cls.getEnumConstants())
                .filter(e -> e.token().equals(t))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Unknown token '" +
                        tok + "' for enum " + cls.getName()));
    }
}

Класс AbstractEnumConverter - это общий класс, который также прост. Он реализует интерфейс атрибута JPA 2.1 AttributeConverter, но не предоставляет реализаций для его методов (поскольку этот класс не может знать конкретные типы, необходимые для получения соответствующих констант перечисления). Вместо этого он определяет вспомогательные методы, которые классы конкретного конвертера будут иметь цепочку:

public abstract class AbstractEnumConverter<E extends Enum<E> & Encodeable>
            implements AttributeConverter<E, String> {

    public String toDatabaseColumn(E attr) {
        return (attr == null)
                ? null
                : attr.token();
    }

    public E toEntityAttribute(Class<E> cls, String dbCol) {
        return (dbCol == null)
                ? null
                : Encodeable.forToken(cls, dbCol);
    }
}

Ниже приведен пример конкретного класса enum, который теперь можно сохранить в базе данных с помощью средства преобразования JPA 2.1 (обратите внимание, что он реализует Encodeable и что токен для каждой константы перечисления определяется как частное поле ):

public enum GenderCode implements Encodeable {

    MALE   ("M"), 
    FEMALE ("F"), 
    OTHER  ("O");

    final String e_token;

    GenderCode(String v) {
        this.e_token = v;
    }

    @Override
    public String token() {
        return this.e_token;
    }
}

Оболочка для каждого класса конвертера JPA 2.1 теперь будет выглядеть так (обратите внимание, что каждый такой конвертер должен будет расширять AbstractEnumConverter и обеспечивать реализацию методов интерфейса JPA AttributeConverter):

@Converter
public class GenderCodeConverter 
            extends AbstractEnumConverter<GenderCode> {

    @Override
    public String convertToDatabaseColumn(GenderCode attribute) {
        return this.toDatabaseColumn(attribute);
    }

    @Override
    public GenderCode convertToEntityAttribute(String dbData) {
        return this.toEntityAttribute(GenderCode.class, dbData);
    }
}