通用枚举JPA AttributeConverter实现 [英] Generic enum JPA AttributeConverter implementation

查看:391
本文介绍了通用枚举JPA AttributeConverter实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为Hibernate实现枚举映射.到目前为止,我已经研究了可用的选项,而@Enumerated(EnumType.ORDINAL)@Enumerated(EnumType.STRING)似乎都不足以满足我的需求. @Enumerated(EnumType.ORDINAL)似乎很容易出错,因为仅枚举常量的重新排序可能会使映射混乱,并且@Enumerated(EnumType.STRING)也不足够,因为我使用的数据库已经充满了要映射的值,这些值不是我希望枚举常量命名的值(这些值是外语字符串/整数).

I am trying to implement enum mapping for Hibernate. So far I have researched available options, and both the @Enumerated(EnumType.ORDINAL) and @Enumerated(EnumType.STRING) seemed inadequate for my needs. The @Enumerated(EnumType.ORDINAL) seems to be very error-prone, as a mere reordering of enum constants can mess the mapping up, and the @Enumerated(EnumType.STRING) does not suffice too, as the database I work with is already full of values to be mapped, and these values are not what I would like my enum constants to be named like (the values are foreign language strings / integers).

当前,所有这些值都被映射到String/Integer属性.同时,这些属性应仅允许使用一组受限的值(想象的meetingStatus属性允许使用Strings:PLANNEDCANCELEDDONE.或者另一个属性允许使用一组受限制的Integer值: 12345).

Currently, all these values are being mapped to String / Integer properties. At the same time the properties should only allow for a restricted sets of values (imagine meetingStatus property allowing for Strings: PLANNED, CANCELED, and DONE. Or another property allowing for a restricted set of Integer values: 1, 2, 3, 4, 5).

我的想法是用枚举代替实现以提高代码的类型安全性. String/Integer实现可能会导致错误的一个很好的示例是String方法参数,它代表该值-使用String时,一切就在那儿.另一方面,使用Enum参数类型可以提高编译时的安全性.

My idea was to replace the implementation with enums to improve the type safety of the code. A good example where the String / Integer implementation could cause errors is String method parameter representing such value - with String, anything goes there. Having an Enum parameter type on the other hand introduces compile time safety.

唯一可以满足我需要的解决方案是为每个枚举实现带有@Converter批注的自定义javax.persistence.AttributeConverter.由于我的模型需要大量枚举,因此为它们中的每个编写自定义转换器似乎很快就变得很疯狂.因此,我搜索了该问题的通用解决方案->如何为任何类型的枚举编写一个通用转换器.以下答案在这里有很大帮助: https://stackoverflow.com/a/23564597/7024402 .答案中的代码示例提供了某种通用的实现,但是对于每个枚举,仍然需要一个单独的转换器类.答案的作者还继续:

The only solution that seemed to fulfill my needs was to implement custom javax.persistence.AttributeConverter with @Converter annotation for every enum. As my model would require quite a few enums, writing custom converter for each of them started to seem like a madness really quickly. So I searched for a generic solution to the problem -> how to write a generic converter for any type of enum. The following answer was of big help here: https://stackoverflow.com/a/23564597/7024402. The code example in the answer provides for somewhat generic implementation, yet for every enum there is still a separate converter class needed. The author of the answer also continues:

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

这就是我想引起的兴趣.不幸的是,我找不到更多的相关信息,我需要更多的指导以了解需要做什么以及如何使用这种方法.

And that's what I think I would be interested in. I could, unfortunately, not find any more information about that, and I would need a little more guidance to understand what needs to be done and how would it work with this approach.

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

这就是我实现部分通用转换器实现的方式.

This is how I was able to achieve the partially generic converter implementation.

目标:摆脱为每个枚举创建新的转换器类的需求.

推荐答案

幸运的是,您不应为此而修补休眠模式.

Luckily, you should not patch the hibernate for this.

  1. 您可以声明如下注释:

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

  1. 类似于以下的休眠用户类型:

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

  1. 然后,您可以使用它:

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

另请参阅鲍默(Bauer),金格里高里(Gregory)的优秀著作《 Java持久性与Hibernate》中的章节 5.3.3用用户类型扩展Hibernate .

See also the chapter 5.3.3 Extending Hibernate with UserTypes at the excellent book "Java Persistence with Hibernate" by Bauer, King, Gregory.

这篇关于通用枚举JPA AttributeConverter实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆