Table of Contents
Overview
One of the lesser-known yet powerful annotations in Hibernate is @ColumnTransformer. This annotation let us apply SQL transformations on entity properties when interacting with the database. You can think of this annotation as a two way converter.
A real life use case
Let’s consider a weather app that initially use Kelvin scale to store the temperature. Later, it needs to display Celsius instead. That means when reading the value from database, we need to subtract the value to 273.15. In the opposite direction, when writing to database, we need to add the value to 273.15.
Hibernate provides a elegant solution for this use case by introduction @ColumnTransformer.
@Entity public class WeatherData { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String location; @Column(name = "temperature_kelvin") @ColumnTransformer( read = "temperature_kelvin - 273.15", write = "? + 273.15" ) private double temperatureCelsius; // Transformed property // Constructors, getters, setters... }
Now we can observe the power of @ColumnTransformer in action:
public class WeatherDemo { private static final StandardServiceRegistry registry = new StandardServiceRegistryBuilder() .configure().build(); private static final SessionFactory factory = new MetadataSources(registry).buildMetadata().buildSessionFactory(); public static void main(String[] args) { //insert some weather data insertWeatherData("Hanoi", 30); insertWeatherData("New York", 20); insertWeatherData("London", 32); //get weather data by location var hanoi = findByLocation("Hanoi"); System.out.println("Hanoi temperature: " + hanoi.getTemperatureCelsius()); } public static WeatherData findByLocation(String location) { try (Session session = factory.openSession()) { return session.createQuery("from WeatherData where location = :location", WeatherData.class) .setParameter("location", location) .getSingleResult(); } } private static void insertWeatherData(String location, int temperature) { var weatherData = new WeatherData(); weatherData.setLocation(location); weatherData.setTemperatureCelsius(temperature); try (Session session = factory.openSession()) { Transaction tx = session.beginTransaction(); session.persist(weatherData); tx.commit(); } } }
In this code snippet, I’ve inserted the data for three cities and try to read the data back.
As you can see, I was able to write the data to and read as Celsius. However, in the database, the values are actually Kelvin:
Other Potential Use Cases of @ColumnTransformer
Let’s consider some other potential use case
Enum Conversions
Imagine an entity class representing an Order with a status property stored as an enum in the database. You can use @ColumnTransformer to convert the enum to its corresponding database representation (e.g., as an integer or string) and vice versa.
JSON Data Transformation
For entities storing JSON data in the database, you can use @ColumnTransformer to handle conversions between JSON and string representations automatically.
Date and Time Zone Conversion
To handle date and time zone conversions, you can use @ColumnTransformer to adjust date values during database interactions.
Consider this example:
@CreationTimestamp @Column(name = "created_at") @ColumnTransformer( read = "CONVERT_TZ(created_at, 'UTC', 'America/New_York')", write = "CONVERT_TZ(?, 'America/New_York', 'UTC')" ) private java.sql.Timestamp createdAt;// Transformed property (Adjusted for time zone)
In this example,I used the “CONVERT_TZ” to convert a temporal value when reading from and writing to database. Note that CONVERT_TZ is database specific function (MySQL/MariaDB). This may break your application if you change the underlying database.
Conclusion
The @ColumnTransformer annotation in Hibernate is a powerful tool that let you customize data transformations and calculations without altering the underlying database schema.
I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.