JavaRush /Курсы /Модуль 4. Работа с БД /Использование различных конвертеров типов данных

Использование различных конвертеров типов данных

Модуль 4. Работа с БД
12 уровень , 6 лекция
Открыта

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;
}
Комментарии (10)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Роман Уровень 88
13 января 2025
честно говоря эту лекцию только скипать, объяснения нулевые какие-то
Alibek Уровень 1 Expert
4 ноября 2024
что то с созданием собственного типа так и не получилось. То библиотека отсутствует, то аннотация не работает а код в лекции вообще для hibernate 5 конструкция поменялась. Наверное эту тему более глубоко надо изучать
Arseniy Rigin Уровень 1 Expert
13 августа 2024
в данной главе делали все на тяп-ляп. уверен, что большинство пришло, чтобы сэкономить время на поиск информации, а тут наооборот....
Дмитрий Уровень 111 Expert
21 января 2024
Я один не пойму, откуда половина классов взялась? Наличие ошибок в примерах не добавляет ясности: то ли библиотек каких не хватает, то ли я что то не правильно сделал, то ли составитель лекций и здесь ошибся. Вот и думай сиди теперь.
Anonymous #3322801 Уровень 2 Expert
25 ноября 2023
А не проще первый пример реализовать вот так? Сплит по точкам в дате это конечно хард программирование какое-то уже.

@Converter(autoApply = true)
public class DateConverter implements AttributeConverter<LocalDate, String> {

    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.ENGLISH);

    @Override
    public String convertToDatabaseColumn(LocalDate localDate) {
        return localDate.format(dateTimeFormatter);
    }
    @Override
    public LocalDate convertToEntityAttribute(String s) {
        return LocalDate.parse(s, dateTimeFormatter);
    }
}
А по поводу остальных примеров, буду надеяться что никогда не столкнусь с этой жестью в жизни😃
Надежда Уровень 104 Expert
27 сентября 2023
Куча ошибок. Исправьте, чтобы не вводить в заблуждение своих студентов: @Converter(autoApply = true) public class DateConverter implements AttributeConverter<LocalDate, String> { @Override public String convertToDatabaseColumn(LocalDate date) { // Создаем DateTimeFormatter с желаемым форматом DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yy.MM.dd"); // формат для года и дня записывается в нижнем регистре // Форматируем дату с использованием форматтера return date.format(formatter); } @Override public LocalDate convertToEntityAttribute(String dbData) { // Разбиваем строку dbData на составляющие по точке (.) String[] data = dbData.split("\\."); // Нужно экранировать точку с помощью "\\" // Парсим значения из массива и создаем объект LocalDate int year = Integer.parseInt("19" + data[0]); // Правильно складываем год int month = Integer.parseInt(data[1]); int day = Integer.parseInt(data[2]); return LocalDate.of(day, month, year); }
Сергей Уровень 108 Expert
7 сентября 2023
интересно это действительно нужно и повсеместно используется (про свои типы) или это просто инфы навалили сырой чтоб абы страничку чемто заполнить
aDuVaN4Ik Уровень 42
18 января 2023
Ну это не в какие ворота не лезет. Слишком плохо объяснили и код без подсветок как то не по родному выглядет:/
Эльдар Уровень 108 Expert
6 октября 2023
Я один просмотрел эту лекцию сверху вниз и даже не захотел вникать что есть что???=))
Руслан Уровень 108 Expert
4 декабря 2022
Смутно понятно о чем речь, ощущение, будто автор выдохся в конце и хотел побыстрее закончить этот уровень. Код почти без объяснений.