首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >泛型枚举JPA AttributeConverter实现

泛型枚举JPA AttributeConverter实现
EN

Stack Overflow用户
提问于 2019-11-03 15:57:43
回答 3查看 1.9K关注 0票数 4

我正在努力解决的问题

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

目前,所有这些值都映射到String / Integer属性。同时,属性应该只允许一组受限的值(假设meetingStatus属性允许String:PLANNEDCANCELEDDONE )。或另一个允许受限制的整数值集的属性:12345)。

我的想法是用枚举替换实现,以提高代码的类型安全性。String / Integer实现可能导致错误的一个很好的例子是表示这种值的String方法参数--使用String,任何东西都会出现。另一方面,使用Enum参数类型会带来编译时的安全性。

到目前为止我最好的方法

似乎满足我需求的唯一解决方案是为每个枚举实现带有@Converter注释的自定义@Converter。由于我的模型需要相当多的枚举,为每个模型编写自定义转换器似乎很快就会变得疯狂。因此,我为->问题寻找了一个通用解决方案--如何为任何类型的枚举编写泛型转换器。下面的答案在这里很有帮助:https://stackoverflow.com/a/23564597/7024402。答案中的代码示例提供了一些通用的实现,但是对于每个枚举,仍然需要一个单独的转换器类。答复的作者还继续说:

“另一种方法是定义一个自定义注释,对JPA提供程序进行修补以识别该注释。这样,您可以在构建映射信息时检查字段类型,并将所需的枚举类型输入到纯泛型转换器中。”

我想我会对此感兴趣的。不幸的是,我找不到更多关于这方面的信息,我需要更多的指导来理解需要做什么,以及如何使用这种方法。

目前执行情况

代码语言:javascript
复制
public interface PersistableEnum<T> {
    T getValue();
}
代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
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);
    }
}
代码语言:javascript
复制
@Converter
public class IntegerEnumConverter extends PersistableEnumConverter<IntegerEnum, Integer> {

    public IntegerEnumConverter() {
        super(IntegerEnum.class);
    }
}

这就是我如何能够实现部分通用转换器实现。

目标:不再需要为每个枚举创建新的转换器类.

EN

回答 3

Stack Overflow用户

发布于 2019-11-09 19:25:53

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

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

代码语言:javascript
复制
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. hibernate用户类型如下所示:

代码语言:javascript
复制
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. ,然后,您可以使用它:

代码语言:javascript
复制
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的优秀著作“”。

票数 1
EN

Stack Overflow用户

发布于 2020-11-20 21:19:55

简化:

代码语言:javascript
复制
import com.pismo.apirest.mvc.enums.OperationType;
import com.pismo.apirest.mvc.enums.support.PersistableEnum;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@SuppressWarnings("unused")
public interface EnumsConverters {

    @RequiredArgsConstructor
    abstract class AbstractPersistableEnumConverter<E extends Enum<E> & PersistableEnum<I>, I> implements AttributeConverter<E, I> {

        private final E[] enumConstants;

        public AbstractPersistableEnumConverter(@NonNull Class<E> enumType) {
            enumConstants = enumType.getEnumConstants();
        }


        @Override
        public I convertToDatabaseColumn(E attribute) {
            return Objects.isNull(attribute) ? null : attribute.getId();
        }

        @Override
        public E convertToEntityAttribute(I dbData) {
            return fromId(dbData, enumConstants);
        }

        public E fromId(I idValue) {
            return fromId(idValue, enumConstants);
        }

        public static <E extends Enum<E> & PersistableEnum<I>, I> E fromId(I idValue, E[] enumConstants) {
            return Objects.isNull(idValue) ? null : Stream.of(enumConstants)
                                                          .filter(e -> e.getId().equals(idValue))
                                                          .findAny()
                                                          .orElseThrow(() -> new IllegalArgumentException(
                                                              String.format("Does not exist %s with ID: %s", enumConstants[0].getClass().getSimpleName(), idValue)));
        }

    }

    @Converter(autoApply = true)
    class OperationTypeConverter extends AbstractPersistableEnumConverter<OperationType, Integer> {

        public OperationTypeConverter() {
            super(OperationType.class);
        }

    }

}
票数 0
EN

Stack Overflow用户

发布于 2022-04-12 15:44:41

我尝试了1000次,创造了同样的东西。为每个枚举动态生成转换器--不存在问题,但它们将具有相同的类。主要问题: org.hibernate.boot.internal.MetadataBuilderImpl#applyAttributeConverter(java.lang.Class<?扩展javax.persistence.AttributeConverter>,布尔)。

如果转换器已经注册,我们就有异常。

代码语言:javascript
复制
public void addAttributeConverterInfo(AttributeConverterInfo info) {
        if ( this.attributeConverterInfoMap == null ) {
            this.attributeConverterInfoMap = new HashMap<>();
        }

        final Object old = this.attributeConverterInfoMap.put( info.getConverterClass(), info );

        if ( old != null ) {
            throw new AssertionFailure(
                    String.format(
                            "AttributeConverter class [%s] registered multiple times",
                            info.getConverterClass()
                    )
            );
        }
    }

也许我们可以改变org.hibernate.boot.internal.BootstrapContext,但我相信它创建的代码过于复杂和不灵活。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58681902

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档