Spring ShedLock is a distributed lock for scheduled tasks. It ensures that only one instance of a scheduled task runs at the same time in a distributed environment, such as a cluster of microservices. This is particularly useful for tasks that should not be executed concurrently, like data processing or cleanup jobs.
How ShedLock Works
Lock Provider: A lock provider is configured to manage the locks. This could be a database, Redis, etc.
Annotations: Tasks are annotated with @SchedulerLock to specify lock names and durations.
Execution: When a task is scheduled to run, ShedLock checks if a lock is available. If it is, the task runs and the lock is held for the specified duration.
Release: After the task completes or the lock duration expires, the lock is released, allowing other instances to acquire it.
Ensure that scheduling is enabled in your Spring Boot application by adding @EnableScheduling to your main application class.
Main class
package com.kswaughs.batch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Step 5: Create Database Table - ShedLock
The ShedLock library does not create the table automatically. You need to create the table manually. Here is the SQL script to create the ShedLock table.
Database Table
CREATE TABLE shedlock(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
Key Features
Distributed Locking
Ensures that only one instance of a task runs at a time across multiple nodes.
Multiple Lock Providers
Supports various lock providers like JDBC, MongoDB, Redis, ZooKeeper, etc.
Annotations
Uses annotations like @SchedulerLock to easily lock scheduled tasks.
Configuration
Can be configured using Spring's configuration classes and properties.
Integration
Seamlessly integrates with Spring's @Scheduled tasks.
Benefits
Prevents Duplicate Execution
Ensures that tasks are not executed concurrently in a distributed setup.
Easy to Use
Simple annotations and configuration make it easy to integrate into existing Spring applications.
Flexible
Supports various backends for lock storage, making it adaptable to different environments.
Step 3: Generate Domain classes based on Schema defined. When you run Maven build, maven-jaxb2-plugin will generate the java files and stores in target/generated-sources/xjc folder. Configure this folder as source folder in your eclipse IDE.
Step 4: Create Web Service Client Configuration class.
Step 5: Create a Service Client class which will consume the web service through above configured WebServiceTemplate class.
BookServiceClient.java
package com.kswaughs.services;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ws.client.core.WebServiceTemplate;
import com.kswaughs.services.booksvc.AddBookRequest;
import com.kswaughs.services.booksvc.AddBookResponse;
import com.kswaughs.services.booksvc.Book;
import com.kswaughs.services.booksvc.GetBookRequest;
import com.kswaughs.services.booksvc.GetBookResponse;
import com.kswaughs.services.booksvc.ObjectFactory;
@Component
public class BookServiceClient {
private static final Logger LOGGER = LoggerFactory.getLogger(BookServiceClient.class);
@Autowired
private WebServiceTemplate webServiceTemplate;
public String addBook(String name, String author, String price) {
ObjectFactory factory = new ObjectFactory();
AddBookRequest req = factory.createAddBookRequest();
Book book = new Book();
book.setAuthor(author);
book.setName(name);
book.setPrice(price);
req.setBook(book);
LOGGER.info("Client sending book[Name={},", book.getName());
AddBookResponse resp = (AddBookResponse) webServiceTemplate.marshalSendAndReceive(req);
LOGGER.info("Client received status='{}'", resp.getStatus());
return resp.getStatus();
}
public Book getBook(String name) {
ObjectFactory factory = new ObjectFactory();
GetBookRequest req = factory.createGetBookRequest();
req.setName(name);
LOGGER.info("Client sending book[Name={},", name);
GetBookResponse resp = (GetBookResponse) webServiceTemplate.marshalSendAndReceive(req);
LOGGER.info("Client received book='{}'", resp.getBook());
return resp.getBook();
}
}
Step 6: To test this application, I am creating a sample class which will reads the user entered commands from console and calls the service client methods.
BookCommandLineListener.java
package com.kswaughs.services;
import java.util.Arrays;
import java.util.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.kswaughs.services.booksvc.Book;
@Component
public class BookCommandLineListener {
private static final Logger LOGGER = LoggerFactory
.getLogger(BookCommandLineListener.class);
@Autowired
private BookServiceClient bookSvcClient;
@Scheduled(fixedDelay=1000)
public void run() throws Exception {
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
LOGGER.info("Enter your command");
String text = scanner.nextLine();
LOGGER.info("you entered :{}", text);
String[] args = text.split("\\s+");
LOGGER.info("args :{}", Arrays.toString(args));
if ("ADD".equalsIgnoreCase(args[0])) {
String name = args[1];
String author = args[2];
String price = args[3];
String status = bookSvcClient.addBook(name, author, price);
LOGGER.info("Book Added Status :{}", status);
} else if ("GET".equalsIgnoreCase(args[0])) {
String name = args[1];
Book book = bookSvcClient.getBook(name);
if(book != null) {
LOGGER.info("GET Book Details : Name:{}, Author:{}, Price:{}",
book.getName(), book.getAuthor(), book.getPrice());
}
} else {
LOGGER.info("Invalid Command.");
}
}
}
}
}
Step 7: Create Spring Boot Main Application Class
SpringBootSoapServiceClientApplication.java
package com.kswaughs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class SpringBootSoapServiceClientApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSoapServiceClientApplication.class, args);
}
}
Step 8: Configure web service url in application.properties
src/main/resources/application.properties
books.svc.url=http://localhost:8088/MyApp/ws
Testing the Application
Test Add method
2017-12-01 11:19:13.376 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : Enter your command
ADD SpringGuide kswaughs 10
2017-12-01 11:20:27.922 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : you entered :ADD SpringGuide kswaughs 10
2017-12-01 11:20:27.922 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : args :[ADD, SpringGuide, kswaughs, 10]
2017-12-01 11:20:27.922 INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient : Client sending book[Name=SpringGuide,
2017-12-01 11:20:28.930 INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient : Client received status='SUCCESS'
2017-12-01 11:20:28.930 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : Book Added Status :SUCCESS
Test Get method
2017-12-01 11:20:28.930 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : Enter your command
GET SpringGuide
2017-12-01 11:20:43.711 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : you entered :GET SpringGuide
2017-12-01 11:20:43.711 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : args :[GET, SpringGuide]
2017-12-01 11:20:43.720 INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient : Client sending book[Name=SpringGuide,
2017-12-01 11:20:43.740 INFO 10440 --- [pool-1-thread-1] com.kswaughs.services.BookServiceClient : Client received book='com.kswaughs.services.booksvc.Book@fa6b61e'
2017-12-01 11:20:43.740 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : GET Book Details : Name:SpringGuide, Author:kswaughs, Price:10
Test invalid command
2017-12-01 11:20:43.740 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : Enter your command
ABCD
2017-12-01 11:20:56.944 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : you entered :ABCD
2017-12-01 11:20:56.944 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : args :[ABCD]
2017-12-01 11:20:56.956 INFO 10440 --- [pool-1-thread-1] c.k.services.BookCommandLineListener : Invalid Command.
This post explains how to develop a soap based web service with Spring Boot. In this Book Store example, We will create a web service that allows to add books information and get the book information.
Step 3: Generate Domain classes based on Schema defined. When you run Maven build, jaxb2-maven-plugin will generate the java files and stores in src/main/java folder.
Step 4: Create a book repository class to store books details and Initialise the list with few books.
BookRepository.java
package com.kswaughs.repo;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.kswaughs.services.booksvc.Book;
@Component
public class BookRepository {
private static final List<Book> books = new ArrayList<Book>();
@PostConstruct
public void initData() {
Book book1 = new Book();
book1.setName("The Family Way");
book1.setAuthor("Tony Parsons");
book1.setPrice("10 $");
books.add(book1);
Book book2 = new Book();
book2.setName("Count To Ten");
book2.setAuthor("Karen Rose");
book2.setPrice("12 $");
books.add(book2);
}
public Book findBook(String name) {
Assert.notNull(name);
Book result = null;
for (Book book : books) {
if (name.equals(book.getName())) {
result = book;
}
}
return result;
}
public void addBook(Book book) {
books.add(book);
}
}
Step 5: Create BookService EndPoint class to handle the incoming SOAP requests.
BookEndPoint.java
package com.kswaughs.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import com.kswaughs.repo.BookRepository;
import com.kswaughs.services.booksvc.AddBookRequest;
import com.kswaughs.services.booksvc.AddBookResponse;
import com.kswaughs.services.booksvc.GetBookRequest;
import com.kswaughs.services.booksvc.GetBookResponse;
@Endpoint
public class BookEndPoint {
private static final String NAMESPACE_URI = "http://com/kswaughs/services/bookSvc";
private BookRepository bookRepository;
@Autowired
public BookEndPoint(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
// To handle getBookRequest
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getBookRequest")
@ResponsePayload
public GetBookResponse getBook(@RequestPayload GetBookRequest request) {
GetBookResponse response = new GetBookResponse();
response.setBook(bookRepository.findBook(request.getName()));
return response;
}
// To handle addBookRequest
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "addBookRequest")
@ResponsePayload
public AddBookResponse addBook(@RequestPayload AddBookRequest request) {
AddBookResponse response = new AddBookResponse();
bookRepository.addBook(request.getBook());
response.setStatus("SUCCESS");
return response;
}
}
Step 6: Configure Web Service Spring beans
SpringWebConfig.java
package com.kswaughs.config;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
@EnableWs
@Configuration
public class SpringWebConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(
ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name = "books")
public DefaultWsdl11Definition defaultBookWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BooksPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://com/kswaughs/services/bookSvc");
wsdl11Definition.setSchema(booksSchema());
return wsdl11Definition;
}
@Bean
public XsdSchema booksSchema() {
return new SimpleXsdSchema(new ClassPathResource("books.xsd"));
}
}
Step 7: Create Spring Boot Main Application Class
BootApp.java
package com.kswaughs.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.kswaughs.*" })
public class BootApp {
public static void main(String[] args) {
SpringApplication.run(BootApp.class, args);
}
}
Step 8: Define application Context path & port in application.properties
WebSocket is a very thin, lightweight layer above TCP used to build interactive web applications that send messages back and forth between a browser and the server.
The best examples are live updates websites, where once user access the website neither user nor browser sends request to the server to get the latest updates. Server only keeps sending the messages to the browser.
In this example, I am building a WebSocket application with Spring Boot using STOMP Messaging to provide the cricket live score updates for every 5 secs.
Create a business logic component to get the live updates from back-end service. In this example, this class initializes the list of batsman objects with pre-filled data and later for every request, it will increments the runs & balls by 1 of any one batsman randomly.
LiveScoreService.java
package com.kswaughs.cricket.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.springframework.stereotype.Component;
import com.kswaughs.cricket.beans.Batsman;
@Component
public class LiveScoreService {
private List<Batsman> scoresList = initialScores();
public List<Batsman> getScore() {
Random rand = new Random();
int randomNum = rand.nextInt((2 - 1) + 1);
Batsman batsman = scoresList.get(randomNum);
batsman.setBalls(batsman.getBalls() + 1);
batsman.setRuns(batsman.getRuns() + 1);
return scoresList;
}
private List<Batsman> initialScores() {
Batsman sachin = new Batsman();
sachin.setName("Sachin Tendulkar");
sachin.setRuns(24);
sachin.setBalls(26);
Batsman ganguly = new Batsman();
ganguly.setName("Sourav Ganguly");
ganguly.setRuns(28);
ganguly.setBalls(30);
List<Batsman> scoresList = new ArrayList<Batsman>();
scoresList.add(sachin);
scoresList.add(ganguly);
return scoresList;
}
}
Step 6: Scheduler Configuration
Configure a Scheduler task to send the updated scores to subscribed channel. The task is configured to run every 5 seconds.
Step 7 : Create a main class which will initialize and runs the spring boot application.
BootApp.java
package com.kswaughs.cricket.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({ "com.kswaughs.cricket" })
public class BootApp {
public static void main(String[] args) {
SpringApplication.run(new Object[] { BootApp.class }, args);
}
}
Testing : AngularJS WebSocket Example
Now we will connect to this application from HTML Page using AngularJS Stomp component and display the live score updates. You can download ng-stomp.standalone.min.js from https://github.com/beevelop/ng-stomp.
This example shows how to read CLOB data returned from Stored Procedure using Spring.
Step 1: Oracle Stored Procedure Setup
Stored Procedure
create or replace PROCEDURE GETARTICLE
(
IN_ARTICLE_ID IN NUMBER,
OUT_ARTICLE_NAME OUT VARCHAR2,
OUT_ARTICLE_CONTENT OUT CLOB
) AS
BEGIN
SELECT ARTICLE_NAME , ARTICLE_CONTENT
INTO OUT_ARTICLE_NAME, OUT_ARTICLE_CONTENT
from ARTICLES WHERE ARTICLE_ID = IN_ARTICLE_ID;
END GETARTICLE;
Step 2: Implement SqlReturnType
Create a new class that implements org.springframework.jdbc.core.SqlReturnType to read CLOB data and convert it into String object.
CLOBToStringConverter.java
package com.kswaughs.util;
import java.io.IOException;
import java.io.Reader;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.SQLException;
import org.springframework.jdbc.core.SqlReturnType;
public class CLOBToStringConverter implements SqlReturnType {
@Override
public Object getTypeValue(CallableStatement cs,
int paramIndex, int sqlType, String typeName) throws SQLException {
Clob aClob = cs.getClob(paramIndex);
final Reader clobReader = aClob.getCharacterStream();
int length = (int) aClob.length();
char[] inputBuffer = new char[1024];
final StringBuilder outputBuffer = new StringBuilder();
try {
while ((length = clobReader.read(inputBuffer)) != -1)
{
outputBuffer.append(inputBuffer, 0, length);
}
} catch (IOException e) {
throw new SQLException(e.getMessage());
}
return outputBuffer.toString();
}
}
Step 3: DAO Implementation
Write a DAO class that extends org.springframework.jdbc.object.StoredProcedure. While declaring the OUT parameters in constructor, pass the above CLOBToStringConverter as parameter to SqlOutParameter.
If we pass typeName as Empty String to SqlOutParameter as below,
declareParameter(new SqlOutParameter("OUT_ARTICLE_CONTENT", Types.CLOB,
"", new CLOBToStringConverter()));
we will get below SQLException
Error occured while executing the Stored procedure. CallableStatementCallback; uncategorized SQLException for SQL [{call GETARTICLE(?, ?, ?)}]; SQL state [99999]; error code [17060]; Fail to construct descriptor: empty Object name; nested exception is java.sql.SQLException: Fail to construct descriptor: empty Object name at com.kswaughs.dao.ArticleDAO.getArticleContent.
Spring MVC Framework provides pagination capabilities for web pages with PagedListHolder class. In this example, we will see how to develop an online phone store application that displays list of the phones available with a specified number of phones and allows user to choose the next or previous list including required page index. Follow the below steps in developing this application.
1. Maven Dependencies
2. Define Service & model classes
3. Define pagination logic in Controller
4. Define Spring MVC Configuration
5. Servlet Configuration
6. JSPs implementation
Spring MVC Framework has built in methods and components to render the output in many documents like PDF, EXCEL along with JSPs. In this example, we will see how to configure and develop our spring based application to allow users to download the web content in PDF document.
We have an online store application where the home page displays list of the phones available and allows user to choose a phone to get its details. After submission, price details are shown in the next page along with a download link to view the page in PDF format. Now, we will follow the below steps in developing this application.
1. Maven Dependencies
2. Define Controllers, Service & model classes
3. Define Custom PDF View
4. View Configuration
5. Servlet Configuration
6. JSPs implementation
1. Maven Dependencies
pom.xml
<dependencies>
<!--Spring framework related jars-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.10.RELEASE</version>
</dependency>
<!--Itext jar for PDF generation-->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
2. Define Controllers, Service & model classes
Create a Controller class to handle the requests based on the URL mappings.
package com.kswaughs.web.beans;
public class Phone {
private String id;
private String name;
private String price;
public Phone() {
}
public Phone(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
3. Define Custom PDF View Component
Create a new class that extends AbstractPdfView and override buildPdfDocument() method with data from session object.
PDFBuilder
package com.kswaughs.web.views;
import java.awt.Color;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractPdfView;
import com.lowagie.text.Document;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.kswaughs.web.beans.Phone;
@Component
public class PDFBuilder extends AbstractPdfView {
@Override
protected void buildPdfDocument(Map<String, Object> model, Document doc,
PdfWriter writer, HttpServletRequest req, HttpServletResponse resp)
throws Exception {
Phone ph = (Phone) req.getSession().getAttribute("phone");
doc.add(new Paragraph(
"Dear User, Following is the list of available phones in our shop."));
List<Phone> phonesList = (List<Phone>) model.get("phonesList");
PdfPTable table = new PdfPTable(2);
table.setSpacingBefore(10);
// define font for table header row
Font font = FontFactory.getFont(FontFactory.HELVETICA);
font.setColor(Color.WHITE);
// define table header cell
PdfPCell cell = new PdfPCell();
cell.setBackgroundColor(Color.BLUE);
cell.setPadding(5);
cell.setPhrase(new Phrase("ID", font));
table.addCell(cell);
cell.setPhrase(new Phrase("Name", font));
table.addCell(cell);
for (Phone phone : phonesList) {
table.addCell(phone.getId());
table.addCell(phone.getName());
}
doc.add(table);
doc.add(new Paragraph(
"Please find the price details of phone you have selected."));
Font priceTxtFont = new Font();
priceTxtFont.setColor(Color.BLUE);
doc.add(new Paragraph("Phone :" + ph.getName(), priceTxtFont));
doc.add(new Paragraph("Price :" + ph.getPrice() + "Rs/-", priceTxtFont));
}
}
4. View Configuration
Add below entry in 'views.properties' file which should be placed in src/main/resources folder. The view name 'pdfView' is mapped to PDFBuilder View component.
views.properties
pdfView.(class)=com.poc.web.views.PDFBuilder
Configure a new ViewResolver with this properties file in Java Configuration
In this example, We will see how to run that Batch job with spring scheduler using spring boot.
Step 1 : By default, the batch job runs immediately when we start the application. So we have to disable the auto run feature in application.properties file.
spring.batch.job.enabled=false
Step 2 : Configure JobLauncher in one more configuration class with defining required dependant components.
BatchScheduler
package com.kswaughs.config;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling
public class BatchScheduler {
@Bean
public ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
@Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(
ResourcelessTransactionManager txManager) throws Exception {
MapJobRepositoryFactoryBean factory = new
MapJobRepositoryFactoryBean(txManager);
factory.afterPropertiesSet();
return factory;
}
@Bean
public JobRepository jobRepository(
MapJobRepositoryFactoryBean factory) throws Exception {
return factory.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
return launcher;
}
}
Step 3 : Follow the below methods
Import the above Configuration class to your batch configuration class.
Get the reference of the configured JobLauncher through autowired injection.
Write a new method annotated with @Scheduled and desired cron expression.
Run the JobLauncher with passing job bean and custom job parameter.
BatchConfiguration
package com.kswaughs.config;
import java.util.Date;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
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.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Scheduled;
import com.kswaughs.beans.Order;
import com.kswaughs.beans.SvcReq;
import com.kswaughs.order.OrderSvcInvoker;
import com.kswaughs.processor.JobCompletionNotificationListener;
import com.kswaughs.processor.OrderItemProcessor;
@Configuration
@EnableBatchProcessing
@Import({BatchScheduler.class})
public class BatchConfiguration {
@Autowired
private SimpleJobLauncher jobLauncher;
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Scheduled(cron = "1 53/3 17 * * ?")
public void perform() throws Exception {
System.out.println("Job Started at :" + new Date());
JobParameters param = new JobParametersBuilder().addString("JobID",
String.valueOf(System.currentTimeMillis())).toJobParameters();
JobExecution execution = jobLauncher.run(processOrderJob(), param);
System.out.println("Job finished with status :" + execution.getStatus());
}
@Bean
public Job processOrderJob() {
return jobBuilderFactory.get("processOrderJob")
.incrementer(new RunIdIncrementer())
.listener(listener())
.flow(orderStep()).end().build();
}
@Bean
public Step orderStep() {
return stepBuilderFactory.get("orderStep").<Order, SvcReq> chunk(3)
.reader(reader()).processor(processor()).writer(writer())
.build();
}
@Bean
public FlatFileItemReader<Order> reader() {
FlatFileItemReader<Order> reader = new FlatFileItemReader<Order>();
reader.setResource(new ClassPathResource("PhoneData.csv"));
reader.setLineMapper(new DefaultLineMapper<Order>() {
{
setLineTokenizer(new DelimitedLineTokenizer() {
{
setNames(new String[] { "orderID", "orderName" });
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Order>() {
{
setTargetType(Order.class);
}
});
}
});
return reader;
}
@Bean
public OrderItemProcessor processor() {
return new OrderItemProcessor();
}
@Bean
public ItemWriter<SvcReq> writer() {
return new OrderSvcInvoker();
}
@Bean
public JobExecutionListener listener() {
return new JobCompletionNotificationListener();
}
}
In this example, I configured the job to start at '5 PM 53 minutes 1 second' and run for every 3 minutes till 6 PM with cron expression.
Output console logs
Job Started at :Tue Mar 22 17:53:01 IST 2016
2016-03-22 17:53:01.052 INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] launched with the following parameters: [{JobID=1458649381004}]
2016-03-22 17:53:01.078 INFO 10932 --- [pool-2-thread-1] o.s.batch.core.job.SimpleStepHandler: Executing step: [orderStep]
Converting (Order [orderID=101, orderName=Apple IPhone]) into (SvcReq [id=101, name=APPLE IPHONE])
Converting (Order [orderID=102, orderName=Samsung Galaxy Y]) into (SvcReq [id=102, name=SAMSUNG GALAXY Y])
Converting (Order [orderID=103, orderName=Moto E]) into (SvcReq [id=103, name=MOTO E])
calling web service:SvcResp [id=101, message=APPLE IPHONE Processed successfully]
calling web service:SvcResp [id=102, message=SAMSUNG GALAXY Y Processed successfully]
calling web service:SvcResp [id=103, message=MOTO E Processed successfully]
Processed items:3
Converting (Order [orderID=104, orderName=Moto X]) into (SvcReq [id=104, name=MOTO X])
Converting (Order [orderID=105, orderName=Yuphoria]) into (SvcReq [id=105, name=YUPHORIA])
calling web service:SvcResp [id=104, message=MOTO X Processed successfully]
calling web service:SvcResp [id=105, message=YUPHORIA Processed successfully]
Processed items:2
BATCH JOB FINISHED SUCCESSFULLY
2016-03-22 17:53:01.350 INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] completed with the following parameters: [{JobID=1458649381004}] and the following status: [COMPLETED]
Job finished with status :COMPLETED
Job Started at :Tue Mar 22 17:56:00 IST 2016
2016-03-22 17:56:01.006 INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] launched with the following parameters: [{JobID=1458649560996}]
2016-03-22 17:56:01.031 INFO 10932 --- [pool-2-thread-1] o.s.batch.core.job.SimpleStepHandler: Executing step: [orderStep]
Converting (Order [orderID=101, orderName=Apple IPhone]) into (SvcReq [id=101, name=APPLE IPHONE])
Converting (Order [orderID=102, orderName=Samsung Galaxy Y]) into (SvcReq [id=102, name=SAMSUNG GALAXY Y])
Converting (Order [orderID=103, orderName=Moto E]) into (SvcReq [id=103, name=MOTO E])
calling web service:SvcResp [id=101, message=APPLE IPHONE Processed successfully]
calling web service:SvcResp [id=102, message=SAMSUNG GALAXY Y Processed successfully]
calling web service:SvcResp [id=103, message=MOTO E Processed successfully]
Processed items:3
Converting (Order [orderID=104, orderName=Moto X]) into (SvcReq [id=104, name=MOTO X])
Converting (Order [orderID=105, orderName=Yuphoria]) into (SvcReq [id=105, name=YUPHORIA])
calling web service:SvcResp [id=104, message=MOTO X Processed successfully]
calling web service:SvcResp [id=105, message=YUPHORIA Processed successfully]
Processed items:2
BATCH JOB FINISHED SUCCESSFULLY
2016-03-22 17:56:01.154 INFO 10932 --- [pool-2-thread-1] o.s.b.c.l.support.SimpleJobLauncher: Job: [FlowJob: [name=processOrderJob]] completed with the following parameters: [{JobID=1458649560996}] and the following status: [COMPLETED]
Job finished with status :COMPLETED