diff --git a/framework-docs/modules/ROOT/pages/integration/scheduling.adoc b/framework-docs/modules/ROOT/pages/integration/scheduling.adoc index 886f2941aa..415e043920 100644 --- a/framework-docs/modules/ROOT/pages/integration/scheduling.adoc +++ b/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 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] ==== As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope. diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java index 0eec31358a..b9ba2081c0 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java +++ b/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 - * {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes must be - * specified. + * {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes + * must be specified. * *

The annotated method must not accept arguments. It will typically have * 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. * *

This annotation can be used as a {@linkplain Repeatable repeatable} - * 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. * *

This annotation may be used as a meta-annotation to create custom * composed annotations with attribute overrides. diff --git a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java index 695c760d6f..2fb7bed7f9 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/MBeanExporterTests.java @@ -354,7 +354,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setBeans(beansToExport); exporter.setServer(getServer()); 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... start(exporter); } @@ -524,18 +524,16 @@ public class MBeanExporterTests extends AbstractMBeanServerTests { } @Test - void notRunningInBeanFactoryAndPassedBeanNameToExport() throws Exception { + void notRunningInBeanFactoryAndPassedBeanNameToExport() { Map beans = Map.of(OBJECT_NAME, "beanName"); exporter.setBeans(beans); - assertThatExceptionOfType(MBeanExportException.class) - .isThrownBy(() -> start(exporter)); + assertThatExceptionOfType(MBeanExportException.class).isThrownBy(() -> start(exporter)); } @Test - void notRunningInBeanFactoryAndAutodetectionIsOn() throws Exception { - exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); - assertThatExceptionOfType(MBeanExportException.class) - .isThrownBy(() -> start(exporter)); + void notRunningInBeanFactoryAndAutodetectionIsOn() { + exporter.setAutodetect(true); + assertThatExceptionOfType(MBeanExportException.class).isThrownBy(() -> start(exporter)); } @Test // SPR-2158 @@ -556,7 +554,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests { } @Test // SPR-3302 - void beanNameCanBeUsedInNotificationListenersMap() throws Exception { + void beanNameCanBeUsedInNotificationListenersMap() { String beanName = "charlesDexterWard"; BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); @@ -576,7 +574,7 @@ public class MBeanExporterTests extends AbstractMBeanServerTests { } @Test - void wildcardCanBeUsedInNotificationListenersMap() throws Exception { + void wildcardCanBeUsedInNotificationListenersMap() { String beanName = "charlesDexterWard"; BeanDefinitionBuilder testBean = BeanDefinitionBuilder.rootBeanDefinition(JmxTestBean.class); @@ -636,24 +634,23 @@ public class MBeanExporterTests extends AbstractMBeanServerTests { exporter.setServer(getServer()); exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(firstBeanName, secondBeanName)); exporter.setBeanFactory(factory); - exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); + exporter.setAutodetect(true); exporter.addExcludedBean(secondBeanName); start(exporter); - assertIsRegistered("Bean not autodetected in (AUTODETECT_ALL) mode", - ObjectNameManager.getInstance(firstBeanName)); - assertIsNotRegistered("Bean should have been excluded", - ObjectNameManager.getInstance(secondBeanName)); + assertIsRegistered("Bean not autodetected", ObjectNameManager.getInstance(firstBeanName)); + assertIsNotRegistered("Bean should have been excluded", ObjectNameManager.getInstance(secondBeanName)); } @Test void registerFactoryBean() throws MalformedObjectNameException { 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.setBeanFactory(factory); - exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); + exporter.setAutodetect(true); start(exporter); assertIsRegistered("Non-null FactoryBean object registered", @@ -663,11 +660,12 @@ public class MBeanExporterTests extends AbstractMBeanServerTests { @Test void ignoreNullObjectFromFactoryBean() throws MalformedObjectNameException { 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.setBeanFactory(factory); - exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL); + exporter.setAutodetect(true); start(exporter); assertIsNotRegistered("Null FactoryBean object not registered", 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 8b4bd9c304..5178b4a0f4 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 @@ -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"); * 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. - * This allows to initialize an SQLErrorCodeSQLExceptionTranslator without - * obtaining a Connection from the DataSource to get the meta-data. + * Specify the database product name for the {@code DataSource} that this accessor uses. + * 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 + * @see #setExceptionTranslator * @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName * @see java.sql.DatabaseMetaData#getDatabaseProductName() */ @@ -98,22 +99,20 @@ public abstract class JdbcAccessor implements InitializingBean { /** * Set the exception translator for this instance. - *

If no custom translator is provided, a default - * {@link SQLErrorCodeSQLExceptionTranslator} is used - * which examines the SQLException's vendor-specific error code. + *

A {@link SQLErrorCodeSQLExceptionTranslator} used by default if a user-provided + * `sql-error-codes.xml` file has been found in the root of the classpath. Otherwise, + * {@link SQLExceptionSubclassTranslator} serves as the default translator as of 6.0. * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator - * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator + * @see org.springframework.jdbc.support.SQLExceptionSubclassTranslator */ public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) { this.exceptionTranslator = exceptionTranslator; } /** - * Return the exception translator for this instance. - *

Creates a default {@link SQLErrorCodeSQLExceptionTranslator} - * for the specified DataSource if none set, or a - * {@link SQLStateSQLExceptionTranslator} in case of no DataSource. - * @see #getDataSource() + * Return the exception translator to use for this instance, + * creating a default if necessary. + * @see #setExceptionTranslator */ public SQLExceptionTranslator getExceptionTranslator() { SQLExceptionTranslator exceptionTranslator = this.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 bda750f472..e03987499a 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-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -59,8 +59,8 @@ public class JdbcTransactionManager extends DataSourceTransactionManager { /** - * Create a new JdbcTransactionManager instance. - * A DataSource has to be set to be able to use it. + * Create a new {@code JdbcTransactionManager} instance. + * A {@code DataSource} has to be set to be able to use it. * @see #setDataSource */ 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 */ 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 - * uses. This allows to initialize an SQLErrorCodeSQLExceptionTranslator without - * obtaining a Connection from the DataSource to get the meta-data. + * Specify the database product name for the {@code DataSource} that this + * transaction manager operates on. + * 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 - * @see JdbcAccessor#setDatabaseProductName + * @see #setExceptionTranslator * @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName * @see java.sql.DatabaseMetaData#getDatabaseProductName() + * @see JdbcAccessor#setDatabaseProductName */ public void setDatabaseProductName(String dbName) { if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) { @@ -97,22 +99,22 @@ public class JdbcTransactionManager extends DataSourceTransactionManager { } /** - * Set the exception translator for this instance. - *

If no custom translator is provided, a default - * {@link SQLErrorCodeSQLExceptionTranslator} is used - * which examines the SQLException's vendor-specific error code. - * @see JdbcAccessor#setExceptionTranslator + * Set the exception translator for this transaction manager. + *

A {@link SQLErrorCodeSQLExceptionTranslator} used by default if a user-provided + * `sql-error-codes.xml` file has been found in the root of the classpath. Otherwise, + * {@link SQLExceptionSubclassTranslator} serves as the default translator as of 6.0. * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator + * @see org.springframework.jdbc.support.SQLExceptionSubclassTranslator + * @see JdbcAccessor#setExceptionTranslator */ public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) { this.exceptionTranslator = exceptionTranslator; } /** - * Return the exception translator for this instance. - *

Creates a default {@link SQLErrorCodeSQLExceptionTranslator} - * for the specified DataSource if none set. - * @see #getDataSource() + * Return the exception translator to use for this instance, + * creating a default if necessary. + * @see #setExceptionTranslator */ public SQLExceptionTranslator getExceptionTranslator() { SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java index 058348f7ec..eb5fdcd250 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java +++ b/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"); * you may not use this file except in compliance with the License. @@ -51,6 +51,8 @@ import org.springframework.lang.Nullable; *

Falls back to a standard {@link SQLStateSQLExceptionTranslator} if the JDBC * driver does not actually expose JDBC 4 compliant {@code SQLException} subclasses. * + *

This translator serves as the default translator as of 6.0. + * * @author Thomas Risberg * @author Juergen Hoeller * @since 2.5 @@ -72,7 +74,7 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } if (ex instanceof SQLTransactionRollbackException) { - if ("40001".equals(ex.getSQLState())) { + if (SQLStateSQLExceptionTranslator.indicatesCannotAcquireLock(ex.getSQLState())) { return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); } return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java index f8d64b9485..5954618b3e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java +++ b/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.). * For more precise translation, consider {@link SQLErrorCodeSQLExceptionTranslator}. * + *

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 Juergen Hoeller * @author Thomas Risberg * @see java.sql.SQLException#getSQLState() * @see SQLErrorCodeSQLExceptionTranslator + * @see SQLExceptionSubclassTranslator */ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLExceptionTranslator { @@ -111,7 +116,7 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } 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 PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); @@ -148,9 +153,10 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException return sqlState; } + /** * 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 * 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB, * 2601/2627 for MS SQL Server). @@ -163,4 +169,13 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException (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); + } + }