Use SQLExceptionTranslator bean if defined

This commit improves the auto-configuration of JdbcTemplate and
HibernateJpaDialect so that an SQLExceptionTranslator bean is used if
it is available.

See gh-43511
This commit is contained in:
Dmytro Nosan 2024-12-14 14:40:44 +02:00 committed by Stéphane Nicoll
parent 276b888064
commit 86ef089d16
4 changed files with 62 additions and 4 deletions

View File

@ -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 @@ package org.springframework.boot.autoconfigure.jdbc;
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> sqlExceptionTranslator) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
JdbcProperties.Template template = properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
@ -44,6 +47,7 @@ class JdbcTemplateConfiguration {
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
}
sqlExceptionTranslator.ifUnique(jdbcTemplate::setExceptionTranslator);
return jdbcTemplate;
}

View File

@ -53,6 +53,7 @@ import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
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> sqlExceptionTranslator;
private final List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers;
HibernateJpaConfiguration(DataSource dataSource, JpaProperties jpaProperties,
@ -104,11 +107,13 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration {
ObjectProvider<SchemaManagementProvider> providers,
ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy,
ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy,
ObjectProvider<SQLExceptionTranslator> sqlExceptionTranslator,
ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
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 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration {
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
this.sqlExceptionTranslator.ifUnique(adapter.getJpaDialect()::setJdbcExceptionTranslator);
return adapter;
}
@Override

View File

@ -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.JdbcOperations;
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 @@ class JdbcTemplateAutoConfigurationTests {
});
}
@Test
void shouldConfigureJdbcTemplateWithSQLExceptionTranslatorIfPresent() {
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 shouldNotConfigureJdbcTemplateWithSQLExceptionTranslatorIfNotUnique() {
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 {

View File

@ -78,6 +78,8 @@ import org.springframework.context.annotation.Bean;
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 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
(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")