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.
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.
- Flyway migrations for schema setup.
- PostgreSQL local environment via Docker Compose.
Userentity +UserRepository.- Custom repository fragment:
BulkRepository<T>withinsertBatch(...)BulkRepositoryImpl<T>usingJdbcTemplate.batchUpdate(...)
- Benchmark endpoints:
POST /api/users/benchmark/save-allPOST /api/users/benchmark/insert-batch
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.
This project uses the repository fragment approach from Spring Data:
- Define a fragment interface:
BulkRepository<T>. - Provide implementation:
BulkRepositoryImpl<T>. - Register fragment in
META-INF/spring.factories. - Extend your repository with the fragment interface:
UserRepository extends ListCrudRepository<User, Long>, BulkRepository<User>
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 (
@Repositoryor@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.
- 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
idare skipped withWARNlog (batch insert expects new entities). - Values are bound with prepared statement placeholders (
?), not string-injected values.
src/main/java/dev/platforma/b2b/springdataext/repository/BulkRepository.javasrc/main/java/dev/platforma/b2b/springdataext/repository/BulkRepositoryImpl.javasrc/main/resources/META-INF/spring.factoriessrc/main/resources/db/migration/V1__create_user_table.sqlsrc/main/java/dev/platforma/b2b/springdataext/user/UserRepository.javasrc/main/java/dev/platforma/b2b/springdataext/user/UserController.java
cd sandbox
docker compose up -d./gradlew bootRunThe 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
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- Custom repository implementations and fragments:
Spring Data Relational - Custom Repository Implementations