From 083113d8a4ce1220ee4ee7e21395830e34ea8450 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Jun 2022 14:00:56 +0200 Subject: [PATCH] Use SQLExceptionSubclassTranslator by default (avoiding sql-error-codes.xml) SQLErrorCodeSQLExceptionTranslator kicks in for user-provided sql-error-codes.xml files. It will still pick up Spring's legacy default error code mappings as well but only when triggered by a (potentially empty) user-provided file in the root of the classpath. Closes gh-28216 --- .../jdbc/support/JdbcAccessor.java | 23 ++++++------------- .../jdbc/support/JdbcTransactionManager.java | 22 +++++++----------- .../SQLErrorCodeSQLExceptionTranslator.java | 11 +++++++++ .../jdbc/support/SQLErrorCodesFactory.java | 14 ++++++++++- .../jdbc/core/JdbcTemplateTests.java | 13 +++++------ 5 files changed, 45 insertions(+), 38 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcAccessor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcAccessor.java index 070ada3a306..8b4bd9c304f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcAccessor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcAccessor.java @@ -22,7 +22,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.SpringProperties; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -41,13 +40,6 @@ import org.springframework.util.Assert; */ public abstract class JdbcAccessor implements InitializingBean { - /** - * Boolean flag controlled by a {@code spring.xml.ignore} system property that instructs Spring to - * ignore XML, i.e. to not initialize the XML-related infrastructure. - *

The default is "false". - */ - private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore"); - /** Logger available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); @@ -96,9 +88,12 @@ public abstract class JdbcAccessor implements InitializingBean { * @see java.sql.DatabaseMetaData#getDatabaseProductName() */ public void setDatabaseProductName(String dbName) { - if (!shouldIgnoreXml) { + if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) { this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName); } + else { + this.exceptionTranslator = new SQLExceptionSubclassTranslator(); + } } /** @@ -128,15 +123,11 @@ public abstract class JdbcAccessor implements InitializingBean { synchronized (this) { exceptionTranslator = this.exceptionTranslator; if (exceptionTranslator == null) { - DataSource dataSource = getDataSource(); - if (shouldIgnoreXml) { - exceptionTranslator = new SQLExceptionSubclassTranslator(); - } - else if (dataSource != null) { - exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); + if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) { + exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(obtainDataSource()); } else { - exceptionTranslator = new SQLStateSQLExceptionTranslator(); + exceptionTranslator = new SQLExceptionSubclassTranslator(); } this.exceptionTranslator = exceptionTranslator; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcTransactionManager.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcTransactionManager.java index b1cab0b75d4..bda750f4722 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcTransactionManager.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -20,7 +20,6 @@ import java.sql.SQLException; import javax.sql.DataSource; -import org.springframework.core.SpringProperties; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.lang.Nullable; @@ -53,14 +52,6 @@ import org.springframework.lang.Nullable; @SuppressWarnings("serial") public class JdbcTransactionManager extends DataSourceTransactionManager { - /** - * Boolean flag controlled by a {@code spring.xml.ignore} system property that instructs Spring to - * ignore XML, i.e. to not initialize the XML-related infrastructure. - *

The default is "false". - */ - private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore"); - - @Nullable private volatile SQLExceptionTranslator exceptionTranslator; @@ -97,9 +88,12 @@ public class JdbcTransactionManager extends DataSourceTransactionManager { * @see java.sql.DatabaseMetaData#getDatabaseProductName() */ public void setDatabaseProductName(String dbName) { - if (!shouldIgnoreXml) { + if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) { this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName); } + else { + this.exceptionTranslator = new SQLExceptionSubclassTranslator(); + } } /** @@ -128,11 +122,11 @@ public class JdbcTransactionManager extends DataSourceTransactionManager { synchronized (this) { exceptionTranslator = this.exceptionTranslator; if (exceptionTranslator == null) { - if (shouldIgnoreXml) { - exceptionTranslator = new SQLExceptionSubclassTranslator(); + if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) { + exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(obtainDataSource()); } else { - exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(obtainDataSource()); + exceptionTranslator = new SQLExceptionSubclassTranslator(); } this.exceptionTranslator = exceptionTranslator; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java index e28af0160b3..609f16325ff 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java @@ -23,6 +23,7 @@ import java.util.Arrays; import javax.sql.DataSource; +import org.springframework.core.io.ClassPathResource; import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.CannotSerializeTransactionException; import org.springframework.dao.DataAccessException; @@ -417,4 +418,14 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep } } + + /** + * Check whether there is a user-provided `sql-error-codes.xml` file + * in the root of the classpath. + */ + static boolean hasUserProvidedErrorCodesFile() { + return new ClassPathResource(SQLErrorCodesFactory.SQL_ERROR_CODE_OVERRIDE_PATH, + SQLErrorCodesFactory.class.getClassLoader()).exists(); + } + } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java index 2b78fa740f0..00869ece51b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -28,6 +28,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.SpringProperties; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.lang.Nullable; @@ -66,6 +67,13 @@ public class SQLErrorCodesFactory { private static final Log logger = LogFactory.getLog(SQLErrorCodesFactory.class); + /** + * Boolean flag controlled by a {@code spring.xml.ignore} system property that instructs Spring to + * ignore XML, i.e. to not initialize the XML-related infrastructure. + *

The default is "false". + */ + private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore"); + /** * Keep track of a single instance so we can return it to classes that request it. */ @@ -101,6 +109,10 @@ public class SQLErrorCodesFactory { * @see #loadResource(String) */ protected SQLErrorCodesFactory() { + if (shouldIgnoreXml) { + throw new UnsupportedOperationException("XML support disabled"); + } + Map errorCodes; try { diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java index 65c59fb321d..85fe6fe6459 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java @@ -944,6 +944,8 @@ public class JdbcTemplateTests { mockDatabaseMetaData(false); given(this.connection.createStatement()).willReturn(this.preparedStatement); + this.template.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator(this.dataSource)); + assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(() -> this.template.query(sql, (RowCallbackHandler) rs -> { throw sqlException; @@ -955,20 +957,17 @@ public class JdbcTemplateTests { } @Test - public void testSQLErrorCodeTranslationWithSpecifiedDbName() throws Exception { + public void testSQLErrorCodeTranslationWithSpecifiedDatabaseName() throws Exception { final SQLException sqlException = new SQLException("I have a known problem", "99999", 1054); final String sql = "SELECT ID FROM CUSTOMER"; given(this.resultSet.next()).willReturn(true); given(this.connection.createStatement()).willReturn(this.preparedStatement); - JdbcTemplate template = new JdbcTemplate(); - template.setDataSource(this.dataSource); - template.setDatabaseProductName("MySQL"); - template.afterPropertiesSet(); + this.template.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator("MySQL")); assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(() -> - template.query(sql, (RowCallbackHandler) rs -> { + this.template.query(sql, (RowCallbackHandler) rs -> { throw sqlException; })) .withCause(sqlException); @@ -983,7 +982,7 @@ public class JdbcTemplateTests { * to get the metadata */ @Test - public void testUseCustomSQLErrorCodeTranslator() throws Exception { + public void testUseCustomExceptionTranslator() throws Exception { // Bad SQL state final SQLException sqlException = new SQLException("I have a known problem", "07000", 1054); final String sql = "SELECT ID FROM CUSTOMER";