Skip to content

Ulllie/spring-data-ext

Repository files navigation

Language

EN | RU

Spring Data JDBC Batch Insert Extension

This project demonstrates how to extend Spring Data(by example - JDBC) repositories with a custom batch insert method so developers do not rely on saveAll(...) for large inserts.

Why

saveAll(...) is convenient, but for large collections it can be significantly slower than a dedicated JDBC batch insert strategy.

This repo adds a reusable repository extension with insertBatch(...) for high-volume inserts.

What is implemented

  • Flyway migrations for schema setup.
  • PostgreSQL local environment via Docker Compose.
  • User entity + UserRepository.
  • Custom repository fragment:
    • BulkRepository<T> with insertBatch(...)
    • BulkRepositoryImpl<T> using JdbcTemplate.batchUpdate(...)
  • Benchmark endpoints:
    • POST /api/users/benchmark/save-all
    • POST /api/users/benchmark/insert-batch

Local benchmark example

Local DB results from this project run:

Strategy Users Generation (ms) Insert (ms) Total (ms)
saveAll 5000 5 232 238
insertBatch 5000 5 58 67

On a remote database, the gap is often larger because network round-trips amplify inefficient insert patterns.

How the Spring Data extension works

This project uses the repository fragment approach from Spring Data:

  1. Define a fragment interface: BulkRepository<T>.
  2. Provide implementation: BulkRepositoryImpl<T>.
  3. Register fragment in META-INF/spring.factories.
  4. Extend your repository with the fragment interface:
    • UserRepository extends ListCrudRepository<User, Long>, BulkRepository<User>

Alternative to META-INF/spring.factories

Instead of spring.factories, you can rely on Spring Data fragment autodetection:

  • Keep naming convention: BulkRepository -> BulkRepositoryImpl
  • Keep implementation in repository-scanned packages
  • Optionally annotate implementation as a Spring bean (@Repository or @Component)

This can work well inside a single application module.
For reusable/shared extensions (for example in another module/JAR), META-INF/spring.factories is usually a more explicit and robust option.

Internal details in insertBatch(...)

  • SQL is generated from Spring Data mapping metadata (table/column names).
  • Insert plan (SQL + mapped properties) is cached per domain type.
  • Entities with non-null id are skipped with WARN log (batch insert expects new entities).
  • Values are bound with prepared statement placeholders (?), not string-injected values.

Project structure (important files)

  • src/main/java/dev/platforma/b2b/springdataext/repository/BulkRepository.java
  • src/main/java/dev/platforma/b2b/springdataext/repository/BulkRepositoryImpl.java
  • src/main/resources/META-INF/spring.factories
  • src/main/resources/db/migration/V1__create_user_table.sql
  • src/main/java/dev/platforma/b2b/springdataext/user/UserRepository.java
  • src/main/java/dev/platforma/b2b/springdataext/user/UserController.java

Run locally

1) Start PostgreSQL

cd sandbox
docker compose up -d

2) Run the app

./gradlew bootRun

The app uses defaults from application.yaml:

  • DB URL: jdbc:postgresql://localhost:5432/spring_data_ext
  • Username: postgres
  • Password: postgres
  • Flyway enabled on startup.
  • PostgreSQL optimization enabled: reWriteBatchedInserts=true

Benchmark endpoints

Run each endpoint and compare results:

curl -X POST http://localhost:8080/api/users/benchmark/save-all
curl -X POST http://localhost:8080/api/users/benchmark/insert-batch

Spring Data documentation

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages