Browse Source

Merge branch '6.0.x'

# Conflicts:
#	spring-jdbc/src/main/java/org/springframework/jdbc/support/AbstractFallbackSQLExceptionTranslator.java
pull/28374/merge
Juergen Hoeller 1 year ago
parent
commit
fd17df91fd
  1. 6
      framework-docs/modules/ROOT/pages/integration/scheduling.adoc
  2. 9
      spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
  3. 36
      spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java
  4. 25
      spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcAccessor.java
  5. 36
      spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcTransactionManager.java
  6. 6
      spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java
  7. 19
      spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java

6
framework-docs/modules/ROOT/pages/integration/scheduling.adoc

@ -380,6 +380,12 @@ Notice that the methods to be scheduled must have void returns and must not acce
arguments. If the method needs to interact with other objects from the application arguments. If the method needs to interact with other objects from the application
context, those would typically have been provided through dependency injection. context, those would typically have been provided through dependency injection.
`@Scheduled` can be used as a repeatable annotation. If several scheduled declarations
are found on the same method, each of them will be processed independently, with a
separate trigger firing for each of them. As a consequence, such co-located schedules
may overlap and execute multiple times in parallel or in immediate succession.
Please make sure that your specified cron expressions etc do not accidentally overlap.
[NOTE] [NOTE]
==== ====
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope. As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.

9
spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java

@ -29,8 +29,8 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/** /**
* Annotation that marks a method to be scheduled. Exactly one of the * Annotation that marks a method to be scheduled. Exactly one of the
* {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes must be * {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes
* specified. * must be specified.
* *
* <p>The annotated method must not accept arguments. It will typically have * <p>The annotated method must not accept arguments. It will typically have
* a {@code void} return type; if not, the returned value will be ignored * a {@code void} return type; if not, the returned value will be ignored
@ -56,7 +56,10 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* or {@link EnableScheduling @EnableScheduling} annotation. * or {@link EnableScheduling @EnableScheduling} annotation.
* *
* <p>This annotation can be used as a <em>{@linkplain Repeatable repeatable}</em> * <p>This annotation can be used as a <em>{@linkplain Repeatable repeatable}</em>
* annotation. * annotation. If several scheduled declarations are found on the same method,
* each of them will be processed independently, with a separate trigger firing
* for each of them. As a consequence, such co-located schedules may overlap
* and execute multiple times in parallel or in immediate succession.
* *
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em> with attribute overrides. * <em>composed annotations</em> with attribute overrides.

36
spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java

@ -354,7 +354,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
exporter.setBeans(beansToExport); exporter.setBeans(beansToExport);
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_NONE); exporter.setAutodetect(false);
// MBean has a bad ObjectName, so if said MBean is autodetected, an exception will be thrown... // MBean has a bad ObjectName, so if said MBean is autodetected, an exception will be thrown...
start(exporter); start(exporter);
} }
@ -524,18 +524,16 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void notRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception { void notRunningInBeanFactoryAndPassedBeanNameToExport() {
Map<String, Object> beans = Map.of(OBJECT_NAME, "beanName"); Map<String, Object> beans = Map.of(OBJECT_NAME, "beanName");
exporter.setBeans(beans); exporter.setBeans(beans);
assertThatExceptionOfType(MBeanExportException.class) assertThatExceptionOfType(MBeanExportException.class).isThrownBy(() -> start(exporter));
.isThrownBy(() -> start(exporter));
} }
@Test @Test
void notRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { void notRunningInBeanFactoryAndAutodetectionIsOn() {
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetect(true);
assertThatExceptionOfType(MBeanExportException.class) assertThatExceptionOfType(MBeanExportException.class).isThrownBy(() -> start(exporter));
.isThrownBy(() -> start(exporter));
} }
@Test // SPR-2158 @Test // SPR-2158
@ -556,7 +554,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test // SPR-3302 @Test // SPR-3302
void beanNameCanBeUsedInNotificationListenersMap() throws Exception { void beanNameCanBeUsedInNotificationListenersMap() {
String beanName = "charlesDexterWard"; String beanName = "charlesDexterWard";
BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class);
@ -576,7 +574,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
} }
@Test @Test
void wildcardCanBeUsedInNotificationListenersMap() throws Exception { void wildcardCanBeUsedInNotificationListenersMap() {
String beanName = "charlesDexterWard"; String beanName = "charlesDexterWard";
BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class);
@ -636,24 +634,23 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(firstBeanName, secondBeanName)); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(firstBeanName, secondBeanName));
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetect(true);
exporter.addExcludedBean(secondBeanName); exporter.addExcludedBean(secondBeanName);
start(exporter); start(exporter);
assertIsRegistered("Bean not autodetected in (AUTODETECT_ALL) mode", assertIsRegistered("Bean not autodetected", ObjectNameManager.getInstance(firstBeanName));
ObjectNameManager.getInstance(firstBeanName)); assertIsNotRegistered("Bean should have been excluded", ObjectNameManager.getInstance(secondBeanName));
assertIsNotRegistered("Bean should have been excluded",
ObjectNameManager.getInstance(secondBeanName));
} }
@Test @Test
void registerFactoryBean() throws MalformedObjectNameException { void registerFactoryBean() throws MalformedObjectNameException {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("spring:type=FactoryBean", new RootBeanDefinition(ProperSomethingFactoryBean.class)); factory.registerBeanDefinition("spring:type=FactoryBean",
new RootBeanDefinition(ProperSomethingFactoryBean.class));
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetect(true);
start(exporter); start(exporter);
assertIsRegistered("Non-null FactoryBean object registered", assertIsRegistered("Non-null FactoryBean object registered",
@ -663,11 +660,12 @@ public class MBeanExporterTests extends AbstractMBeanServerTests {
@Test @Test
void ignoreNullObjectFromFactoryBean() throws MalformedObjectNameException { void ignoreNullObjectFromFactoryBean() throws MalformedObjectNameException {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("spring:type=FactoryBean", new RootBeanDefinition(NullSomethingFactoryBean.class)); factory.registerBeanDefinition("spring:type=FactoryBean",
new RootBeanDefinition(NullSomethingFactoryBean.class));
exporter.setServer(getServer()); exporter.setServer(getServer());
exporter.setBeanFactory(factory); exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); exporter.setAutodetect(true);
start(exporter); start(exporter);
assertIsNotRegistered("Null FactoryBean object not registered", assertIsNotRegistered("Null FactoryBean object not registered",

25
spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcAccessor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -80,10 +80,11 @@ public abstract class JdbcAccessor implements InitializingBean {
} }
/** /**
* Specify the database product name for the DataSource that this accessor uses. * Specify the database product name for the {@code DataSource} that this accessor uses.
* This allows to initialize an SQLErrorCodeSQLExceptionTranslator without * This allows for initializing a {@link SQLErrorCodeSQLExceptionTranslator} without
* obtaining a Connection from the DataSource to get the meta-data. * obtaining a {@code Connection} from the {@code DataSource} to get the meta-data.
* @param dbName the database product name that identifies the error codes entry * @param dbName the database product name that identifies the error codes entry
* @see #setExceptionTranslator
* @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName * @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName
* @see java.sql.DatabaseMetaData#getDatabaseProductName() * @see java.sql.DatabaseMetaData#getDatabaseProductName()
*/ */
@ -98,22 +99,20 @@ public abstract class JdbcAccessor implements InitializingBean {
/** /**
* Set the exception translator for this instance. * Set the exception translator for this instance.
* <p>If no custom translator is provided, a default * <p>A {@link SQLErrorCodeSQLExceptionTranslator} used by default if a user-provided
* {@link SQLErrorCodeSQLExceptionTranslator} is used * `sql-error-codes.xml` file has been found in the root of the classpath. Otherwise,
* which examines the SQLException's vendor-specific error code. * {@link SQLExceptionSubclassTranslator} serves as the default translator as of 6.0.
* @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
* @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator * @see org.springframework.jdbc.support.SQLExceptionSubclassTranslator
*/ */
public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) { public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator; this.exceptionTranslator = exceptionTranslator;
} }
/** /**
* Return the exception translator for this instance. * Return the exception translator to use for this instance,
* <p>Creates a default {@link SQLErrorCodeSQLExceptionTranslator} * creating a default if necessary.
* for the specified DataSource if none set, or a * @see #setExceptionTranslator
* {@link SQLStateSQLExceptionTranslator} in case of no DataSource.
* @see #getDataSource()
*/ */
public SQLExceptionTranslator getExceptionTranslator() { public SQLExceptionTranslator getExceptionTranslator() {
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator; SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;

36
spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcTransactionManager.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -59,8 +59,8 @@ public class JdbcTransactionManager extends DataSourceTransactionManager {
/** /**
* Create a new JdbcTransactionManager instance. * Create a new {@code JdbcTransactionManager} instance.
* A DataSource has to be set to be able to use it. * A {@code DataSource} has to be set to be able to use it.
* @see #setDataSource * @see #setDataSource
*/ */
public JdbcTransactionManager() { public JdbcTransactionManager() {
@ -68,7 +68,7 @@ public class JdbcTransactionManager extends DataSourceTransactionManager {
} }
/** /**
* Create a new JdbcTransactionManager instance. * Create a new {@code JdbcTransactionManager} instance.
* @param dataSource the JDBC DataSource to manage transactions for * @param dataSource the JDBC DataSource to manage transactions for
*/ */
public JdbcTransactionManager(DataSource dataSource) { public JdbcTransactionManager(DataSource dataSource) {
@ -79,13 +79,15 @@ public class JdbcTransactionManager extends DataSourceTransactionManager {
/** /**
* Specify the database product name for the DataSource that this transaction manager * Specify the database product name for the {@code DataSource} that this
* uses. This allows to initialize an SQLErrorCodeSQLExceptionTranslator without * transaction manager operates on.
* obtaining a Connection from the DataSource to get the meta-data. * This allows for initializing a {@link SQLErrorCodeSQLExceptionTranslator} without
* obtaining a {@code Connection} from the {@code DataSource} to get the meta-data.
* @param dbName the database product name that identifies the error codes entry * @param dbName the database product name that identifies the error codes entry
* @see JdbcAccessor#setDatabaseProductName * @see #setExceptionTranslator
* @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName * @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName
* @see java.sql.DatabaseMetaData#getDatabaseProductName() * @see java.sql.DatabaseMetaData#getDatabaseProductName()
* @see JdbcAccessor#setDatabaseProductName
*/ */
public void setDatabaseProductName(String dbName) { public void setDatabaseProductName(String dbName) {
if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) { if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) {
@ -97,22 +99,22 @@ public class JdbcTransactionManager extends DataSourceTransactionManager {
} }
/** /**
* Set the exception translator for this instance. * Set the exception translator for this transaction manager.
* <p>If no custom translator is provided, a default * <p>A {@link SQLErrorCodeSQLExceptionTranslator} used by default if a user-provided
* {@link SQLErrorCodeSQLExceptionTranslator} is used * `sql-error-codes.xml` file has been found in the root of the classpath. Otherwise,
* which examines the SQLException's vendor-specific error code. * {@link SQLExceptionSubclassTranslator} serves as the default translator as of 6.0.
* @see JdbcAccessor#setExceptionTranslator
* @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
* @see org.springframework.jdbc.support.SQLExceptionSubclassTranslator
* @see JdbcAccessor#setExceptionTranslator
*/ */
public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) { public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator; this.exceptionTranslator = exceptionTranslator;
} }
/** /**
* Return the exception translator for this instance. * Return the exception translator to use for this instance,
* <p>Creates a default {@link SQLErrorCodeSQLExceptionTranslator} * creating a default if necessary.
* for the specified DataSource if none set. * @see #setExceptionTranslator
* @see #getDataSource()
*/ */
public SQLExceptionTranslator getExceptionTranslator() { public SQLExceptionTranslator getExceptionTranslator() {
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator; SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;

6
spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -51,6 +51,8 @@ import org.springframework.lang.Nullable;
* <p>Falls back to a standard {@link SQLStateSQLExceptionTranslator} if the JDBC * <p>Falls back to a standard {@link SQLStateSQLExceptionTranslator} if the JDBC
* driver does not actually expose JDBC 4 compliant {@code SQLException} subclasses. * driver does not actually expose JDBC 4 compliant {@code SQLException} subclasses.
* *
* <p>This translator serves as the default translator as of 6.0.
*
* @author Thomas Risberg * @author Thomas Risberg
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 2.5 * @since 2.5
@ -72,7 +74,7 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
} }
if (ex instanceof SQLTransactionRollbackException) { if (ex instanceof SQLTransactionRollbackException) {
if ("40001".equals(ex.getSQLState())) { if (SQLStateSQLExceptionTranslator.indicatesCannotAcquireLock(ex.getSQLState())) {
return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); return new CannotAcquireLockException(buildMessage(task, sql, ex), ex);
} }
return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex);

19
spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java

@ -39,11 +39,16 @@ import org.springframework.lang.Nullable;
* does not require special initialization (no database vendor detection, etc.). * does not require special initialization (no database vendor detection, etc.).
* For more precise translation, consider {@link SQLErrorCodeSQLExceptionTranslator}. * For more precise translation, consider {@link SQLErrorCodeSQLExceptionTranslator}.
* *
* <p>This translator is commonly used as a {@link #setFallbackTranslator fallback}
* behind a primary translator such as {@link SQLErrorCodeSQLExceptionTranslator} or
* {@link SQLExceptionSubclassTranslator}.
*
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Thomas Risberg * @author Thomas Risberg
* @see java.sql.SQLException#getSQLState() * @see java.sql.SQLException#getSQLState()
* @see SQLErrorCodeSQLExceptionTranslator * @see SQLErrorCodeSQLExceptionTranslator
* @see SQLExceptionSubclassTranslator
*/ */
public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLExceptionTranslator { public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLExceptionTranslator {
@ -111,7 +116,7 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
} }
else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) { else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) {
if ("40001".equals(sqlState)) { if (indicatesCannotAcquireLock(sqlState)) {
return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); return new CannotAcquireLockException(buildMessage(task, sql, ex), ex);
} }
return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex);
@ -148,9 +153,10 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
return sqlState; return sqlState;
} }
/** /**
* Check whether the given SQL state (and the associated error code in case * Check whether the given SQL state (and the associated error code in case
* of a generic SQL state value) indicate a duplicate key exception: * of a generic SQL state value) indicate a {@link DuplicateKeyException}:
* either SQL state 23505 as a specific indication, or the generic SQL state * either SQL state 23505 as a specific indication, or the generic SQL state
* 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB, * 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB,
* 2601/2627 for MS SQL Server). * 2601/2627 for MS SQL Server).
@ -163,4 +169,13 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
(errorCode == 1 || errorCode == 1062 || errorCode == 2601 || errorCode == 2627))); (errorCode == 1 || errorCode == 1062 || errorCode == 2601 || errorCode == 2627)));
} }
/**
* Check whether the given SQL state indicates a {@link CannotAcquireLockException},
* with SQL state 40001 as a specific indication.
* @param sqlState the SQL state value
*/
static boolean indicatesCannotAcquireLock(@Nullable String sqlState) {
return "40001".equals(sqlState);
}
} }

Loading…
Cancel
Save