7.1 Создание собственного конвертера типов
Иногда возникают ситуации, когда ты хочешь сохранить в одну колонку таблицы достаточно сложный тип данных. Если Hibernate знает, как преобразовать его в строку (и обратно), то все отлично. Если же нет, то тебе придется написать собственный конвертер данных.
Допустим, кто-то решил хранить год рождения пользователя в базе в виде ГГ.ММ.ДД
, например: 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;
}
Конвертеры очень часто приходится использовать, если не ты проектировал базу данных. Данные там могут находиться в "странных форматах". Даты могут храниться как строки, Boolean как CHAR со значениями Y и N и тому подобное.
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";
}
}
Это что-то типа Enum, который состоит из одного значения. Набор таких одиноких энамов и есть все типы известные 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.javarush.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;
}