我们正在将 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 到狗的映射并不明确,因此我们希望在整个代码中保持枚举名称相同,以提高可读性。
无论我们尝试什么,当尝试向数据库中插入数据时,我们都会看到抛出异常。
我们实现了持久枚举,如下所示:
定义所有可持久枚举将实现的接口:
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
中处理,不需要为每个不同的枚举实现。