Transactions with @Transactional
Use Spring transactions properly and avoid the two mistakes that silently disable them.
Open this lesson in the learning hubKey points
- A persistence context lives and dies with a transaction. No transaction means no dirty checking and no lazy loading.
- Annotate the service method that represents one unit of work, not every repository call.
- Spring wraps the bean in a proxy. Calling an annotated method from inside the same class skips the proxy, so no transaction starts.
- Rollback is automatic for
RuntimeExceptionandErroronly. For a checked exception you must addrollbackFor. readOnly = trueskips dirty-checking work and hints the database. Use it on every query-only method.- Keep transactions short. An HTTP call inside one holds a database connection hostage for its whole duration.
Example
@Service
public class OrderService {
private final OrderRepository orders;
private final StockService stock;
OrderService(OrderRepository orders, StockService stock) {
this.orders = orders;
this.stock = stock;
}
@Transactional
public Order place(Long customerId, String sku, int qty) {
stock.reserve(sku, qty); // same transaction
return orders.save(new Order(customerId, sku, qty));
}
@Transactional(readOnly = true)
public List<Order> recent(Long customerId) {
return orders.findTop10ByCustomerIdOrderByCreatedAtDesc(customerId);
}
@Transactional(rollbackFor = PaymentDeclinedException.class)
public void charge(Long orderId) throws PaymentDeclinedException {
// checked exceptions do NOT roll back unless you say so
}
}
One transaction per unit of work, and it has to be entered from outside the bean.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Hibernate course, and every lesson in it is listed on the Hibernate contents page.