Hiberate 6 和 Spring Boot 3:无法将 Java 枚举映射到 PostgreSql 枚举

问题描述 投票:0回答:1

我们正在将 Java 17 服务从 Spring Boot 2 迁移到 Spring Boot 3,从而将 Hibernate 5 迁移到 Hibernate 6。我使用动物示例对下面的示例进行了匿名化处理。

我们的表格列定义为:

CREATE TYPE animal as ENUM ('dog', 'cat', 'other');

在 Java 中,Spring Boot 2 的工作实现是:

public enum AnimalType{
  DOG("dog"),
  CAT("cat"),
  UNKNOWN("other");

  private String type;

  AnimalType(String type) {
    this.type = type;
  }

  @Override
  public String getType() {
     return type;
  }

  public static AnimalType fromType(final String type) {
    if ("dog".equals(type)) {
      return AnimalType.DOG;
    } else if ("cat".equals(type)) {
      return AnimalType.CAT;
    } else {
       return AnimalType.UNKNOWN;
    }
  }
}
@TypeDefs({
  @TypeDef(
    name = "animal_type",
    typeClass = AnimalTypeSql.class
  )
})

@Type(type = "animal_type")
@Column(name = "TYPE")
private AnimalType animalType;
public class AnimalTypeSql extends EnumType { // EnumType is now deprecated so we want to avoid using it

  @Override
  public Object nullSafeGet(final ResultSet rs, final String[] names, final SharedSessionContractImplementor session, final Object owner) throws SQLException {
    if (rs.wasNull() || names == null || names.length == 0) {
      return null;
    }
    final String value = rs.getString(names[0]);
    return AnimalType.fromType(value);
  }

  @Override
  public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException {
    if (value == null) {
      st.setNull(index, Types.OTHER);
    } else {
      st.setObject(index, value.toString, Types.OTHER);
  }
}

在 Spring Boot 3.2.9 中我们在专栏上尝试了多种组合

//@Convert(converter = AnimalTypeConverter.class)
//@JdbcType(value = PostgreSQLEnumJdbcType.class)
//@JdbcTypeCode(SqlType.NAMED_ENUM)
//@Enumerated(EnumType.String)
@Column(name = "TYPE")
private AnimalType animalType;

我们唯一要做的就是将枚举更改为:

public enum AnimalType{
  dog("dog"),
  cat("cat"),
  other("other");
 ....

我们希望避免这种情况,因为在某些情况下 DOG 到狗的映射并不明确,因此我们希望在整个代码中保持枚举名称相同,以提高可读性。

无论我们尝试什么,当尝试向数据库中插入数据时,我们都会看到抛出异常。

postgresql spring-boot hibernate spring-data-jpa
1个回答
0
投票

我们实现了持久枚举,如下所示:

定义所有可持久枚举将实现的接口:

public interface PersistableEnum<T> {
  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(final Class<T> clazz) {
    this.clazz = clazz;
  }

  @Override
  public E convertToDatabaseColumn(final T attribute) {
    return attribute != null ? attribute.getValue() : null;
  }

  @Override
  public T convertToEntityAttribute(final E dbData) {
    if (dbData == null) {
      return null;
    }

    final T[] enums = clazz.getEnumConstants();

    for (final T e : enums) {
      if (e.getValue().equals(dbData)) {
        return e;
      }
    }

    throw new UnsupportedOperationException("Unknown enumeration " + 
      dbData.toString());
  }
}

持久枚举的示例如下:

public enum Unit implements PersistableEnum<String> {
  OUNCES("oz"),
  POUNDS("lb"),
  GRAMS("g");

  private final String value;

  private Unit(final String value) {
    this.value = value;
  }

  public String getValue() {
    return this.value;
  }

  public static Unit fromValue(final String value) {
    for (final Unit unit : Unit.class.getEnumConstants()) {
      if (unit.getValue().equals(value)) {
        return unit;
      }
    }

    throw new IllegalArgumentException("Unknown unit " + value);
  }

  @Converter(autoApply = true)
  public static class UnitConverter 
    extends AbstractEnumConverter<Unit, String> {

    public UnitConverter() {
      super(Unit.class);
    }
  }
}

然后,您可以将枚举定义为实体属性,就像定义任何其他属性一样。没有什么需要做的。

@Column(length = 10)
private Unit unit;

转换所需的许多样板代码都在

AbstractEnumConverter
中处理,不需要为每个不同的枚举实现。

© www.soinside.com 2019 - 2024. All rights reserved.