Use Jackson Objectmapper configured by Spring boot in Hibernate

8.1k Views Asked by At

I want to configure Hibernate to use Jackson's Objectmapper created by Spring to map between json and entities. In the project I'm working on I already configured Jooq to use the Spring's ObjectMapper but I'm having trouble how to configure Hibernate to use it. The goal in the end is that both Jooq and Hibernate would use the same ObjectMapper.

I checked this article by Vlad. Unfortunately all the tips given in the article don't work for the project I'm working on.

Here's an example configuration I tried

@Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer {
    //Autowire Objectmapper created by Spring
    @Autowired
    ObjectMapper objectMapper;

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        ObjectMapperSupplier objectMapperSupplier = () -> objectMapper;
        // Below config doesn't work since Hibernate types creates it's own mapper
        hibernateProperties.put("hibernate.types.jackson.object.mapper", objectMapperSupplier);
}

Also tried the same approach by adding the Objectmapper to hibernate-types.properties.

#Used by Hibernate but cannot get reference of Spring managed ObjectMapper since this is class is called outside of Spring's context.
hibernate.types.jackson.object.mapper=path.to.ObjectMapperSupplier

Another approach I used but it fails with a NullpointerException when converting from JSON to an entity in JsonTypeDescriptor class.

@Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer{

    @Autowired
    ObjectMapper objectMapper;

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        // Underlying implementation needs some JavaType or propertyClass, otherwise when converting 
        // from JSON we get a nullpointer.
        var jsonBinaryType = new JsonBinaryType(objectMapper);
        hibernateProperties.put("hibernate.type_contributors", (TypeContributorList) () -> 
        Collections.singletonList((typeContributions, serviceRegistry) -> 
                      typeContributions.contributeType(jsonBinaryType)));
}

Below is the type declaration for entity super class.

// This makes Hibernate types create it's own mapper.
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
@MappedSuperclass
public abstract class Entity{

}

So, are there any possible solutions how I can hook up Spring managed ObjectMapper to Hibernate?

6

There are 6 best solutions below

2
Gerard de Leeuw On

I finally figured this out, but it is kind of an creative solution...

TLDR: I have a bean that stores the Spring-configured objectMapper in a static field. A BeanFactoryPostProcessor ensures that this bean is initialized before Hibernate (types) tries to load / get the ObjectMapper.

hibernate.properties

hibernate.types.jackson.object.mapper=com.github.lion7.example.HibernateObjectMapperSupplier

HibernateObjectMapperSupplier.kt

package com.github.lion7.example

import com.fasterxml.jackson.databind.ObjectMapper
import com.vladmihalcea.hibernate.type.util.ObjectMapperSupplier
import org.springframework.beans.factory.config.BeanFactoryPostProcessor
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.stereotype.Component

class HibernateObjectMapperSupplier : ObjectMapperSupplier {
    override fun get(): ObjectMapper =
        ObjectMapperHolder.objectMapper
}

@Component
class ObjectMapperHolder(objectMapper: ObjectMapper) {

    companion object {
        lateinit var objectMapper: ObjectMapper
    }

    init {
        Companion.objectMapper = objectMapper
    }

}

@Component
class ObjectMapperDependencyFixer : BeanFactoryPostProcessor {
    override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
        val beanDefinition = beanFactory.getBeanDefinition("entityManagerFactory")
        val oldDependsOn = beanDefinition.dependsOn ?: emptyArray()
        val newDependsOn = oldDependsOn + "objectMapperHolder"
        beanDefinition.setDependsOn(*newDependsOn)
    }
}

Same code as gist: https://gist.github.com/lion7/c8006b69a309e38183deb69124b888b5

0
OrangeDog On

A Java implementation.

@Component
public class HibernateObjectMapper implements Supplier<ObjectMapper> {

    private static ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        HibernateObjectMapper.objectMapper = objectMapper;
    }

    @Override
    public ObjectMapper get() {
        return objectMapper;
    }

}

If you define your own JPA beans, simply add @DependsOn("hibernateObjectMapper") to their config. Otherwise you need a BeanPostProcessor to add the dependency to the autoconfigured bean:

@Component
class HibernateBeanDependencyProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
        BeanDefinition beanDefinition = factory.getBeanDefinition("entityManagerFactory");
        String[] dependsOn = beanDefinition.getDependsOn();
        dependsOn = dependsOn == null ? new String[]{} : dependsOn;
        String[] newDependsOn = new String[dependsOn.length + 1];
        System.arraycopy(dependsOn, 0, newDependsOn, 1, dependsOn.length);
        newDependsOn[0] = "hibernateObjectMapper";
        beanDefinition.setDependsOn(newDependsOn);
    }
}

As for the property, hibernate.types.* don't work when set programmatically. The library looks directly in the hibernate.properties, hibernate-types.properties, and application.properties files.

2
Feng68 On
System.getProperties().put(
    Configuration.PropertyKey.JACKSON_OBJECT_MAPPER.getKey(),
    MyObjectMapperSupplier.class.getName()
);
0
pustypawel On

I think that I've found solution to do it programmatically (without magic with fixing dependency graph). HibernateConfiguration.kt

@Configuration(proxyBeanMethods = false)
class HibernateConfiguration {

    @Bean
    fun hibernatePropertiesCustomizer(
        objectMapper: ObjectMapper // Thanks to that Spring can create correct dependency graph
    ): HibernatePropertiesCustomizer =
        HibernatePropertiesCustomizer { hibernateProperties ->
            HibernateObjectMapperSupplier.objectMapper = objectMapper
            hibernateProperties["hibernate.types.jackson.object.mapper"] = HibernateObjectMapperSupplier::class.qualifiedName
        }
}

HibernateObjectMapperSupplier.kt

class HibernateObjectMapperSupplier : Supplier<ObjectMapper> {

    override fun get(): ObjectMapper {
        return objectMapper
    }

    companion object {
        lateinit var objectMapper: ObjectMapper
    }
}
2
akuma8 On

For those working on Spring Boot 3 and Hibernate 6, check this solution from Andy Wilkinson:

    @Bean
    public HibernatePropertiesCustomizer jsonFormatMapperCustomizer(ObjectMapper objectMapper) {
        return properties -> properties
                .put(AvailableSettings.JSON_FORMAT_MAPPER, new JacksonJsonFormatMapper(objectMapper));
    }
0
selalerer On

The thing that worked best for me is to configure the already existing ObjectMapper used by Hypersistence:

   import io.hypersistence.utils.hibernate.type.util.ObjectMapperWrapper;
   
   ObjectMapperWrapper.INSTANCE.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);