7.1 创建自己的类型转换器

有时,当您想在表的一列中存储相当复杂的数据类型时,会出现这种情况。如果 Hibernate 知道如何将它转换为字符串(并返回),那么一切都很好。如果没有,那么您将不得不编写自己的数据转换器。

假设某人决定将用户的出生年份存储在数据库中YY.MM.DD,例如:98.12.15。您还需要将其转换为常规日期:15/12/1998。然后你必须编写自己的转换器。

为此,您需要实现一个接口AttributeConverter<EntityType, DbType>


@Converter(autoApply = true)
public class DateConverter implements AttributeConverter<java.time.LocalDate, String> {
 
    public String convertToDatabaseColumn(java.time.LocalDate date) {
    	return date.format("YY.MM.DD");
    }
 
    public java.time.LocalDate convertToEntityAttribute(String dbData) {
    	String[] data = dbData.split(".");
    	return LocalDate.of(data[2], data[1], "19"+data[0]);
    }
}

当然,这个转换器可以添加到任何字段(前提是类型匹配):


@Entity
@Table(name="user")
class User {
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_date")
   @Convert(converter = DateConverter.class)
   public java.time.LocalDate date;
}

如果您没有设计数据库,则通常必须使用转换器。那里的数据可能是“奇怪的格式”。日期可以存储为字符串,布尔值可以存储为具有 Y 和 N 值的 CHAR,等等。

7.2 创建我们自己的数据类型

还记得带有 Hibernate 已知类型列表的表格吗?我说的是与注释一起指定的类型@Type。您可以编写自己的数据类型,它可以像 Hibernate 中的其他内置类型一样使用。

例如,我们想要 LocalTime 类型,它在数据库中存储的不是 TIME,而是 VARCHAR。并且,例如,我们可以访问这样的数据库,并且不允许我们更改其列中的数据类型。然后我们可以编写自己的 Hibernate 类型。我们称它为 LocalTimeString。

首先我们需要一个小类来描述我们的新类型:

public class LocalTimeStringType extends AbstractSingleColumnStandardBasicType<<LocalTime> {

    public static final LocalTimeStringType  INSTANCE = new LocalTimeStringType ();

    public LocalTimeStringType () {
    	super(VarcharTypeDescriptor.INSTANCE, LocalTimeStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
    	return "LocalTimeString";
    }
}

它是一种由一个值组成的枚举类型。此类单个 enams 的集合是 Hibernate 已知的所有类型。

我们还需要一个类 - 转换器的模拟,它将包含两个方法 -wrap()并将unwrap()LocalTime 类型的值转换为 String。

这是不执行这些方法时的样子:

public class LocalTimeStringJavaDescriptor extends AbstractTypeDescriptor<LocalTime> {

    public static final LocalTimeStringJavaDescriptor INSTANCE =  new  LocalTimeStringJavaDescriptor();

    public LocalTimeStringJavaDescriptor() {
    	super(LocalTime.class, ImmutableMutabilityPlan.INSTANCE);
    }

    public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    }

    public <X> LocalTime wrap(X value, WrapperOptions options) {

    }

}

现在让我们编写方法的实现:

public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    if (value == null)
    	return null;

    if (String.class.isAssignableFrom(type))
    	return (X) LocalTimeType.FORMATTER.format(value);

    throw unknownUnwrap(type);
}

第二种方法:

@Override
public <X> LocalTime wrap(X value, WrapperOptions options) {
    if (value == null)
    	return null;

    if(String.class.isInstance(value))
    	return LocalTime.from(LocalTimeType.FORMATTER.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

准备好。您可以使用此类将时间存储为字符串:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "com.codegym.hibernate.customtypes.LocalTimeStringType")  
   public java.time.LocalTime time;
}

7.3 注册你的类型

您还可以在 Hibernate 配置期间注册您的数据类型。这有点不平凡。


ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
    .applySettings(getProperties()).build();
                                                                                                                                                              	                                        	 
    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources
  	.addAnnotatedClass(User.class)
  	.getMetadataBuilder()
  	.applyBasicType(LocalTimeStringType.INSTANCE)
  	.build();
                                                                                                                                                              	                                        	 
    SessionFactory factory =  metadata.buildSessionFactory();

您首先需要获取 MetadataSources,从中获取 MetadataBuilder,然后使用它来添加您的类。通过 是可以的hibernate.cfg.xml,但是也有点麻烦。

但是注册之后,你可以这样写:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "LocalTimeString")  
   public java.time.LocalTime time;
}