是否可以为 JPA 编写通用枚举转换器?

2022-09-01 02:30:05

我想为JPA编写一个转换器,将任何枚举存储为大写。我们遇到的一些枚举还没有遵循仅使用大写字母的约定,因此在重构它们之前,我仍然存储未来值。

到目前为止,我得到了什么:

package student;

public enum StudentState {

    Started,
    Mentoring,
    Repeating,
    STUPID,
    GENIUS;
}

我希望将“已启动”存储为“已启动”,依此类推。

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

谢谢!


答案 1

基于@scottb解决方案,我做了这个,针对休眠4.3进行了测试:(没有休眠类,应该在JPA上运行就好了)

接口枚举必须实现:

public interface PersistableEnum<T> {
    public T getValue();
}

基本抽象转换器:

@Converter
public abstract class AbstractEnumConverter<T extends Enum<T> & PersistableEnum<E>, E> implements AttributeConverter<T, E> {
    private final Class<T> clazz;

    public AbstractEnumConverter(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public E convertToDatabaseColumn(T attribute) {
        return attribute != null ? attribute.getValue() : null;
    }

    @Override
    public T convertToEntityAttribute(E dbData) {
        T[] enums = clazz.getEnumConstants();

        for (T e : enums) {
            if (e.getValue().equals(dbData)) {
                return e;
            }
        }

        throw new UnsupportedOperationException();
    }
}

你必须为每个枚举创建一个转换器类,我发现在枚举内创建静态类更容易:(jpa/hibernate可以只提供枚举的接口,哦,好吧...)

public enum IndOrientation implements PersistableEnum<String> {
    LANDSCAPE("L"), PORTRAIT("P");

    private final String value;

    @Override
    public String getValue() {
        return value;
    }

    private IndOrientation(String value) {
        this.value= value;
    }

    public static class Converter extends AbstractEnumConverter<IndOrientation, String> {
        public Converter() {
            super(IndOrientation.class);
        }
    }
}

带注释的映射示例:

...
@Convert(converter = IndOrientation.Converter.class)
private IndOrientation indOrientation;
...

通过一些更改,您可以创建一个 IntegerEnum 接口并为此进行生成。


答案 2

您需要做的是编写一个泛型基类,然后为要保留的每个枚举类型扩展该基类。然后在批注中使用扩展类型:@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提供程序以识别此注释。这样,您可以在构建映射信息时检查字段类型,并将必要的枚举类型馈送到纯泛型转换器中。

相关: