How to customize the Hypersistence Utils Jackson ObjectMapper
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
The Hypersistence Utils open-source project allows you to map JSON, and ARRAY when using JPA and Hibernate.
Since I launched this project, one of the most wanted features was to add support for customizing the underlying Jackson ObjectMapper
, and since version 2.1.1
, this can be done either declaratively or programmatically.
In this article, you are going to see how to customize the ObjectMapper
when using the Hypersistence Utils project.
How to customize the Jackson ObjectMapper used by #Hibernate-Types - @vlad_mihalceahttps://t.co/nF1CcLVL7I pic.twitter.com/lBIMsw0Hh7
— Java (@java) March 10, 2018
Declarative configuration
The easiest way to achieve this goal is to set the hypersistence.utils.jackson.object.mapper
configuration property in the hibernate.properties
file:
hypersistence.utils.jackson.object.mapper=com.vladmihalcea.hibernate.type.json.loader.CustomObjectMapperSupplier
This property takes the fully-qualified class name of an implementation of the ObjectMapperSupplier
interface:
What’s nice about customizing the Hypersistence Utils project is that you can use either the Hibernate-specific hibernate.properties
or you can provide a hypersistence-utils.properties
file if you cannot alter hibernate.properties
.
You can even supply a different Java properties file via the hypersistence-utils.properties.path
System property.
The CustomObjectMapperSupplier
can look as follows:
public class CustomObjectMapperSupplier implements ObjectMapperSupplier { @Override public ObjectMapper get() { ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); objectMapper.setTimeZone( TimeZone.getTimeZone("GMT") ); SimpleModule simpleModule = new SimpleModule( "SimpleModule", new Version(1, 0, 0, null, null, null) ); simpleModule.addSerializer(new MoneySerializer()); objectMapper.registerModule(simpleModule); return objectMapper; } }
Here, we are using a custom ObjectMapper
that uses our own MoneySerializer
for handling BigDecimal
Java Object types:
public class MoneySerializer extends JsonSerializer<BigDecimal> { @Override public void serialize( BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException { jsonGenerator.writeString( value.setScale(2, BigDecimal.ROUND_HALF_UP).toString() ); } @Override public Class<BigDecimal> handledType() { return BigDecimal.class; } }
So, if you are persisting the following Book
entity which has a JSON properties
attribute:
Book book = new Book(); book.setIsbn("978-9730228236"); book.setProperties( JacksonUtil.toJsonNode(""" { "title": "High-Performance Java Persistence", "author": "Vlad Mihalcea", "publisher": "Amazon", "price": 44.991234567 } """ ) ); entityManager.persist(book);
When fetching the Book
entity, we can see that the BigDecimal
takes only two decimals:
Book book = entityManager .unwrap(Session.class) .bySimpleNaturalId(Book.class) .load("978-9730228236"); assertEquals( "44.99", book.getProperties().get("price").asText() );
Programmatic configuration
For even more flexibility, you can use the programmatic configuration.
Assuming we have the following Object type that we want to serialize as JSON:
public class Location implements Serializable { private String country; private String city; private BigDecimal reference; //Getters and setters omitted for brevity }
And we have an Event
entity that maps the Location
Java Object to a jsonb
PostgreSQL column type:
@Entity(name = "Event") @Table(name = "event") public class Event { @Id private Long id; @Type(type = "location") @Column(columnDefinition = "jsonb") private Location location; //Getters and setters omitted for brevity }
We can use a custom ObjectMapper
as follows:
JsonType jsonType = new JsonType( new CustomObjectMapperSupplier().get(), Location.class ); properties.put("hibernate.type_contributors", (TypeContributorList) () -> Collections.singletonList( (typeContributions, serviceRegistry) -> typeContributions.contributeType( jsonType, "location" ) ) );
The hibernate.type_contributors
configuration property allows you to register custom Hibernate Types, and we can use this feature to provide our own custom ObjectMapper
to the JsonType
provided by the Hypersistence Utils project.
So, if we persist the following Event
entity:
Location location = new Location(); location.setCountry("Romania"); location.setCity("Cluj-Napoca"); location.setReference( BigDecimal.valueOf(2.25262562526626D) ); Event event = new Event(); event.setId(1L); event.setLocation(location); entityManager.persist(event);
When fetching the Event
entity, we can see that the BigDecimal
takes only two decimals:
Event event = entityManager.find(Event.class, 1L); assertEquals("2.25", event.getLocation().getReference().toString());
Cool, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Not only that you can customize the ObjectMapper
when using the Hypersistence Utils project, but the new configuration mechanism will allow for customizing other behaviors as well.
So, stay tuned for more!
