Batch Inserts & Updates
AdvancedEnable hibernate.jdbc.batch_size and use SEQUENCE ID generation (not IDENTITY) to allow Hibernate to batch INSERT/UPDATE statements, drastically reducing round-trips.
Overview
By default, Hibernate sends one INSERT/UPDATE statement per entity to the database — even when persisting a thousand records in a loop. This means 1000 individual round-trips for 1000 entities. **JDBC batching** groups multiple statements into a single network round-trip, dramatically reducing latency. Enable it with `hibernate.jdbc.batch_size`. Critical prerequisite: **use `SEQUENCE` or `TABLE` ID generation** — NOT `IDENTITY` (auto-increment). Hibernate cannot batch INSERTs with `IDENTITY` because it needs the generated ID from the DB after each INSERT to populate the entity. Optionally combine with `saveAll()` (Spring Data) and periodic `flush() + clear()` to avoid `OutOfMemoryError` on very large datasets.
Enabling JDBC Batching
Set `hibernate.jdbc.batch_size` in application.properties. For ordered batching (all INSERTs before all UPDATEs, improving cache efficiency), enable `hibernate.order_inserts` and `hibernate.order_updates`. Must use `SEQUENCE` ID generation — `IDENTITY` silently disables batching.
# application.properties
spring.jpa.properties.hibernate.jdbc.batch_size=50 # batch up to 50 statements
spring.jpa.properties.hibernate.order_inserts=true # group all INSERTs together
spring.jpa.properties.hibernate.order_updates=true # group all UPDATEs together
spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true # batch versioned (optimistic lock) entities
# Required: use SEQUENCE (not IDENTITY) for the ID strategy
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "product_seq")
@SequenceGenerator(name = "product_seq",
sequenceName = "product_id_seq",
allocationSize = 50) // pre-allocate 50 IDs per call — efficient
Long id;
// ...
}
# Why IDENTITY breaks batching:
# INSERT INTO product ... → DB returns generated ID
# Hibernate needs the ID immediately to set entity.id → cannot batchBatch Insert Loop with Flush and Clear
When persisting large volumes, the persistence context grows unboundedly — all managed entities are held in memory. Periodically flush and clear the persistence context to keep memory constant. `flush()` sends the current batch to the DB; `clear()` detaches all entities from the session.
@Transactional
public void bulkImport(List<ProductCsvRow> rows) {
int batchSize = 50; // must match hibernate.jdbc.batch_size
for (int i = 0; i < rows.size(); i++) {
Product product = mapper.toEntity(rows.get(i));
entityManager.persist(product);
if (i > 0 && i % batchSize == 0) {
entityManager.flush(); // send the batch of 50 to DB
entityManager.clear(); // detach all — free memory
}
}
// final flush for remaining entities
entityManager.flush();
entityManager.clear();
}
// Spring Data equivalent: use saveAll() which calls persist() in a loop
// + configure batch_size → Spring Data does NOT auto-flush mid-loop
// so only practical for < few thousand rows without manual flush/clearVerifying Batching with Statistics
Enable Hibernate statistics and the JDBC batch logging to verify batching is actually happening. Without `order_inserts`, mixed INSERT/UPDATE statements in the same session prevent batching. The log should show `"batch update returned unexpected row count from update [0]; actual row count: X"` if something went wrong, or the statistics counter `getPrepareStatementCount()` should be much lower than the entity count.
# Verify batching is active
spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.engine.jdbc.batch.internal.BatchingBatch=DEBUG
logging.level.org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl=TRACE
# Log output with batching ON:
# DEBUG: Executing batch size: 50
# DEBUG: Executing batch size: 50
# → 1000 entities = 20 SQL calls (rounds of 50)
# Log output with batching OFF (IDENTITY strategy):
# DEBUG: Executing batch size: 1 (repeated 1000 times)
@Autowired SessionFactory sessionFactory;
@Test
void shouldBatchInserts() {
Statistics stats = sessionFactory.getStatistics();
stats.setStatisticsEnabled(true);
stats.clear();
importService.bulkImport(generate(500));
// With batch_size=50: ~10 prepare statements, not 500
assertThat(stats.getPrepareStatementCount()).isLessThan(20);
}Key Points to Remember
- 1hibernate.jdbc.batch_size groups multiple INSERT/UPDATE into one network round-trip
- 2IDENTITY (auto-increment) ID generation silently DISABLES batching — use SEQUENCE instead
- 3allocationSize on @SequenceGenerator pre-allocates IDs in bulk — reduces DB sequence calls
- 4order_inserts + order_updates groups statements by type for better batch efficiency
- 5Flush + clear every N entities to keep memory constant during large bulk imports
- 6Verify batching with statistics: getPrepareStatementCount() should be rows/batch_size, not rows
Interview Questions
Sign in to ask AriaWhy does using GenerationType.IDENTITY prevent Hibernate from batching INSERT statements?
What does hibernate.order_inserts do and why is it important for batching?
How would you bulk-import 100,000 records using Hibernate without running out of memory?
What is allocationSize in @SequenceGenerator and how does it improve performance?
How would you verify that JDBC batching is actually working in a Spring Boot application?
Ask Aria about Batch Inserts & Updates
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.