admin管理员组

文章数量:1419250

I've started using the @Retryable annotation in my Spring Boot application (Spring Boot 2.7.18, JDK 21) and it works perfectly fine!

Below are some relevant extracts from the code:

@EnableRetry
@EnableTransactionManagement
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
@Service
public class MyService {

    @Retryable(
        retryFor = {LockAcquisitionException.class, SQLServerException.class},
        maxAttempts = 5,
        backoff = @Backoff(delay = 500),
        listeners = "myRetryListener"
    )
    @Transactional
    @Override
    public void doSomething() throws MyException {
        // Does something
    }
}
public class MyRetryListener implements RetryListener {

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        log.info("Retry open.");
        return true;
    }

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        int attempt = context.getRetryCount();
        if (throwable == null) {
            log.info("Operation succeeded after {} attempts.", attempt);
        } else {
            log.info("Operation failed after {} attempts.", attempt);
        }
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        log.warn("Inner retry {} of {} failed due to: {}", context.getRetryCount(), context.getAttribute("context.max-attempts"), throwable.getMessage());
    }
}

HOWEVER, when I run an integration test for the application, although the retry mechanism works, it only retries once. I suspect I am missing some initialization in the test, but I cannot figure out what it is.

Has anyone experienced anything similar?

@SpringBootTest
@ActiveProfiles("test")
@EnableAutoConfiguration
@AutoConfigureMockMvc
class MyApplicationIntegrationTest {
}

The application writes some records to a MS SQL Server database, which I have simulated in test with an H2 in memory database. In the integration test I throw a SQLException via a 'BEFORE INSERT' trigger in the H2 table.

The output of the application is, as expected:

2025-01-29 12:05:14  INFO .MyRetryListener    : Retry open.
2025-01-29 12:05:15  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:15 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:15  WARN .MyRetryListener    : Inner retry 1 of 5 failed due to: Could not persist entities
2025-01-29 12:05:15  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:15 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:15  WARN .MyRetryListener    : Inner retry 2 of 5 failed due to: Could not persist entities
2025-01-29 12:05:16  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:16 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:16  WARN .MyRetryListener    : Inner retry 3 of 5 failed due to: Could not persist entities
2025-01-29 12:05:16  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:16 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:16  WARN .MyRetryListener    : Inner retry 4 of 5 failed due to: Could not persist entities
2025-01-29 12:05:17  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:17 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:17  WARN .MyRetryListener    : Inner retry 5 of 5 failed due to: Could not persist entities
2025-01-29 12:05:17  INFO .MyRetryListener    : Operation failed after 5 attempts.

while the output of the test is, sadly:

2025:01:29 12:12:37  INFO  .MyRetryListener   : Retry open.
2025:01:29 12:12:37  WARN  TriggerImpl        : Executing database trigger H2TriggerImpl: Simulated SQLException for testing
2025:01:29 12:12:37  WARN  SqlExceptionHelper : SQL Error: 0, SQLState: null
2025:01:29 12:12:37 ERROR  SqlExceptionHelper : Simulated SQLException for testing
2025:01:29 12:12:37  INFO  .AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2025:01:29 12:12:38  WARN  .MyRetryListener   : Inner retry 1 of 5 failed due to: Could not persist entities
2025:01:29 12:12:38  INFO  .MyRetryListener   : Operation failed after 1 attempts.

I've started using the @Retryable annotation in my Spring Boot application (Spring Boot 2.7.18, JDK 21) and it works perfectly fine!

Below are some relevant extracts from the code:

@EnableRetry
@EnableTransactionManagement
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
@Service
public class MyService {

    @Retryable(
        retryFor = {LockAcquisitionException.class, SQLServerException.class},
        maxAttempts = 5,
        backoff = @Backoff(delay = 500),
        listeners = "myRetryListener"
    )
    @Transactional
    @Override
    public void doSomething() throws MyException {
        // Does something
    }
}
public class MyRetryListener implements RetryListener {

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        log.info("Retry open.");
        return true;
    }

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        int attempt = context.getRetryCount();
        if (throwable == null) {
            log.info("Operation succeeded after {} attempts.", attempt);
        } else {
            log.info("Operation failed after {} attempts.", attempt);
        }
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        log.warn("Inner retry {} of {} failed due to: {}", context.getRetryCount(), context.getAttribute("context.max-attempts"), throwable.getMessage());
    }
}

HOWEVER, when I run an integration test for the application, although the retry mechanism works, it only retries once. I suspect I am missing some initialization in the test, but I cannot figure out what it is.

Has anyone experienced anything similar?

@SpringBootTest
@ActiveProfiles("test")
@EnableAutoConfiguration
@AutoConfigureMockMvc
class MyApplicationIntegrationTest {
}

The application writes some records to a MS SQL Server database, which I have simulated in test with an H2 in memory database. In the integration test I throw a SQLException via a 'BEFORE INSERT' trigger in the H2 table.

The output of the application is, as expected:

2025-01-29 12:05:14  INFO .MyRetryListener    : Retry open.
2025-01-29 12:05:15  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:15 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:15  WARN .MyRetryListener    : Inner retry 1 of 5 failed due to: Could not persist entities
2025-01-29 12:05:15  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:15 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:15  WARN .MyRetryListener    : Inner retry 2 of 5 failed due to: Could not persist entities
2025-01-29 12:05:16  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:16 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:16  WARN .MyRetryListener    : Inner retry 3 of 5 failed due to: Could not persist entities
2025-01-29 12:05:16  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:16 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:16  WARN .MyRetryListener    : Inner retry 4 of 5 failed due to: Could not persist entities
2025-01-29 12:05:17  WARN SqlExceptionHelper  : SQL Error: 50000, SQLState: S0001
2025-01-29 12:05:17 ERROR SqlExceptionHelper  : Simulated SQLException for testing
2025-01-29 12:05:17  WARN .MyRetryListener    : Inner retry 5 of 5 failed due to: Could not persist entities
2025-01-29 12:05:17  INFO .MyRetryListener    : Operation failed after 5 attempts.

while the output of the test is, sadly:

2025:01:29 12:12:37  INFO  .MyRetryListener   : Retry open.
2025:01:29 12:12:37  WARN  TriggerImpl        : Executing database trigger H2TriggerImpl: Simulated SQLException for testing
2025:01:29 12:12:37  WARN  SqlExceptionHelper : SQL Error: 0, SQLState: null
2025:01:29 12:12:37 ERROR  SqlExceptionHelper : Simulated SQLException for testing
2025:01:29 12:12:37  INFO  .AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2025:01:29 12:12:38  WARN  .MyRetryListener   : Inner retry 1 of 5 failed due to: Could not persist entities
2025:01:29 12:12:38  INFO  .MyRetryListener   : Operation failed after 1 attempts.
Share Improve this question asked Jan 29 at 12:23 GepGep 9283 gold badges16 silver badges33 bronze badges 4
  • 2 pls share the full test code and application-test.properties as well – Geii Lvov Commented Jan 29 at 12:35
  • 2 Your config is retryFor = {LockAcquisitionException.class, SQLServerException.class}, but probably your test environment doesn't not throw those exceptions. Some other instead, which is not retryable. – Artem Bilan Commented Jan 31 at 15:42
  • Maybe the OptimisticLockingFailureException is not retryable by your code. Probably related: stackoverflow/q/79228209 – Gábor Bakos Commented Feb 2 at 13:36
  • Thank you @ArtemBilan that was the actual issue. The test environment throws a SQLException, while the Retryable annotation expects a SQLServerException. I got confused by my own log statement while running the application (it should say "Simulated SQLServerException for testing"). – Gep Commented Feb 3 at 10:47
Add a comment  | 

2 Answers 2

Reset to default 1

According to your configuration only these exceptions are retryable:

retryFor = {LockAcquisitionException.class, SQLServerException.class},

So, make sure that your test environment throws one of those. All other exceptions are not retryable.

I do not have enough stackoverflow points so I am responding via an answer.

Given that you have LockAcquisition, I assume you are using a version column.

If you are using versioning, there are some additional annotations you might require depending on what type of locking approach you are taking.

For instance, for optimistic locking there's the annotation below where you can specify the exact lock type mode you want.

@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)

And also exceptions related to optimistic locking such as OptimisticLockException.class, CannotAcquireLockException.class, UnexpectedRollbackException.class, etc based on your requirements..

If this isn't the case, let me know and I can try to help as I have PROD experience with having to implement retryable.

本文标签: javaRetryable only retrying once in integration testStack Overflow