Конвертация значений между базой данных и объектами предметной области в Hibernate с помощью аннотации @ColumnTransformer

Данные, хранимые в колонках БД бывает необходимо как-то преобразовывать при отображении в объекты классов предметной области. И, наоборот, при сохранении данных из объектов в БД также необходимо делать обратное преобразование. Например, температура в объекта может храниться в градусах Цельсия, а в БД в фаренгейтах. Выполнить такое преобразование можно с помощью аннотации @ColumnTransformer.

Прежде всего отметим следующим момент. Hibernate может пользоваться геттерами и сеттерами при чтении/записи данных в объект. Поэтому проблему можно решить внутри Java-приложения, поместив алгоритмы преобразования в геттеры и сеттеры.

Но, если нужно, чтобы необходимые для выполнения преобразований вычисления происходили внутри БД (возможно с использованием стоковых функций и хранимых процедур данной БД, если нет вопроса переносимости), то задача решается с помощью аннотации @ColumnTransformer над соответствующим полем. Рассмотрим пример.

Создадим базовое веб-приложение на связке Spring Boot 3 + Hibernate + PostgreSQL

Создадим класс предметной области следующего содержания:

В поле diagonalCentimeters мы будем хранить данные о диагонали телевизора в сантиметрах. Но в БД этому полю должна соответствовать колонка diagonal_inches, данные в которой, как это видно из названия, должны быть в дюймах.

Чтобы преобразование из сантиметров в дюймы и обратно происходило автоматически силами БД, мы ставим над полем аннотацию @Column(name = «diagonal_inches»), указав таким образом имя колонки для отображения. А также аннотацию @ColumnTransformer с двумя параметрами:

  • read — фрагмент sql-кода, который должен быть выполнен во время выборки данных из БД.
  • write — фрагмент sql-кода, который должен быть выполнен во время вставки строки в БД.

Создадим тестовый класс следующего содержания:

В первом тестовом методе мы просто создаём новый объект типа TvSet с диагональю 81,28 сантиметров. Сохраняем данные в БД и тут же извлекаем их. Убеждаемся, что после извлечения данных ничего не поменялось и диагональ по-прежнему 81,28 в сантиметрах.

Во втором тестовом методе мы сохраняем в БД ещё одну строку с телевизором побольше — 101,6 сантиметров диагонали. Затем с помощью кастомного запроса выбираем из базы телевизоры с диагональю больше 100 см. Во всех случаях, как видите, мы оперируем исключительно сантиметрами.

Откроем консоль БД и посмотрим, что в итоге хранится в нашей таблице:

В БД диагональ хранится в дюймах. Обратите внимание, что предикат кастомного запроса WHERE t.diagonalCentimeters > :d отработал абсолютно правильно, хотя в параметр мы передали число 100 (имея в виду 100 сантиметров). В таблице нет ни одного поля с числом больше ста. И тем не менее выборка отработала корретно, с учётом преобразований сантиметров в дюймы.

Однако стоит иметь в виду, что фильтрация по преобразовываемым полям исключает использование индексов и на больших данных может быть не самым эффективным решением.

Если включить вывод sql-кода, формируемого Hibernate’ом, в консоль. То вставка и выборка будут выглядеть следующим образом:

Из данных примеров довольно очевидно, как именно используются фрагменты sql-кода, которые мы передаём в параметры read и write аннотации @ColumnTransformer. Соответственно, примерно понятны возможности и ограничения этого метода преобразования значений между полями БД и полями сохраняемых сущностей.