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

问题描述 投票:22回答:3

我想为JPA编写一个Converter,它将任何枚举存储为UPPERCASE。我们遇到的一些枚举不遵循惯例只使用大写字母,因此在重构之前我仍然存储未来值。

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

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

谢谢!

java jpa enums eclipselink
3个回答
14
投票

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

有关:


12
投票

基于@scottb解决方案我做了这个,针对hibernate 4.3进行了测试:(没有hibernate类,应该在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接口并为此进行生成。


5
投票

我对这个问题的解决方案看起来很相似,并且还使用了JPA 2.1 Converter工具。唉,Java 8中的泛型类型没有具体化,因此似乎没有一种简单的方法可以避免为每个希望能够转换为数据库格式或从数据库格式转换的Java枚举编写单独的类。

但是,您可以减少将枚举转换器类写入纯样板的过程。该解决方案的组件是:

  1. Encodeable界面;枚举类的合约,为每个枚举常量授予访问String标记的权限(也是为匹配标记获取枚举常量的工厂)
  2. AbstractEnumConverter类;提供了用于将令牌转换为枚举常量的公共代码
  3. 实现Encodeable接口的Java枚举类
  4. 扩展AbstractEnumConverter类的JPA转换器类

Encodeable接口很简单,包含一个静态工厂方法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);
    }
}

现在可以使用JPA 2.1 Converter工具持久保存到数据库的具体枚举类的示例如下所示(请注意,它实现了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 Converter类的样板现在看起来像这样(请注意,每个这样的转换器都需要扩展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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.