Using Custom Converter In Hibernate

Overview

Imagine this scenario, you have a legacy database that store weather data. The previous developer, due to various reason, store the temperature in the following format:

idlocationtemperaturecapture_date
1Hanoi30.50 C2023-07-31 22:10:30.000
2New York95.20 F2023-07-31 22:10:30.000

As you can see, the temperature stored in a sub-optimal way with the value and the unit in the same column. As the business requires, you need to extract the temperature to a value type called Temperature. The POJO is something like this:

enum Scale {
 CELSIUS,
  FAHRENHEIT
}

public class Temperature {
  private double temperature;
  private Scale scale;
}

without changing current database schema.

In cases like this, using a custom converter makes sense.

Creating a Hibernate Converter For Temperature

Creating a converter in Hibernate is quite straightforward. You need to override the methods that convert the database value to POJO and vice versa.

Here is the implementation of the weather data case:

@Converter(autoApply = true)
class TemperatureConverter implements AttributeConverter<Temperature, String> {

    @Override
    public String convertToDatabaseColumn(Temperature temperature) {
        return temperature.getTemperature() + " " + temperature.getScale();
    }

    @Override
    public Temperature convertToEntityAttribute(String s) {
        String[] parts = s.split(" ");
        var valuePart = parts[0];
        var scalePart = parts[1];
        Temperature temperature = new Temperature();
        temperature.setTemperature(Double.parseDouble(valuePart));
        temperature.setScale(scalePart.equals("F") ? Scale.FAHRENHEIT : Scale.CELSIUS);
        return temperature;
    }
}

Now I have the converter ready, let’s see how to configure the field temperature in the entity:

@Entity
@Table(name = "legacy_weather_data")
public class LegacyWeatherData {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String location;

    @Convert(converter = TemperatureConverter.class)
    @Column(name = "temperature")
    private Temperature temperature;
  
    @CreationTimestamp
    private Date captureDate;

    public LegacyWeatherData() {
    }
}

As you can see, the field temperature is annotated with @Convert and @Column.

Let’s try inserting and reading some data:

public class LegacyWeatherDemo {

    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 legacy data
        var temperature = new Temperature(30, Scale.CELSIUS);
        var legacyWeather = new LegacyWeatherData("Hanoi", temperature);

        try (var session = factory.openSession()) {
            var tx = session.beginTransaction();
            session.persist(legacyWeather);
            tx.commit();
        }

        var hanoi = findByLocation("Hanoi");
        System.out.println("Hanoi temperature: " + hanoi.getTemperature().getTemperature());
    }

    private static LegacyWeatherData findByLocation(String location) {
        try (var session = factory.openSession()) {
            return session.createQuery("from LegacyWeatherData where location = :location", LegacyWeatherData.class)
                    .setParameter("location", location)
                    .getSingleResult();
        }
    }
}

From the application’s console, the result is expected:

Reading data from database using converter

If I look into the database, the data is stored in one field as concatenated string:

Data stored as concatenated string in the database

Conclusion

In this post, I’ve shown you how to create a custom converter to convert data back and forth between the database and your application.

Leave a Comment