diff --git a/persistence-modules/spring-jdbc/pom.xml b/persistence-modules/spring-jdbc/pom.xml
index 28a858dd43..08e43e8292 100644
--- a/persistence-modules/spring-jdbc/pom.xml
+++ b/persistence-modules/spring-jdbc/pom.xml
@@ -31,6 +31,11 @@
mysql-connector-java
runtime
+
+ org.postgresql
+ postgresql
+ runtime
+
\ No newline at end of file
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/SpringJdbcBatchPerformanceApplication.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/SpringJdbcBatchPerformanceApplication.java
new file mode 100644
index 0000000000..d523717118
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/SpringJdbcBatchPerformanceApplication.java
@@ -0,0 +1,48 @@
+package com.baeldung.spring.jdbc.batch;
+
+import com.baeldung.spring.jdbc.batch.service.ProductService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+import java.util.Collections;
+
+@SpringBootApplication
+@ComponentScan(basePackages = "com.baeldung.spring.jdbc.batch")
+public class SpringJdbcBatchPerformanceApplication implements CommandLineRunner {
+
+ @Autowired
+ @Qualifier("batchProductService")
+ private ProductService batchProductService;
+ @Autowired
+ @Qualifier("simpleProductService")
+ private ProductService simpleProductService;
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringJdbcBatchPerformanceApplication.class, args);
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ int[] recordCounts = { 1, 10, 100, 1000, 10_000, 100_000, 1000_000 };
+
+ for (int recordCount : recordCounts) {
+ long regularElapsedTime = simpleProductService.createProducts(recordCount);
+ long batchElapsedTime = batchProductService.createProducts(recordCount);
+
+ System.out.println(String.join("", Collections.nCopies(50, "-")));
+ System.out.format("%-20s%-5s%-10s%-5s%8sms\n", "Regular inserts", "|", recordCount, "|", regularElapsedTime);
+ System.out.format("%-20s%-5s%-10s%-5s%8sms\n", "Batch inserts", "|", recordCount, "|", batchElapsedTime);
+ System.out.printf("Total gain: %d %s\n", calculateGainInPercent(regularElapsedTime, batchElapsedTime), "%");
+ }
+
+ }
+
+ int calculateGainInPercent(long before, long after) {
+ return (int) Math.floor(100D * (before - after) / before);
+ }
+}
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/config/AppConfig.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/config/AppConfig.java
new file mode 100644
index 0000000000..1dd7c63adc
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/config/AppConfig.java
@@ -0,0 +1,27 @@
+package com.baeldung.spring.jdbc.batch.config;
+
+import com.baeldung.spring.jdbc.batch.repo.BatchProductRepository;
+import com.baeldung.spring.jdbc.batch.repo.SimpleProductRepository;
+import com.baeldung.spring.jdbc.batch.service.ProductService;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+import java.time.Clock;
+import java.util.Random;
+
+@Configuration
+@PropertySource("classpath:com/baeldung/spring/jdbc/batch/application.properties")
+public class AppConfig {
+
+ @Bean
+ public ProductService simpleProductService(SimpleProductRepository simpleProductRepository) {
+ return new ProductService(simpleProductRepository, new Random(), Clock.systemUTC());
+ }
+
+ @Bean
+ public ProductService batchProductService(BatchProductRepository batchProductRepository) {
+ return new ProductService(batchProductRepository, new Random(), Clock.systemUTC());
+ }
+}
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/model/Product.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/model/Product.java
new file mode 100644
index 0000000000..6454952fdc
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/model/Product.java
@@ -0,0 +1,54 @@
+package com.baeldung.spring.jdbc.batch.model;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+public class Product {
+ private long id;
+ private String title;
+ private LocalDateTime createdTs;
+ private BigDecimal price;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public LocalDateTime getCreatedTs() {
+ return createdTs;
+ }
+
+ public void setCreatedTs(LocalDateTime createdTs) {
+ this.createdTs = createdTs;
+ }
+
+ public BigDecimal getPrice() {
+ return price;
+ }
+
+ public void setPrice(BigDecimal price) {
+ this.price = price;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Product{");
+ sb.append("id=").append(id);
+ sb.append(", title='").append(title).append('\'');
+ sb.append(", createdTs=").append(createdTs);
+ sb.append(", price=").append(price);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/BatchProductRepository.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/BatchProductRepository.java
new file mode 100644
index 0000000000..9b4f0208ab
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/BatchProductRepository.java
@@ -0,0 +1,34 @@
+package com.baeldung.spring.jdbc.batch.repo;
+
+import com.baeldung.spring.jdbc.batch.model.Product;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.PreparedStatement;
+import java.sql.Timestamp;
+import java.util.List;
+
+@Repository
+public class BatchProductRepository implements ProductRepository {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public BatchProductRepository(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @Override
+ @Transactional
+ public void saveAll(List products) {
+ jdbcTemplate.batchUpdate("INSERT INTO PRODUCT (TITLE, CREATED_TS, PRICE) VALUES (?, ?, ?)",
+ products,
+ 100,
+ (PreparedStatement ps, Product product) -> {
+ ps.setString(1, product.getTitle());
+ ps.setTimestamp(2, Timestamp.valueOf(product.getCreatedTs()));
+ ps.setBigDecimal(3, product.getPrice());
+ });
+ }
+}
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/ProductRepository.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/ProductRepository.java
new file mode 100644
index 0000000000..ed193f87dd
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/ProductRepository.java
@@ -0,0 +1,9 @@
+package com.baeldung.spring.jdbc.batch.repo;
+
+import com.baeldung.spring.jdbc.batch.model.Product;
+
+import java.util.List;
+
+public interface ProductRepository {
+ void saveAll(List products);
+}
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/SimpleProductRepository.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/SimpleProductRepository.java
new file mode 100644
index 0000000000..4a381dd8b9
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/repo/SimpleProductRepository.java
@@ -0,0 +1,30 @@
+package com.baeldung.spring.jdbc.batch.repo;
+
+import com.baeldung.spring.jdbc.batch.model.Product;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Repository
+public class SimpleProductRepository implements ProductRepository {
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public SimpleProductRepository(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @Override
+ @Transactional
+ public void saveAll(List products) {
+ for (Product product : products) {
+ jdbcTemplate.update("INSERT INTO PRODUCT (TITLE, CREATED_TS, PRICE) VALUES (?, ?, ?)",
+ product.getTitle(), Timestamp.valueOf(product.getCreatedTs()), product.getPrice());
+ }
+ }
+
+}
diff --git a/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/service/ProductService.java b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/service/ProductService.java
new file mode 100644
index 0000000000..436764ffcf
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/java/com/baeldung/spring/jdbc/batch/service/ProductService.java
@@ -0,0 +1,55 @@
+package com.baeldung.spring.jdbc.batch.service;
+
+import com.baeldung.spring.jdbc.batch.model.Product;
+import com.baeldung.spring.jdbc.batch.repo.ProductRepository;
+
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class ProductService {
+
+ private final ProductRepository productRepository;
+ private final Random random;
+ private final Clock clock;
+
+ public ProductService(ProductRepository productRepository, Random random, Clock clock) {
+ this.productRepository = productRepository;
+ this.random = random;
+ this.clock = clock;
+ }
+
+ @Transactional
+ public long createProducts(int count) {
+ List products = generate(count);
+ long startTime = clock.millis();
+ productRepository.saveAll(products);
+ return clock.millis() - startTime;
+ }
+
+ private List generate(int count) {
+ final String[] titles = { "car", "plane", "house", "yacht" };
+ final BigDecimal[] prices = {
+ new BigDecimal("12483.12"),
+ new BigDecimal("8539.99"),
+ new BigDecimal("88894"),
+ new BigDecimal("458694")
+ };
+
+ final List products = new ArrayList<>(count);
+
+ for (int i = 0; i < count; i++) {
+ Product product = new Product();
+ product.setCreatedTs(LocalDateTime.now(clock));
+ product.setPrice(prices[random.nextInt(4)]);
+ product.setTitle(titles[random.nextInt(4)]);
+ products.add(product);
+ }
+ return products;
+ }
+}
diff --git a/persistence-modules/spring-jdbc/src/main/resources/com/baeldung/spring/jdbc/batch/application.properties b/persistence-modules/spring-jdbc/src/main/resources/com/baeldung/spring/jdbc/batch/application.properties
new file mode 100644
index 0000000000..9898ae022b
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/main/resources/com/baeldung/spring/jdbc/batch/application.properties
@@ -0,0 +1,5 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/sample-baeldung-db
+spring.datasource.username=postgres
+spring.datasource.password=root
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.hikari.data-source-properties.reWriteBatchedInserts=true
\ No newline at end of file
diff --git a/persistence-modules/spring-jdbc/src/test/java/com/baeldung/spring/jdbc/batch/service/ProductServiceUnitTest.java b/persistence-modules/spring-jdbc/src/test/java/com/baeldung/spring/jdbc/batch/service/ProductServiceUnitTest.java
new file mode 100644
index 0000000000..26a5f64dc0
--- /dev/null
+++ b/persistence-modules/spring-jdbc/src/test/java/com/baeldung/spring/jdbc/batch/service/ProductServiceUnitTest.java
@@ -0,0 +1,76 @@
+package com.baeldung.spring.jdbc.batch.service;
+
+import com.baeldung.spring.jdbc.batch.model.Product;
+import com.baeldung.spring.jdbc.batch.repo.ProductRepository;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.Random;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+@ExtendWith(MockitoExtension.class)
+class ProductServiceUnitTest {
+
+ ProductRepository productRepository;
+ Random random;
+ Clock clock;
+ ProductService productService;
+
+ @Captor
+ ArgumentCaptor> proArgumentCaptor;
+
+ @BeforeEach
+ void setUp() {
+ this.productRepository = mock(ProductRepository.class);
+ this.random = mock(Random.class, withSettings().withoutAnnotations());
+ this.clock = mock(Clock.class);
+ this.productService = new ProductService(this.productRepository, this.random, this.clock);
+ }
+
+ @Test
+ void testWhenCreateProductsThenShouldSaveAndReturnElapsedTime() {
+ when(random.nextInt(4))
+ .thenReturn(1, 3, 2, 0);
+ when(clock.instant())
+ .thenReturn(Instant.parse("2022-04-09T10:15:30.00Z"));
+ when(clock.getZone())
+ .thenReturn(ZoneId.of("UTC"));
+
+ when(clock.millis())
+ .thenReturn(100L,500L);
+
+ final long actualElapsedTime = productService.createProducts(2);
+
+ assertThat(actualElapsedTime)
+ .isEqualTo(400L);
+
+ verify(productRepository,times(1))
+ .saveAll(proArgumentCaptor.capture());
+
+ assertThat(proArgumentCaptor.getValue())
+ .hasSize(2)
+ .extracting("title", "createdTs", "price")
+ .containsExactly(
+ tuple("yacht", LocalDateTime.parse("2022-04-09T10:15:30"), new BigDecimal("8539.99")),
+ tuple("car", LocalDateTime.parse("2022-04-09T10:15:30"), new BigDecimal("88894"))
+ );
+ }
+}
\ No newline at end of file