The 1-minute fix that slashed my API response time by 40%.
We all know @Transactional makes database work easy. But are you using it correctly?
Here is a mistake I see in 8 out of 10 code reviews:
❌ The Anti-Pattern:
@Service
public class OrderService {
@Transactional
public Order placeOrder(OrderDto dto) {
Order order = new Order();
// 1. Save the parent
orderRepository.save(order);
// 2. Loop and save children one by one
for (Item i : dto.getItems()) {
Item item = new Item();
item.setOrder(order);
itemRepository.save(item); // 💀 N+1 problem + multiple flush calls
}
return order;
}
}
Enter fullscreen mode Exit fullscreen mode
The Problem: Even with @Transactional, calling .save() in a loop forces Hibernate to hit the database for every single item. That’s 50 round trips for 50 items.
✅ The Game-Changer:
Use Batch Insertion with a simple property change.
1.Add this to application.properties:
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
Enter fullscreen mode Exit fullscreen mode
2.Refactor your code to use .saveAll():
@Transactional
public Order placeOrder(OrderDto dto) {
Order order = new Order();
orderRepository.save(order); // Save parent first
List<Item> items = dto.getItems().stream()
.map(i -> new Item(i, order))
.collect(Collectors.toList());
itemRepository.saveAll(items); // ONE single INSERT statement for all items!
return order;
}
Enter fullscreen mode Exit fullscreen mode
The Result:
- Before: 51 SQL queries for 50 items.
- After: 2 SQL queries for 50 items.
- Performance: 40% faster response time.
- DB Load: Drastically reduced.
The takeaway: @Transactional manages the transaction, but it doesn’t batch your queries. You have to explicitly tell Hibernate to batch them.
Discussion Question: What’s the one Spring Boot property you can’t live without? Drop it in the comments! 👇