10.3 @Transactional Declarative Transactions and MyBatis Integration
Except for statistics or single-retrieve functions, almost all business collection/modification logic in modern backend applications internally involves multiple steps of DB state changes (INSERT, UPDATE, etc.).
1. Necessity of Transactions
For example, in an "Order Payment" service:
- Deduct member balance (UPDATE)
- Decrease inventory (UPDATE)
- Record order receipt (INSERT)
Suppose steps 1 and 2 succeed, but a server failure occurs at step 3. The already deducted balance and inventory become orphaned data. Therefore, these three operations must succeed and be applied all at once (COMMIT), or if even one fails, be reset to the state before step 1 (ROLLBACK).
2. Spring + MyBatis Transaction Mechanism
When using mybatis-spring-boot-starter in Spring Boot, MyBatis SqlSession operations are automatically managed by Spring's DataSourceTransactionManager.
- How it works: When a method annotated with
@Transactionalis called, a Spring AOP proxy intercepts the request, acquires a DB connection, and starts a transaction. - MyBatis Integration: MyBatis Mapper methods share the same connection managed by Spring during execution. Once the operation is complete, the proxy performs the commit or rollback.
3. Practical Example (MyBatis Integration)
@Service
@RequiredArgsConstructor
public class ShopService {
private final UserMapper userMapper; // MyBatis Mapper
private final ProductMapper productMapper; // MyBatis Mapper
@Transactional(rollbackFor = Exception.class)
public void buyItem(Long userId, Long productId, int quantity) {
// 1. Deduct user balance (MyBatis UPDATE)
userMapper.updateBalance(userId, -10000);
// 2. Decrease product stock (MyBatis UPDATE)
int updatedRows = productMapper.updateStock(productId, -quantity);
// Business exception handling (triggers rollback)
if (updatedRows == 0) {
throw new RuntimeException("Insufficient stock to process the order.");
}
}
}
4. Transaction Propagation and Isolation Levels
You can fine-tune the scope of transactions depending on the situation.
| Attribute | Description | Note |
|---|---|---|
| REQUIRED | Joins an existing parent transaction or creates a new one if none exists. | Default (Most common) |
| REQUIRES_NEW | Always creates a new transaction regardless of a parent transaction. | Used for independent tasks like logging. |
| READ_COMMITTED | Reads only data that has been committed by other transactions. | Default isolation level. |
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void saveLog() { ... }
5. Default Rollback Rules and Precautions
Spring's @Transactional is powerful because it operates based on AOP (Aspect-Oriented Programming) proxies, but there are important rollback rules to note.
- By default, Spring only performs a rollback when an Unchecked Exception(
RuntimeExceptionand its subclasses) or anErroroccurs. - If a Checked Exception(e.g.,
IOException,SQLException) enforced by the Java compiler occurs, it does not rollback and commits instead.
Solution: Use the rollbackFor = Exception.class option to ensure rollbacks for all exceptions, or design business exceptions as runtime exceptions.
6. timeout and readOnly (Production Use)
Use timeout so long-running transactions do not hold DB connections indefinitely, and readOnly for read-only methods so the DB/driver can apply read optimizations.
| Attribute | Description | Example |
|---|---|---|
| timeout | Max transaction wait time in seconds; throws exception when exceeded. | timeout = 5 |
| readOnly | Read-only transaction; write attempts throw. Can enable DB/driver optimizations. | readOnly = true |
| noRollbackFor | Do not roll back for specified exception(s). Use when treating as “failure” but keeping DB state. | noRollbackFor = BusinessException.class |
@Transactional(readOnly = true, timeout = 10)
public List<OrderDto> findOrdersByUser(Long userId) {
return orderRepository.findByUserId(userId);
}
@Transactional(rollbackFor = Exception.class, timeout = 5, noRollbackFor = DuplicateOrderException.class)
public void createOrder(OrderRequest dto) {
// Duplicate order: throw but do not roll back
}