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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23564506/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 00:00:04  来源:igfitidea点击:

Is it possible to write a generic enum converter for JPA?

javajpaenumseclipselink

提问by wemu

I wanted to write a Converter for JPA that stores any enum as UPPERCASE. Some enums we encounter do not follow yet the convention to use only Uppercase letters so until they are refactored I still store the future value.

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

What I got so far:

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

package student;

public enum StudentState {

    Started,
    Mentoring,
    Repeating,
    STUPID,
    GENIUS;
}

I want "Started" to be stored as "STARTED" and so on.

我希望将“Started”存储为“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;

}

the converter currently looks like this:

转换器目前看起来像这样:

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

}

what will not work is that I do not know what enumClass will be at runtime. And I could not figure out a way to pass this information to the converter in the @Converter annotation.

不起作用的是我不知道 enumClass 在运行时会是什么。而且我无法找到一种方法将这些信息传递给 @Converter 注释中的转换器。

So is there a way to add parameters to the converter or cheat a bit? Or is there another way?

那么有没有办法向转换器添加参数或作弊呢?或者还有其他方法吗?

I'm using EclipseLink 2.4.2

我正在使用 EclipseLink 2.4.2

Thanks!

谢谢!

采纳答案by Aaron Digulla

What you need to do is write a generic base class and then extend that for each enum type you want to persist. Then use the extended type in the @Converterannotation:

您需要做的是编写一个通用基类,然后为您想要保留的每个枚举类型扩展它。然后在@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);
    }
}

where Foois the enum you want to handle.

Foo您要处理的枚举在哪里。

The alternative would be to define a custom annotation, patch the JPA provider to recognize this annotation. That way, you could examine the field type as you build the mapping information and feed the necessary enum type into a purely generic converter.

另一种方法是定义自定义注释,修补 JPA 提供程序以识别此注释。这样,您可以在构建映射信息时检查字段类型,并将必要的枚举类型提供给一个纯粹的通用转换器。

Related:

有关的:

回答by scottb

My solution to this problem looks similar and also makes use of the JPA 2.1 Converter facility. Alas, generic types in Java 8 are not reified, and so there does not appear to be an easy way to avoid writing a separate class for each Java enum that you want to be able to convert to/from a database format.

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

You can however reduce the process of writing an enum converter class to pure boilerplate. The components of this solution are:

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

  1. Encodeableinterface; the contract for an enum class that grants access to a Stringtoken for each enum constant (also a factory for getting the enum constant for a matching token)
  2. AbstractEnumConverterclass; provides the common code for translating tokens to/from enum constants
  3. Java enum classes that implement the Encodeableinterface
  4. JPA converter classes that extend the AbstractEnumConverterclass
  1. Encodeable界面; 授予String对每个枚举常量的令牌的访问权限的枚举类的合同(也是用于获取匹配令牌的枚举常量的工厂)
  2. AbstractEnumConverter班级; 提供用于将令牌转换为/从枚举常量转换的通用代码
  3. 实现Encodeable接口的Java枚举类
  4. 扩展AbstractEnumConverter类的JPA 转换器类

The Encodeableinterface is simple and contains a static factory method, forToken(), for obtaining enum constants:

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()));
    }
}

The AbstractEnumConverterclass is a generic class that is also simple. It implements the JPA 2.1 AttributeConverter interface but provides no implementations for its methods (because this class can't know the concrete types needed for obtaining the appropriate enum constants). Instead, it defines helper methods that the concrete converter classes will chain to:

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);
    }
}

An example of a concrete enum class that could now be persisted to a database with the JPA 2.1 Converter facility is shown below (note that it implements Encodeable, and that the token for each enum constant is defined as a private field):

现在可以使用 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;
    }
}

The boilerplate for every JPA 2.1 Converter class would now look like this (note that every such converter will need to extend AbstractEnumConverterand provide implementations for the methods of the JPA AttributeConverter interface):

每个 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);
    }
}

回答by ChRoNoN

Based on @scottb solution I made this, tested against hibernate 4.3: (no hibernate classes, should run on JPA just fine)

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

Interface enum must implement:

接口枚举必须实现:

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

Base abstract converter:

基础抽象转换器:

@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();
    }
}

You must create a converter class for each enum, I find it easier to create static class inside the enum: (jpa/hibernate could just provide the interface for the enum, oh well...)

您必须为每个枚举创建一个转换器类,我发现在枚举中创建静态类更容易:(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);
        }
    }
}

And mapping example with annotation:

和带注释的映射示例:

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

With some changes you can create a IntegerEnum interface and generify for that.

通过一些更改,您可以创建一个 IntegerEnum 接口并对其进行泛化。