Spring Boot에서 트랜잭션 처리를 위해 흔히 사용하는 어노테이션이 바로 @Transactional입니다.
JPA에서는 꽤 자연스럽게 쓰이지만, MyBatis에서는 트랜잭션이 예상대로 동작하지 않는 경우가 꽤 많습니다.
이 글에서는 MyBatis에서의 트랜잭션 개념과
@Transactional이 정확히 어떻게 동작하는지,
실무에서 꼭 알아야 할 설정과 사용법을 실제 예제 중심으로 정리해봅니다.
기본 개념: MyBatis 트랜잭션은 어떻게 동작하는가?
MyBatis 자체에는 트랜잭션 관리 기능이 없습니다.
Spring과 통합할 때 다음 중 하나를 사용해야 트랜잭션 처리가 가능합니다:
- Spring의 @Transactional 어노테이션
- DataSourceTransactionManager 등록
MyBatis는 Spring의 SqlSessionTemplate을 통해 스프링 트랜잭션 컨텍스트에 참여하게 됩니다.
기본 설정: Spring Boot + MyBatis에서 트랜잭션 활성화
1. @EnableTransactionManagement 설정
@Configuration
@EnableTransactionManagement
@MapperScan("com.example.mapper")
public class MyBatisConfig {
...
}
이 어노테이션은 스프링이 @Transactional
을 인식할 수 있게 만듭니다.
2. DataSourceTransactionManager Bean 등록
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
이 매니저가 MyBatis에서 트랜잭션을 관리해주는 핵심 컴포넌트입니다.
실전 예제
1. 정상 작동하는 트랜잭션 예
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
private final AuditMapper auditMapper;
@Transactional
public void createUser(User user) {
userMapper.insertUser(user);
auditMapper.insertLog("New user registered: " + user.getUsername());
}
}
결과: 둘 중 하나라도 실패하면 둘 다 롤백됩니다.
트랜잭션이 안 먹는 5가지 흔한 이유
1. @Transactional이 private 또는 내부 메서드 호출일 때
@Transactional
private void saveSomething() { ... }
Spring AOP 기반이기 때문에 public만 트랜잭션 적용 가능
자신 클래스 안에서의 메서드 호출도 적용 안됨
해결:
트랜잭션 걸고 싶은 메서드는 public으로 선언
내부 호출은 외부 컴포넌트를 통해 간접 호출해야 함
2. Mapper에 @Transactional 붙였는데 안 됨
@Mapper
public interface UserMapper {
@Transactional
void insertUser(User user);
}
Mapper는 Spring AOP Proxy의 대상이 아니므로 @Transactional이 무시됨
서비스 계층에 트랜잭션을 선언해야 적용됨
3. 예외가 발생했지만 롤백이 안 되는 경우
기본적으로 RuntimeException, Error만 롤백 대상입니다.
Checked Exception은 롤백되지 않습니다.
해결 방법:
@Transactional(rollbackFor = Exception.class)
public void updateSomething() throws Exception {
...
throw new Exception("checked 예외");
}
4. 다중 DB (멀티 데이터소스) 환경에서 트랜잭션 분리 안 됨
각 DB에 대해 별도의 TransactionManager가 필요합니다.
예: master/slave
@Bean(name = "masterTransactionManager")
public DataSourceTransactionManager masterTM(@Qualifier("masterDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
그리고 @Transactional(transactionManager = "masterTransactionManager") 로 지정
5. MyBatis의 수동 commit/rollback 사용 중 충돌
SqlSessionFactory.openSession(false) 로 수동 트랜잭션을 관리하는 경우,
Spring의 트랜잭션과 충돌하거나 무시될 수 있습니다.
→ SqlSessionTemplate를 통해 관리하게 하세요. Spring이 알아서 commit/rollback 합니다.
실무 팁 요약
상황 | 권장 방식 |
일반 트랜잭션 | 서비스 계층에서 @Transactional 사용 |
다중 DB 트랜잭션 | 트랜잭션 매니저 각각 등록 후 명시적으로 사용 |
rollback 안 되는 경우 | rollbackFor 명시 |
내부 메서드 호출 | 트랜잭션 분리 클래스 또는 외부 컴포넌트 사용 |
Mapper에 붙이기 X | 반드시 서비스 계층에서만 트랜잭션 선언 |
트랜잭션 처리 전략 예시
@Transactional
public void signup(User user) {
userMapper.insertUser(user);
userRepository.saveAudit(user.getId());
}
JPA + MyBatis 혼용도 가능 (같은 트랜잭션 매니저를 공유한다면)
단, JPA의 Flush 시점과 MyBatis의 실행 시점 차이에 주의
마무리
MyBatis는 자체 트랜잭션 처리를 하지 않기 때문에
Spring과 통합할 때 @Transactional을 정확하게 이해하고 사용해야 합니다.
기본적인 원칙은:
- 서비스 계층에서 트랜잭션을 선언하고
- 데이터소스에 맞는 트랜잭션 매니저를 연결하며
- 내부 호출, 예외 처리 등의 예외 케이스를 미리 고려하는 것
트랜잭션이 중요한 이유는 단 하나, 데이터 무결성 보장입니다.
사소한 실수 하나가 롤백이 안 되는 큰 사고로 이어질 수 있다는 점을 잊지 마세요.
댓글