Данные, хранимые в колонках БД бывает необходимо как-то преобразовывать при отображении в объекты классов предметной области. И, наоборот, при сохранении данных из объектов в БД также необходимо делать обратное преобразование. Например, температура в объекта может храниться в градусах Цельсия, а в БД в фаренгейтах. Выполнить такое преобразование можно с помощью аннотации @ColumnTransformer.
Прежде всего отметим следующим момент. Hibernate может пользоваться геттерами и сеттерами при чтении/записи данных в объект. Поэтому проблему можно решить внутри Java-приложения, поместив алгоритмы преобразования в геттеры и сеттеры.
Но, если нужно, чтобы необходимые для выполнения преобразований вычисления происходили внутри БД (возможно с использованием стоковых функций и хранимых процедур данной БД, если нет вопроса переносимости), то задача решается с помощью аннотации @ColumnTransformer над соответствующим полем. Рассмотрим пример.
Создадим базовое веб-приложение на связке Spring Boot 3 + Hibernate + PostgreSQL
Создадим класс предметной области следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Entity public class TvSet { @Id @GeneratedValue private Long id; private String model; @Column(name = "diagonal_inches") @ColumnTransformer( read = "diagonal_inches * 2.54", write = "? / 2.54" ) private BigDecimal diagonalCentimeters; //Конструкторы, геттеры и сеттеры, equals(), hashCode() и т.д. } |
В поле diagonalCentimeters мы будем хранить данные о диагонали телевизора в сантиметрах. Но в БД этому полю должна соответствовать колонка diagonal_inches, данные в которой, как это видно из названия, должны быть в дюймах.
Чтобы преобразование из сантиметров в дюймы и обратно происходило автоматически силами БД, мы ставим над полем аннотацию @Column(name = «diagonal_inches»), указав таким образом имя колонки для отображения. А также аннотацию @ColumnTransformer с двумя параметрами:
- read — фрагмент sql-кода, который должен быть выполнен во время выборки данных из БД.
- write — фрагмент sql-кода, который должен быть выполнен во время вставки строки в БД.
Создадим тестовый класс следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@SpringBootTest class SpringHibernatePostgresqlApplicationTests { @PersistenceContext private EntityManager entityManager; @Autowired TvSetRepository tvSetRepository; @Test void lengthConvertationTest() { BigDecimal diagonal = BigDecimal.valueOf(81.28); tvSetRepository.save(new TvSet("XXX-100000000-ABC", diagonal)); List<TvSet> tableData = tvSetRepository.findAll(); BigDecimal diagonalInTable = tableData.get(0).getDiagonalCentimeters().stripTrailingZeros(); assertEquals(diagonalInTable, diagonal); } @Test void lengthConvertationPredicateTest() { String model = "XXX-200000000-ABC"; BigDecimal diagonal = BigDecimal.valueOf(101.6); tvSetRepository.save(new TvSet(model, diagonal)); List<TvSet> result = entityManager .createQuery(" SELECT t FROM TvSet t WHERE t.diagonalCentimeters > :d") .setParameter("d", 100) .getResultList(); assertNotNull(result); assertEquals(1, result.size()); assertEquals(model, result.get(0).getModel()); } } |
В первом тестовом методе мы просто создаём новый объект типа TvSet с диагональю 81,28 сантиметров. Сохраняем данные в БД и тут же извлекаем их. Убеждаемся, что после извлечения данных ничего не поменялось и диагональ по-прежнему 81,28 в сантиметрах.
Во втором тестовом методе мы сохраняем в БД ещё одну строку с телевизором побольше — 101,6 сантиметров диагонали. Затем с помощью кастомного запроса выбираем из базы телевизоры с диагональю больше 100 см. Во всех случаях, как видите, мы оперируем исключительно сантиметрами.
Откроем консоль БД и посмотрим, что в итоге хранится в нашей таблице:

В БД диагональ хранится в дюймах. Обратите внимание, что предикат кастомного запроса WHERE t.diagonalCentimeters > :d
отработал абсолютно правильно, хотя в параметр мы передали число 100 (имея в виду 100 сантиметров). В таблице нет ни одного поля с числом больше ста. И тем не менее выборка отработала корретно, с учётом преобразований сантиметров в дюймы.
Однако стоит иметь в виду, что фильтрация по преобразовываемым полям исключает использование индексов и на больших данных может быть не самым эффективным решением.
Если включить вывод sql-кода, формируемого Hibernate’ом, в консоль. То вставка и выборка будут выглядеть следующим образом:
1 2 |
Hibernate: insert into tv_set (diagonal_inches, model, id) values (? / 2.54, ?, ?) Hibernate: select t1_0.id,t1_0.diagonal_inches * 2.54,t1_0.model from tv_set t1_0 |
Из данных примеров довольно очевидно, как именно используются фрагменты sql-кода, которые мы передаём в параметры read и write аннотации @ColumnTransformer. Соответственно, примерно понятны возможности и ограничения этого метода преобразования значений между полями БД и полями сохраняемых сущностей.