TokyoAJ

도쿄아재

SPRINGBOOT 2025.04.06

MyBatis + Spring Boot에서 트랜잭션 완벽 이해하기

Spring Boot에서 트랜잭션 처리를 위해 흔히 사용하는 어노테이션이 바로 @Transactional입니다.

JPA에서는 꽤 자연스럽게 쓰이지만, MyBatis에서는 트랜잭션이 예상대로 동작하지 않는 경우가 꽤 많습니다.


이 글에서는 MyBatis에서의 트랜잭션 개념과

@Transactional이 정확히 어떻게 동작하는지,

실무에서 꼭 알아야 할 설정과 사용법을 실제 예제 중심으로 정리해봅니다.


기본 개념: MyBatis 트랜잭션은 어떻게 동작하는가?

MyBatis 자체에는 트랜잭션 관리 기능이 없습니다.

Spring과 통합할 때 다음 중 하나를 사용해야 트랜잭션 처리가 가능합니다:


  1. Spring의 @Transactional 어노테이션
  2. 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); // MyBatis
userRepository.saveAudit(user.getId()); // JPA
}

JPA + MyBatis 혼용도 가능 (같은 트랜잭션 매니저를 공유한다면)

단, JPA의 Flush 시점과 MyBatis의 실행 시점 차이에 주의


마무리

MyBatis는 자체 트랜잭션 처리를 하지 않기 때문에

Spring과 통합할 때 @Transactional을 정확하게 이해하고 사용해야 합니다.


기본적인 원칙은:

  1. 서비스 계층에서 트랜잭션을 선언하고
  2. 데이터소스에 맞는 트랜잭션 매니저를 연결하며
  3. 내부 호출, 예외 처리 등의 예외 케이스를 미리 고려하는 것


트랜잭션이 중요한 이유는 단 하나, 데이터 무결성 보장입니다.

사소한 실수 하나가 롤백이 안 되는 큰 사고로 이어질 수 있다는 점을 잊지 마세요.

댓글