java - 通用枚举 JPA AttributeConverter 实现

标签 java hibernate jpa generics enums

我正在尝试解决的问题
我正在尝试为 Hibernate 实现枚举映射。到目前为止,我已经研究了可用的选项,以及 @Enumerated(EnumType.ORDINAL)@Enumerated(EnumType.STRING)似乎不足以满足我的需要。 @Enumerated(EnumType.ORDINAL)似乎很容易出错,因为仅仅对枚举常量进行重新排序就会弄乱映射,而 @Enumerated(EnumType.STRING)还不够,因为我使用的数据库已经充满了要映射的值,而这些值不是我希望我的枚举常量命名的(值是外语字符串/整数)。
目前,所有这些值都被映射到 String/Integer 属性。同时,这些属性应该只允许一组受限制的值(想象一下 meetingStatus 允许字符串的属性: PLANNEDCANCELEDDONE 。或者另一个允许一组受限制的整数值的属性: 12345)。
我的想法是用枚举替换实现以提高代码的类型安全性。 String/Integer 实现可能导致错误的一个很好的例子是表示此类值的 String 方法参数 - 使用 String,任何东西都在那里。另一方面,拥有 Enum 参数类型会引入编译时安全性。
到目前为止我最好的方法
似乎满足我需求的唯一解决方案是实现自定义 javax.persistence.AttributeConverter@Converter每个枚举的注释。由于我的模型需要相当多的枚举,因此为每个枚举编写自定义转换器很快就开始变得疯狂。因此,我搜索了该问题的通用解决方案 -> 如何为任何类型的枚举编写通用转换器。以下答案在这里有很大帮助:https://stackoverflow.com/a/23564597/7024402 .答案中的代码示例提供了某种通用的实现,但对于每个枚举,仍然需要一个单独的转换器类。答案的作者还继续:
“另一种方法是定义自定义注释,修补 JPA 提供程序以识别此注释。这样,您可以在构建映射信息时检查字段类型,并将必要的枚举类型提供给纯通用转换器。”
这就是我认为我会感兴趣的内容。不幸的是,我找不到更多关于此的信息,我需要更多指导来了解需要做什么以及如何使用这种方法。
当前实现

public interface PersistableEnum<T> {
    T getValue();
}
public enum IntegerEnum implements PersistableEnum<Integer> {
    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5),
    SIX(6);

    private int value;

    IntegerEnum(int value) {
        this.value = value;
    }

    @Override
    public Integer getValue() {
        return value;
    }
}
public abstract class PersistableEnumConverter<E extends PersistableEnum<T>, T> implements AttributeConverter<E, T> {

    private Class<E> enumType;

    public PersistableEnumConverter(Class<E> enumType) {
        this.enumType = enumType;
    }

    @Override
    public T convertToDatabaseColumn(E attribute) {
        return attribute.getValue();
    }

    @Override
    public E convertToEntityAttribute(T dbData) {
        for (E enumConstant : enumType.getEnumConstants()) {
            if (enumConstant.getValue().equals(dbData)) {
                return enumConstant;
            }
        }
        throw new EnumConversionException(enumType, dbData);
    }
}
@Converter
public class IntegerEnumConverter extends PersistableEnumConverter<IntegerEnum, Integer> {

    public IntegerEnumConverter() {
        super(IntegerEnum.class);
    }
}
这就是我能够实现部分通用转换器实现的方式。
目标:摆脱为每个枚举创建新转换器类的需要。

最佳答案

幸运的是,您不应该为此修补 hibernate 。

  • 您可以声明如下注释:

  • import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.sql.Types;
    
    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    
    @Target({METHOD, FIELD}) 
    @Retention(RUNTIME)
    public @interface EnumConverter
    {
       Class<? extends PersistableEnum<?>> enumClass() default IntegerEnum.class;
    
       int sqlType() default Types.INTEGER;
    }
    
  • 类似以下的 hibernate 用户类型:

  • import java.io.Serializable;
    import java.lang.annotation.Annotation;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Types;
    import java.util.Objects;
    import java.util.Properties;
    
    import org.hibernate.HibernateException;
    import org.hibernate.engine.spi.SharedSessionContractImplementor;
    import org.hibernate.usertype.DynamicParameterizedType;
    import org.hibernate.usertype.UserType;
    
    public class PersistableEnumType implements UserType, DynamicParameterizedType
    {
       private int sqlType;
       private Class<? extends PersistableEnum<?>> clazz;
    
       @Override
       public void setParameterValues(Properties parameters)
       {
          ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
    
          EnumConverter converter = getEnumConverter(reader);
          sqlType = converter.sqlType();
          clazz = converter.enumClass();
       }
    
       private EnumConverter getEnumConverter(ParameterType reader)
       {
          for (Annotation annotation : reader.getAnnotationsMethod()){
             if (annotation instanceof EnumConverter) {
                return (EnumConverter) annotation;
             }
          }
          throw new IllegalStateException("The PersistableEnumType should be used with @EnumConverter annotation.");
       }
    
       @Override
       public int[] sqlTypes()
       {
          return new int[] {sqlType};
       }
    
       @Override
       public Class<?> returnedClass()
       {
          return clazz;
       }
    
       @Override
       public boolean equals(Object x, Object y) throws HibernateException
       {
          return Objects.equals(x, y);
       }
    
       @Override
       public int hashCode(Object x) throws HibernateException
       {
          return Objects.hashCode(x);
       }
    
       @Override
       public Object nullSafeGet(ResultSet rs,
             String[] names,
             SharedSessionContractImplementor session,
             Object owner) throws HibernateException, SQLException 
       {
          Object val = null;
          if (sqlType == Types.INTEGER) val = rs.getInt(names[0]);
          if (sqlType == Types.VARCHAR) val = rs.getString(names[0]);
    
          if (rs.wasNull()) return null;
    
          for (PersistableEnum<?> pEnum : clazz.getEnumConstants())
          {
             if (Objects.equals(pEnum.getValue(), val)) return pEnum;
          }
          throw new IllegalArgumentException("Can not convert " + val + " to enum " + clazz.getName());
       }
    
       @Override
       public void nullSafeSet(PreparedStatement st,
             Object value,
             int index,
             SharedSessionContractImplementor session) throws HibernateException, SQLException
       {
          if (value == null) {
             st.setNull(index, sqlType);
          }
          else {
             PersistableEnum<?> pEnum = (PersistableEnum<?>) value;
             if (sqlType == Types.INTEGER) st.setInt(index, (Integer) pEnum.getValue());
             if (sqlType == Types.VARCHAR) st.setString(index, (String) pEnum.getValue());
          }
       }
    
       @Override
       public Object deepCopy(Object value) throws HibernateException
       {
          return value;
       }
    
       @Override
       public boolean isMutable()
       {
          return false;
       }
    
       @Override
       public Serializable disassemble(Object value) throws HibernateException
       {
          return Objects.toString(value);
       }
    
       @Override
       public Object assemble(Serializable cached, Object owner) throws HibernateException
       {
          return cached;
       }
    
       @Override
       public Object replace(Object original, Object target, Object owner) throws HibernateException
       {
          return original;
       }
    }
    
    
  • 然后,您可以使用它:

  • import org.hibernate.annotations.Type;
    
    @Entity
    @Table(name="TST_DATA")
    public class TestData
    {
       ...
    
       @EnumConverter(enumClass = IntegerEnum.class, sqlType = Types.INTEGER)
       @Type(type = "com.example.converter.PersistableEnumType")
       @Column(name="INT_VAL")
       public IntegerEnum getIntValue()
       ...
    
       @EnumConverter(enumClass = StringEnum.class, sqlType = Types.VARCHAR)
       @Type(type = "com.example.converter.PersistableEnumType")
       @Column(name="STR_VAL")
       public StringEnum getStrValue()
       ...
    }
    
    

    另见章5.3.3 使用 UserTypes 扩展 Hibernate 在 Bauer、King、Gregory 的优秀著作“Java Persistence with Hibernate”中。

    关于java - 通用枚举 JPA AttributeConverter 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58681902/

    相关文章:

    java - 尽管如此,还是不​​能覆盖bean@Primary Spring注释

    java - 在liferay中使用ajax

    java - 为什么Hibernate要改变DataBase中存储的DateTime?

    java - 如何使用 JPA GROUP BY 获取 SELECT 值和 COUNT 值

    java - 如何使用 JPA 在 Hibernate 中获取计数?

    java - 如何处理JPA中的OptimisticLockException

    java - Spring MVC RestFul服务+Jersey客户端400错误请求

    java - 如何使用 Java 中的通用方法来 CRUD MySQL 中的任何表,使用带有 Criteria 的 Hibernate,传递表名和/或字段作为参数?

    java - 子查询 - 子句列不可见

    java - 如何在 JPA 中定义多态性