How to create custom retry logic for Spring Datasource? - spring-data-jpa

I'm connecting to an Azure SQL database and my next task is to create custom retry logic when a connection has failed. I would like the retry logic to run both on startup (if needed) as well as any time there's a connection failure while the app is running. I did a test where I removed the IP restrictions from my app and that then caused an exception in my application (as excepted). I'd like to handle when that exception is thrown so that I can trigger a job that verifies both the app and the server are configured correctly. I'm looking for a solution where I can handle these exceptions and retry the DB transaction?
DataSource Config
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.username("username")
.password("password")
.url("jdbc:sqlserver://contoso.database.windows.net:1433;database=*********;user=******#*******;password=*****;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;")
.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
.build();
}
application.properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect
spring.jpa.show-sql=true
logging.level.org.springframework.web: ERROR
logging.level.org.hibernate: ERROR
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.max-active=1
spring.datasource.tomcat.test-on-borrow=true
spring.jpa.hibernate.ddl-auto=update

The following code may help you create your retry logic for a data source on Spring Boot:
package com.example.demo;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;
#SpringBootApplication
#EnableRetry
public class DemoApplication {
#Order(Ordered.HIGHEST_PRECEDENCE)
private class RetryableDataSourceBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof DataSource) {
bean = new RetryableDataSource((DataSource)bean);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public BeanPostProcessor dataSouceWrapper() {
return new RetryableDataSourceBeanPostProcessor();
}
}
class RetryableDataSource extends AbstractDataSource {
private DataSource delegate;
public RetryableDataSource(DataSource delegate) {
this.delegate = delegate;
}
#Override
#Retryable(maxAttempts=10, backoff=#Backoff(multiplier=2.3, maxDelay=30000))
public Connection getConnection() throws SQLException {
return delegate.getConnection();
}
#Override
#Retryable(maxAttempts=10, backoff=#Backoff(multiplier=2.3, maxDelay=30000))
public Connection getConnection(String username, String password)
throws SQLException {
return delegate.getConnection(username, password);
}
}

Not sure what you deem custom but, there is an out-of-the-box option with Spring boot and Aspectj by leveraging the pom.xml in the mavern project, as spring-retry depends on Aspectj:
Add the following dependencies in your project pom.xml:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${version}</version>
</dependency>
And then add the #EnableRetry annotation to your code:
package com.example.springretry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
#EnableRetry
#SpringBootApplication
public class SpringRetryApplication {
public static void main(String[] args) {
SpringApplication.run(SpringRetryApplication.class, args);
}
}
The Spring retry module example can be found here: https://howtodoinjava.com/spring-boot2/spring-retry-module/

Related

Why does JobBuilder says could not autowire?

I am following this Udemy course(Batch Processing with Spring Batch & Spring Boot
) for Spring Batch. In the course JBF(JobBuilderFactory) is depracated so I googled what to use instead and it says use JobBuilder.
Right now jobBuilder and stepBuilder are underlined red and says could not autowired.
package com.example.springbatch;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
//1st step
#EnableBatchProcessing
//2nd //use of this?
#ComponentScan("com.example.config") //job and steps will go in this packet
public class SpringBatchApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchApplication.class, args);
}
}
package com.example.config;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration //add annot
public class SampleJob { //3rd creating first samp job
//5th Create the job, spring batch provides one class - job builder. And create the obj named jobBuilder
#Autowired
private JobBuilder jobBuilder;
#Autowired
private StepBuilder stepBuilder;
#Bean //4th define #Bean for the first job
public Job firstJob() { //Job(interface) imports core.Job
//6th use
return jobBuilder.get("First Job") //use get First Job is 1st job name
.start(firstStep()) //Inside the start, pass in your step. Job can hava single or multiple step
.build();
}
#Bean //7th Adding the Step interface. Autowire it as well.
Step firstStep() {
return stepBuilder.get("First Step")
.tasklet(firstTask()) //Will need to call Tasklet.
.build(); //call build to create the step.
}
//8th
private Tasklet firstTask() {
return new Tasklet() {
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.out.println("This is first tasklet step");
return RepeatStatus.FINISHED; //need this
}
};
}
}
I tried to search on google and this is suppose to print System.out.println("This is first tasklet step");
The course is probably using Spring Batch 4. In Spring Batch 5, those builder factories were deprecated for removal and are not exposed as beans in the application context by the #EnableBatchProcessing annotation. Here is the relevant section in the migration guide about that: JobBuilderFactory and StepBuilderFactory bean exposure/configuration.
The typical migration path from v4 to v5 in that regard is as follows:
// Sample with v4
#Configuration
#EnableBatchProcessing
public class MyJobConfig {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Bean
public Job myJob(Step step) {
return this.jobBuilderFactory.get("myJob")
.start(step)
.build();
}
}
// Sample with v5
#Configuration
#EnableBatchProcessing
public class MyJobConfig {
#Bean
public Job myJob(JobRepository jobRepository, Step step) {
return new JobBuilder("myJob", jobRepository)
.start(step)
.build();
}
}

Spring Batch removeJobExecutions fails

I'm trying to explore Spring batch with Spring Boot 2.3.3, and obviously the tests are very important
The batch doesnt' read / process / write anything, I've just created the skeleton.
On the tests side I've the following
#Autowired
private IntegrationTestsNeeds integrationTestsNeeds;
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
#AfterEach
void tearDown() throws InterruptedException {
jobRepositoryTestUtils.removeJobExecutions();
}
#Test
void testUpdateStatisticsBatch() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
ExitStatus exitStatus = jobExecution.getExitStatus();
Assertions.assertThat(exitStatus).isEqualTo(ExitStatus.COMPLETED);
}
The test pass but in the #AfterEach method I've the following error
org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [delete from BATCH_STEP_EXECUTION];
Cannot delete or update a parent row: a foreign key constraint fails (`cvl`.`BATCH_STEP_EXECUTION_CONTEXT`, CONSTRAINT `STEP_EXEC_CTX_FK` FOREIGN KEY (`STEP_EXECUTION_ID`) REFERENCES `BATCH_STEP_EXECUTION` (`STEP_EXECUTION_ID`));
nested exception is java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`cvl`.`BATCH_STEP_EXECUTION_CONTEXT`, CONSTRAINT `STEP_EXEC_CTX_FK` FOREIGN KEY (`STEP_EXECUTION_ID`) REFERENCES `BATCH_STEP_EXECUTION` (`STEP_EXECUTION_ID`))
Which kind of error I'm doing?
I don't know why but the problem is solved using the transactionTemplate.
import org.springframework.transaction.support.TransactionTemplate
#Autowired
private TransactionTemplate transactionTemplate;
#AfterEach
void tearDown() {
transactionTemplate.execute(ts -> {
jobRepositoryTestUtils.removeJobExecutions();
return null;
});
}
Even though the jdbcTemplate is able to perform the delete statements, for some reason is not able to really delete the rows from the database.
I'm not able to reproduce the issue, here is an example that passes without the exception:
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = {MyJobTests.MyJobConfig.class})
public class MyJobTests {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
#Test
public void testMyJob() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
ExitStatus exitStatus = jobExecution.getExitStatus();
Assert.assertEquals(ExitStatus.COMPLETED, exitStatus);
}
#After
public void tearDown() {
jobRepositoryTestUtils.removeJobExecutions();
}
#Configuration
#EnableBatchProcessing
public static class MyJobConfig {
#Bean
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) {
return jobs.get("job")
.start(steps.get("step")
.tasklet((contribution, chunkContext) -> {
System.out.println("hello world");
return RepeatStatus.FINISHED;
})
.build())
.build();
}
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("/org/springframework/batch/core/schema-drop-h2.sql")
.addScript("/org/springframework/batch/core/schema-h2.sql")
.build();
}
}
}
Spring Batch version 4.2.4

Spring Cloud Contract Stub Runner : how to configure Wiremock server?

package com.example.stubrunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
#EnableStubRunnerServer
public class StubRunnerApplication {
public static void main(String[] args) {
SpringApplication.run(StubRunnerApplication.class, args);
}
#Bean
public WireMockConfigurationCustomizer optionsCustomizer() {
WireMockConfigurationCustomizer customizer = new WireMockConfigurationCustomizer() {
#Override
public void customize(com.github.tomakehurst.wiremock.core.WireMockConfiguration config) {
config.jettyHeaderBufferSize(16384);
}
};
return customizer;
}
}
Above customizer bean does not seem to have any effect. This feature has not much documentation. With security token headers Wiremock's (jettty) default value is just too little.
I used start.spring.io with (current) defaults: spring boot 2.5.5. and spring cloud Hoxton.SR3.
java -jar wiremock-standalone-2.26.3.jar --jetty-header-buffer-size 16384
works just fine.
EDIT :
package com.example.wiremockrunnerlatest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
#SpringBootApplication
#EnableStubRunnerServer
#AutoConfigureStubRunner(httpServerStubConfigurer = HeaderSizeConfigurer.class)
public class WiremockRunnerLatestApplication {
public static void main(String[] args) {
SpringApplication.run(WiremockRunnerLatestApplication.class, args);
}
}
... and then :
public class HeaderSizeConfigurer extends WireMockHttpServerStubConfigurer {
#Override
public WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
return httpStubConfiguration.jettyHeaderBufferSize(16384);
}
}
Have you tried using #AutoConfigureStubRunner annotation?
Just add below annotation in your tests:
#AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.CLASSPATH,
ids = "com.org:servicename:+:stubs")
Here stubsmode is classpath which means that stubs would be available in classpath.
to do that add:
testCompile("com.org:servicename:+:stubs") { transitive = false }
in your build if your using gradle or add equivalient from maven.
This will download the application automatically from remote and configures the wiremock server for stubs to be available.
complete configuration to run a spring boot test is like:
#RunWith(SpringRunner.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
classes = HiltiIntegrationApplication.class)
#AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.CLASSPATH,
ids = "com.ict:organization-management:+:stubs")
#DirtiesContext
Hope this helps!

Spring Boot Rest : Error 404 not found when posting JSON via Postman

I am trying to invoke a POST service via Postman. My application is running on embedded tomcat server. However when I try to invoke the service, the error I get is "No mapping found for HTTP request with URI [/findrouting] in DispatcherServlet with name 'dispatcherServlet'"
It is not even recognizing http:localhost:8080/
RoutingRequest and RoutingResponse are the POJOs with getters and setters.
Am I missing something here. I did check lots of examples but didn't find any solution to my problem.
Please see my code below :
package com.ab.hello.ambassador.server;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AmbassadorApplication.class, args);
System.out.println("List of Beans instantiated");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
}
package com.ab.hello.ambassador.server.controller;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.dp.connect.ambassador.server.RoutingRequest;
import com.dp.connect.ambassador.server.RoutingResponse;
#RestController
public class ApplicationController {
#RequestMapping("/")
public String index() {
return "Spring Boot POC Welcomes You!";
}
#RequestMapping(method = POST, value = "/findrouting", consumes = { APPLICATION_JSON_VALUE }, produces = {
APPLICATION_JSON_VALUE })
public RoutingResponse findRoute(#RequestBody RoutingRequest request) throws Exception {
// some business logic that would return response; as of now I have set it to null
RoutingResponse response = null;
return response;
}
}
Thanks for your time. After struggling for quite a long time. I figured out this piece of code. Added annotation #EnableWebMvc. This solved my problem.
#SpringBootApplication
#EnableWebMvc
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Try to define the path in your controller:
#RestController
#RequestMapping("/")
public class ApplicationController {
#RequestMapping
public String index() {
return "Spring Boot POC Welcomes You!";
}
#RequestMapping(method = POST, value = "/findrouting", consumes = { APPLICATION_JSON_VALUE }, produces = {
APPLICATION_JSON_VALUE })
public RoutingResponse findRoute(#RequestBody RoutingRequest request) throws Exception {
// some business logic that would return response; as of now I have set it to null
RoutingResponse response = null;
return response;
}
}
Change the below line
ApplicationContext ctx = SpringApplication.run(AmbassadorApplication.class, args);
with
ApplicationContext ctx = SpringApplication.run(Application.class, args);
according to spring docs , seems need to Override a config method:
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}

JdbcTemplate object is null in my springboot application - using postgres

Here are my spring.datasource properties in application.properties-
spring.datasource.url=jdbc:postgresql://<hostname goes here>
spring.datasource.username=<username goes here>
spring.datasource.password=password
spring.datasource.driverClassName=org.postgresql.Driver
My main class is as follows:
#PropertySources(value = {#PropertySource("classpath:application.properties")})
#PropertySource(value = "classpath:sql.properties")
#SpringBootApplication
public class MyApp implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
#Override
public void run(String... strings) throws Exception {
Execute execute = new Execute();
execute.executeCleanUp();
}
}
The Execute class is as follows:
import com.here.oat.repository.CleanUpEntries;
import com.here.oat.repository.CleanUpEntriesImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import java.io.IOException;
/***
*
*/
public class Execute {
#Autowired
private CleanUpEntries cleanUpEntries;
public void executeCleanUp() throws IOException {
cleanUpEntries = new CleanUpEntriesImpl();
cleanUpEntries.delete();
}
}
Here is the implementation class - CleanupEntriesImpl:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
#Component
public class CleanUpEntriesImpl implements CleanUpEntries{
#Autowired
private JdbcTemplate jdbcTemplate;
#Value(value = "${delete.query}")
private String deleteQuery;
#Override
public int delete() {
int id= jdbcTemplate.queryForObject(deleteQuery, Integer.class);
return id;
}
}
pom.xml has the following dependencies:
<!--jdbc driver-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
<scope>runtime</scope>
</dependency>
Not sure why jdbcTemplate object is null when the delete() method is called. Any ideas?
The issue was resolved by removing all new operators from my classes and autowiring everything and making Execute a Component class.
Thanks!