diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateConfiguration.java index 2043304293ab..bb7960e5769a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,14 @@ import javax.sql.DataSource; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.SQLExceptionTranslator; /** * Configuration for {@link JdbcTemplateConfiguration}. @@ -36,7 +38,8 @@ class JdbcTemplateConfiguration { @Bean @Primary - JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) { + JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties, + ObjectProvider sqlExceptionTranslator) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); JdbcProperties.Template template = properties.getTemplate(); jdbcTemplate.setFetchSize(template.getFetchSize()); @@ -44,6 +47,7 @@ JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) { if (template.getQueryTimeout() != null) { jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds()); } + sqlExceptionTranslator.ifUnique(jdbcTemplate::setExceptionTranslator); return jdbcTemplate; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java index fe8b19db079a..10972e2fd032 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java @@ -53,6 +53,7 @@ import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.jndi.JndiLocatorDelegate; import org.springframework.orm.hibernate5.SpringBeanContainer; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; @@ -95,6 +96,8 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { private final DataSourcePoolMetadataProvider poolMetadataProvider; + private final ObjectProvider sqlExceptionTranslator; + private final List hibernatePropertiesCustomizers; HibernateJpaConfiguration(DataSource dataSource, JpaProperties jpaProperties, @@ -104,11 +107,13 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { ObjectProvider providers, ObjectProvider physicalNamingStrategy, ObjectProvider implicitNamingStrategy, - ObjectProvider hibernatePropertiesCustomizers) { + ObjectProvider hibernatePropertiesCustomizers, + ObjectProvider sqlExceptionTranslator) { super(dataSource, jpaProperties, jtaTransactionManager); this.hibernateProperties = hibernateProperties; this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider(providers); this.poolMetadataProvider = new CompositeDataSourcePoolMetadataProvider(metadataProviders.getIfAvailable()); + this.sqlExceptionTranslator = sqlExceptionTranslator; this.hibernatePropertiesCustomizers = determineHibernatePropertiesCustomizers( physicalNamingStrategy.getIfAvailable(), implicitNamingStrategy.getIfAvailable(), beanFactory, hibernatePropertiesCustomizers.orderedStream().toList()); @@ -134,7 +139,9 @@ private List determineHibernatePropertiesCustomiz @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { - return new HibernateJpaVendorAdapter(); + HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); + this.sqlExceptionTranslator.ifUnique(adapter.getJpaDialect()::setJdbcExceptionTranslator); + return adapter; } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java index 7154f618dd49..db199d3dc322 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -204,6 +206,31 @@ void testDependencyToLiquibaseWithJdbcTemplateMixed() { }); } + @Test + void shouldConfigureSQLExceptionTranslatorIfPresent() { + SQLStateSQLExceptionTranslator sqlExceptionTranslator = new SQLStateSQLExceptionTranslator(); + this.contextRunner.withBean(SQLExceptionTranslator.class, () -> sqlExceptionTranslator).run((context) -> { + assertThat(context).hasSingleBean(JdbcTemplate.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + assertThat(jdbcTemplate.getExceptionTranslator()).isSameAs(sqlExceptionTranslator); + }); + } + + @Test + void shouldNotConfigureSQLExceptionTranslatorIfNotUnique() { + SQLStateSQLExceptionTranslator sqlExceptionTranslator1 = new SQLStateSQLExceptionTranslator(); + SQLStateSQLExceptionTranslator sqlExceptionTranslator2 = new SQLStateSQLExceptionTranslator(); + this.contextRunner + .withBean("sqlExceptionTranslator1", SQLExceptionTranslator.class, () -> sqlExceptionTranslator1) + .withBean("sqlExceptionTranslator2", SQLExceptionTranslator.class, () -> sqlExceptionTranslator2) + .run((context) -> { + assertThat(context).hasSingleBean(JdbcTemplate.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + assertThat(jdbcTemplate.getExceptionTranslator()).isNotSameAs(sqlExceptionTranslator1) + .isNotSameAs(sqlExceptionTranslator2); + }); + } + @Configuration(proxyBeanMethods = false) static class CustomConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 3d0d625c33b5..0014db55d834 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -78,6 +78,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -169,6 +171,24 @@ void hibernateDialectIsNotSetByDefault() { (adapter) -> assertThat(adapter.getJpaPropertyMap()).doesNotContainKeys("hibernate.dialect"))); } + @Test + void shouldConfigureHibernateJpaDialectWithSqlExceptionTranslatorIfPresent() { + SQLStateSQLExceptionTranslator sqlExceptionTranslator = new SQLStateSQLExceptionTranslator(); + contextRunner().withBean(SQLStateSQLExceptionTranslator.class, () -> sqlExceptionTranslator) + .run(assertJpaVendorAdapter((adapter) -> assertThat(adapter.getJpaDialect()) + .hasFieldOrPropertyWithValue("jdbcExceptionTranslator", sqlExceptionTranslator))); + } + + @Test + void shouldNotConfigureHibernateJpaDialectWithSqlExceptionTranslatorIfNotUnique() { + SQLStateSQLExceptionTranslator sqlExceptionTranslator1 = new SQLStateSQLExceptionTranslator(); + SQLStateSQLExceptionTranslator sqlExceptionTranslator2 = new SQLStateSQLExceptionTranslator(); + contextRunner().withBean("sqlExceptionTranslator1", SQLExceptionTranslator.class, () -> sqlExceptionTranslator1) + .withBean("sqlExceptionTranslator2", SQLExceptionTranslator.class, () -> sqlExceptionTranslator2) + .run(assertJpaVendorAdapter((adapter) -> assertThat(adapter.getJpaDialect()) + .hasFieldOrPropertyWithValue("jdbcExceptionTranslator", null))); + } + @Test void hibernateDialectIsSetWhenDatabaseIsSet() { contextRunner().withPropertyValues("spring.jpa.database=H2")