Skip to content

Commit 7247f5e

Browse files
committed
Auto-configure Hibernate with a JsonFormatMapper
Signed-off-by: Dmytro Nosan <[email protected]>
1 parent a49719d commit 7247f5e

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,8 +22,10 @@
2222
import org.springframework.boot.autoconfigure.AutoConfiguration;
2323
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
25+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
2526
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
2627
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration;
2729
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
2830
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizationAutoConfiguration;
2931
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -40,7 +42,8 @@
4042
* @since 1.0.0
4143
*/
4244
@AutoConfiguration(
43-
after = { DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class },
45+
after = { DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class,
46+
JacksonAutoConfiguration.class, JsonbAutoConfiguration.class },
4447
before = { TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
4548
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
4649
@EnableConfigurationProperties(JpaProperties.class)

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java

+39
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@
2727

2828
import javax.sql.DataSource;
2929

30+
import com.fasterxml.jackson.databind.ObjectMapper;
31+
import jakarta.json.bind.Jsonb;
3032
import org.apache.commons.logging.Log;
3133
import org.apache.commons.logging.LogFactory;
3234
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
3335
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
3436
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
37+
import org.hibernate.cfg.AvailableSettings;
3538
import org.hibernate.cfg.ManagedBeanSettings;
39+
import org.hibernate.type.format.jackson.JacksonJsonFormatMapper;
40+
import org.hibernate.type.format.jakartajson.JsonBJsonFormatMapper;
3641

3742
import org.springframework.aot.hint.MemberCategory;
3843
import org.springframework.aot.hint.RuntimeHints;
@@ -42,6 +47,8 @@
4247
import org.springframework.aot.hint.TypeReference;
4348
import org.springframework.beans.factory.ObjectProvider;
4449
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
50+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
51+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4552
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
4653
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration.HibernateRuntimeHints;
4754
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -51,8 +58,10 @@
5158
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
5259
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
5360
import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform;
61+
import org.springframework.context.annotation.Bean;
5462
import org.springframework.context.annotation.Configuration;
5563
import org.springframework.context.annotation.ImportRuntimeHints;
64+
import org.springframework.core.annotation.Order;
5665
import org.springframework.jdbc.support.SQLExceptionTranslator;
5766
import org.springframework.jndi.JndiLocatorDelegate;
5867
import org.springframework.orm.hibernate5.SpringBeanContainer;
@@ -233,6 +242,36 @@ private Object getNoJtaPlatformManager() {
233242
"No available JtaPlatform candidates amongst " + Arrays.toString(NO_JTA_PLATFORM_CLASSES));
234243
}
235244

245+
@ConditionalOnClass({ ObjectMapper.class, JacksonJsonFormatMapper.class })
246+
@ConditionalOnSingleCandidate(ObjectMapper.class)
247+
@Configuration(proxyBeanMethods = false)
248+
@ConditionalOnProperty(name = "spring.jpa.hibernate.auto-configure.json-format-mapper", havingValue = "jackson")
249+
static class HibernateJacksonJsonFormatMapperConfiguration {
250+
251+
@Bean
252+
@Order(0)
253+
HibernatePropertiesCustomizer jacksonJsonFormatMapperHibernatePropertiesCustomizer(ObjectMapper objectMapper) {
254+
return (properties) -> properties.put(AvailableSettings.JSON_FORMAT_MAPPER,
255+
new JacksonJsonFormatMapper(objectMapper));
256+
}
257+
258+
}
259+
260+
@ConditionalOnClass({ Jsonb.class, JsonBJsonFormatMapper.class })
261+
@ConditionalOnSingleCandidate(Jsonb.class)
262+
@Configuration(proxyBeanMethods = false)
263+
@ConditionalOnProperty(name = "spring.jpa.hibernate.auto-configure.json-format-mapper", havingValue = "jsonb")
264+
static class HibernateJsonbJsonFormatMapperConfiguration {
265+
266+
@Bean
267+
@Order(0)
268+
HibernatePropertiesCustomizer jsonbJsonFormatMapperHibernatePropertiesCustomizer(Jsonb jsonb) {
269+
return (properties) -> properties.putIfAbsent(AvailableSettings.JSON_FORMAT_MAPPER,
270+
new JsonBJsonFormatMapper(jsonb));
271+
}
272+
273+
}
274+
236275
private static class NamingStrategiesHibernatePropertiesCustomizer implements HibernatePropertiesCustomizer {
237276

238277
private final PhysicalNamingStrategy physicalNamingStrategy;

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+25
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,12 @@
16411641
"level": "error"
16421642
}
16431643
},
1644+
{
1645+
"name": "spring.jpa.hibernate.auto-configure.json-format-mapper",
1646+
"type": "java.lang.String",
1647+
"description": "Preferred Hibernate JSON format mapper to use.",
1648+
"defaultValue": "none"
1649+
},
16441650
{
16451651
"name": "spring.jpa.hibernate.use-new-id-generator-mappings",
16461652
"type": "java.lang.Boolean",
@@ -3109,6 +3115,25 @@
31093115
}
31103116
]
31113117
},
3118+
{
3119+
"name": "spring.jpa.hibernate.auto-configure.json-format-mapper",
3120+
"values": [
3121+
{
3122+
"value": "jackson"
3123+
},
3124+
{
3125+
"value": "jsonb"
3126+
},
3127+
{
3128+
"value": "none"
3129+
}
3130+
],
3131+
"providers": [
3132+
{
3133+
"name": "any"
3134+
}
3135+
]
3136+
},
31123137
{
31133138
"name": "spring.jpa.hibernate.ddl-auto",
31143139
"values": [

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java

+64
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030

3131
import javax.sql.DataSource;
3232

33+
import com.fasterxml.jackson.databind.ObjectMapper;
3334
import com.zaxxer.hikari.HikariDataSource;
35+
import jakarta.json.bind.Jsonb;
36+
import jakarta.json.bind.JsonbBuilder;
3437
import jakarta.persistence.EntityManager;
3538
import jakarta.persistence.EntityManagerFactory;
3639
import jakarta.transaction.Synchronization;
@@ -40,13 +43,16 @@
4043
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
4144
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
4245
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
46+
import org.hibernate.cfg.AvailableSettings;
4347
import org.hibernate.cfg.ManagedBeanSettings;
4448
import org.hibernate.cfg.SchemaToolingSettings;
4549
import org.hibernate.dialect.H2Dialect;
4650
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
4751
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
4852
import org.hibernate.internal.SessionFactoryImpl;
4953
import org.hibernate.jpa.HibernatePersistenceProvider;
54+
import org.hibernate.type.format.jackson.JacksonJsonFormatMapper;
55+
import org.hibernate.type.format.jakartajson.JsonBJsonFormatMapper;
5056
import org.junit.jupiter.api.Disabled;
5157
import org.junit.jupiter.api.Test;
5258

@@ -59,8 +65,10 @@
5965
import org.springframework.boot.autoconfigure.AutoConfigurations;
6066
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
6167
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
68+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
6269
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
6370
import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration;
71+
import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration;
6472
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
6573
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurationTests.JpaUsingApplicationListenerConfiguration.EventCapturingApplicationListener;
6674
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration.HibernateRuntimeHints;
@@ -387,6 +395,62 @@ void hibernatePropertiesCustomizerTakesPrecedenceOverStrategyInstancesAndNamingS
387395
});
388396
}
389397

398+
@Test
399+
void jacksonJsonFormatMapperHibernatePropertiesCustomizerUsedAutoConfiguredObjectMapper() {
400+
contextRunner().withPropertyValues("spring.jpa.hibernate.auto-configure.json-format-mapper=jackson")
401+
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class))
402+
.run(vendorProperties(
403+
(vendorProperties) -> assertThat(vendorProperties.get(AvailableSettings.JSON_FORMAT_MAPPER))
404+
.isInstanceOf(JacksonJsonFormatMapper.class)));
405+
}
406+
407+
@Test
408+
void jacksonJsonFormatMapperHibernatePropertiesCustomizerShouldNotBeRegisteredIfNoSingleCandidate() {
409+
contextRunner().withPropertyValues("spring.jpa.hibernate.auto-configure.json-format-mapper=jackson")
410+
.withBean("objectMapper1", ObjectMapper.class, ObjectMapper::new)
411+
.withBean("objectMapper2", ObjectMapper.class, ObjectMapper::new)
412+
.run(vendorProperties(
413+
(vendorProperties) -> assertThat(vendorProperties.get(AvailableSettings.JSON_FORMAT_MAPPER))
414+
.isNull()));
415+
}
416+
417+
@Test
418+
void jacksonJsonFormatMapperHibernatePropertiesCustomizerShouldNotBeRegistered() {
419+
contextRunner().withPropertyValues("spring.jpa.hibernate.auto-configure.json-format-mapper=none")
420+
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class))
421+
.run(vendorProperties(
422+
(vendorProperties) -> assertThat(vendorProperties.get(AvailableSettings.JSON_FORMAT_MAPPER))
423+
.isNull()));
424+
}
425+
426+
@Test
427+
void jsonbJsonFormatMapperHibernatePropertiesCustomizerUsedAutoConfiguredObjectMapper() {
428+
contextRunner().withPropertyValues("spring.jpa.hibernate.auto-configure.json-format-mapper=jsonb")
429+
.withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class))
430+
.run(vendorProperties(
431+
(vendorProperties) -> assertThat(vendorProperties.get(AvailableSettings.JSON_FORMAT_MAPPER))
432+
.isInstanceOf(JsonBJsonFormatMapper.class)));
433+
}
434+
435+
@Test
436+
void jsonbJsonFormatMapperHibernatePropertiesCustomizerShouldNotBeRegisteredIfNoSingleCandidate() {
437+
contextRunner().withPropertyValues("spring.jpa.hibernate.auto-configure.json-format-mapper=jsonb")
438+
.withBean("jsonb1", Jsonb.class, JsonbBuilder::create)
439+
.withBean("jsonb2", Jsonb.class, JsonbBuilder::create)
440+
.run(vendorProperties(
441+
(vendorProperties) -> assertThat(vendorProperties.get(AvailableSettings.JSON_FORMAT_MAPPER))
442+
.isNull()));
443+
}
444+
445+
@Test
446+
void jsonbJsonFormatMapperHibernatePropertiesCustomizerShouldNotBeRegistered() {
447+
contextRunner().withPropertyValues("spring.jpa.hibernate.auto-configure.json-format-mapper=none")
448+
.withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class))
449+
.run(vendorProperties(
450+
(vendorProperties) -> assertThat(vendorProperties.get(AvailableSettings.JSON_FORMAT_MAPPER))
451+
.isNull()));
452+
}
453+
390454
@Test
391455
void eventListenerCanBeRegisteredAsBeans() {
392456
contextRunner().withUserConfiguration(TestInitializedJpaConfiguration.class)

0 commit comments

Comments
 (0)