diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml index ee4807933a..612070ea10 100644 --- a/persistence-modules/pom.xml +++ b/persistence-modules/pom.xml @@ -105,6 +105,7 @@ spring-jooq spring-mybatis spring-persistence-simple + spring-jdbc-batch diff --git a/persistence-modules/spring-jdbc-batch/pom.xml b/persistence-modules/spring-jdbc-batch/pom.xml new file mode 100644 index 0000000000..0401044be1 --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/pom.xml @@ -0,0 +1,60 @@ + + + + + 4.0.0 + + spring-jdbc-batch + 0.0.1-SNAPSHOT + spring-jdbc-batch + Demo project for Spring Boot Jdbc batch support + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + 11 + + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/SpringJdbcBatchPerformanceApplication.java b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/SpringJdbcBatchPerformanceApplication.java new file mode 100644 index 0000000000..aef2100de8 --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/SpringJdbcBatchPerformanceApplication.java @@ -0,0 +1,43 @@ +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; + +@SpringBootApplication +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("-".repeat(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-batch/src/main/java/com/baeldung/spring/jdbc/batch/config/AppConfig.java b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/config/AppConfig.java new file mode 100644 index 0000000000..b0fd111ed2 --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/config/AppConfig.java @@ -0,0 +1,24 @@ +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 java.time.Clock; +import java.util.Random; + +@Configuration +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-batch/src/main/java/com/baeldung/spring/jdbc/batch/model/Product.java b/persistence-modules/spring-jdbc-batch/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-batch/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-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/BatchProductRepository.java b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/BatchProductRepository.java new file mode 100644 index 0000000000..d4a4affd0a --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/BatchProductRepository.java @@ -0,0 +1,33 @@ +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-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/ProductRepository.java b/persistence-modules/spring-jdbc-batch/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-batch/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-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/SimpleProductRepository.java b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/SimpleProductRepository.java new file mode 100644 index 0000000000..3dfb998cba --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/repo/SimpleProductRepository.java @@ -0,0 +1,29 @@ +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-batch/src/main/java/com/baeldung/spring/jdbc/batch/service/ProductService.java b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/service/ProductService.java new file mode 100644 index 0000000000..6a3758c07a --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/src/main/java/com/baeldung/spring/jdbc/batch/service/ProductService.java @@ -0,0 +1,54 @@ +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; + } + + protected 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()); + product.setPrice(prices[random.nextInt(4)]); + product.setTitle(titles[random.nextInt(4)]); + products.add(product); + } + return products; + } +} diff --git a/persistence-modules/spring-jdbc-batch/src/test/java/com/baeldung/spring/jdbc/batch/service/ProductServiceUnitTest.java b/persistence-modules/spring-jdbc-batch/src/test/java/com/baeldung/spring/jdbc/batch/service/ProductServiceUnitTest.java new file mode 100644 index 0000000000..b242eaa335 --- /dev/null +++ b/persistence-modules/spring-jdbc-batch/src/test/java/com/baeldung/spring/jdbc/batch/service/ProductServiceUnitTest.java @@ -0,0 +1,26 @@ +package com.baeldung.spring.jdbc.batch.service; + +import com.baeldung.spring.jdbc.batch.repo.ProductRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.Clock; +import java.util.Random; + +class ProductServiceUnitTest { + + @Mock + ProductRepository productRepository; + @Mock + Random random; + @Mock + Clock clock; + @InjectMocks + ProductService productService; + + @Test + void testWhenThen() { + + } +} \ No newline at end of file