개발자가 코드로 commit()/rollback()을 직접 호출하지 않고,
AOP(관점 지향 프로그래밍) 방식으로 컨테이너가 자동으로 트랜잭션을 관리하도록 설청하는 것
<!-- Spring Transaction -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
spring-tx 모듈은 스프링의 트랜잭션 추상화 기능을 제공한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="...
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
트랜잭션 관리자 클래스는 PlatformTransactionManager 인터페이스를 구현한 클래스로, 트랜잭션 관리에 필요한 commit(), rollback() 메서드를 제공한다.
// 예시
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
데이터 접근 기술별로 적절한 구현체를 사용해야 한다.
<beans>
<!-- DataSource 설정 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<!-- Transaction Manager 설정 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
DAO 클래스는 JdbcTemplate 등을 통해 스프링이 관리하는 DataSource 를 사용해야 트랜잭션이 적용된다.
@Repository
public class BoardDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
private static final String BOARD_INSERT =
"INSERT INTO board(seq, title, writer, content) VALUES (?, ?, ?, ?)";
public void insertBoard(BoardVO vo) {
jdbcTemplate.update(BOARD_INSERT,
vo.getSeq(), vo.getTitle(), vo.getWriter(), vo.getContent());
}
}
스프링은 <tx:advice> 엘리먼트에 지정된 속성에 따라 트랜잭션 AOP 어드바이스를 생성하고 자동으로 시작·커밋·롤백한다.
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
어드바이스 관리자가 사용할 트랜잭션 관리자를 transaction-manager 속성으로 지정한다.
<tx:method/> 메서드 이름 패턴에 따라 트랜잭션 속성을 지정한다.
| 속성 | 의미 |
|---|---|
| name | 트랜잭션이 적용될 메서드 |
| read-only | 읽기 전용 여부 지정 (기본 false) 트랜잭션이 데이터 변경 없이 조회만 수행하도록 DB와 ORM에게 알려, 불필요한 flush·락·로그 생성을 줄여주는 최적화 여부 |
| no-rollback-for | 트랜잭션을 롤백하지 않을 예외 지정 |
| rollback-for | 트랜잭션을 롤백할 예외 지정 |
<aop:config>
<aop:pointcut expression="execution(* com.spring.biz..*Impl.get*(..))" id="getPointcut"/>
<aop:aspect ref="log">
<aop:before method="printLog" pointcut-ref="getPointcut"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:pointcut expression="execution(* com.spring.biz..*(..))" id="txPointcut"/>
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
</aop:config>
<aop:aspect>와 <aop:advisor>는 Spring AOP XML 스키마 구조로 인해 동일한 <aop:config> 내에서 함께 사용할 수 없다. 두 개의 <aop:config>로 분리한다.
XML 대신 어노테이션(@Transactional) 으로 간편하게 트랜잭션을 선언할 수 있다.
<!-- 어노테이션 기반 트랜잭션 활성화 -->
<tx:annotation-driven transaction-manager="txManager"/>
@Service
@Transactional
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
@Override
public void insertBoard(BoardVO vo) {
boardDAO.insertBoard(vo);
}
}
@Transactional을 클래스에 선언하면 해당 클래스의 모든 public 메서드에 트랜잭션이 적용된다.
메서드별로 다른 트랜잭션 속성을 주고 싶다면 개별 메서드 위에 직접 지정한다.
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
timeout = -1,
rollbackFor = Exception.class,
noRollbackFor = {}
)
하나의 트랜잭션 안에서 다른 트랜잭션 메서드가 호출될 때,
기존 트랜잭션을 이어받을지 / 새로 만들지 / 별도로 분리할지를 결정한다.
| 옵션 | 설명 |
|---|---|
| REQUIRED (기본값) | 현재 트랜잭션이 있으면 참여, 없으면 새로 생성. 👉 대부분의 서비스 로직에서 기본적으로 사용 |
| REQUIRES_NEW | 항상 새로운 트랜잭션을 시작하고, 기존 트랜잭션은 잠시 중단. |
| SUPPORTS | 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행. |
| MANDATORY | 트랜잭션이 반드시 존재해야 함. 없으면 예외 발생. |
| NOT_SUPPORTED | 트랜잭션이 있어도 일시 중단하고, 트랜잭션 없이 실행. |
| NEVER | 트랜잭션이 있으면 예외 발생. (트랜잭션 금지) |
| NESTED | 기존 트랜잭션 내에 중첩 트랜잭션(sub-transaction) 생성. (rollback은 독립적으로 가능) |
동시에 여러 트랜잭션이 실행될 때 데이터 일관성을 어떻게 보장할지를 결정한다.
| 옵션 | 의미 | 설명 |
|---|---|---|
| DEFAULT | DB 기본 설정 따름 | 보통 DB마다 기본 격리 수준은 READ_COMMITTED |
| READ_UNCOMMITTED | 커밋 안 된 데이터도 읽기 허용 | Dirty Read 발생 가능 |
| READ_COMMITTED | 커밋된 데이터만 읽기 | 대부분 DB의 기본값 (Oracle, SQL Server) |
| REPEATABLE_READ | 같은 트랜잭션 내에서 동일 쿼리 결과 보장 | Phantom Read 가능 |
| SERIALIZABLE | 가장 엄격한 수준, 완전 직렬화 | 성능 저하 가능, 동시성 낮음 |
DB 드라이버나 ORM(Hibernate 등)에게 "쓰기 작업이 없다"는 힌트를 줘서 불필요한 flush·락·로그 생성을 방지한다.
어떤 예외가 발생했을 때 트랜잭션을 롤백할지 지정한다.
기본 동작:
| 옵션 | 설명 |
|---|---|
rollbackFor | 지정한 예외(또는 하위 클래스)가 발생하면 롤백 |
noRollbackFor | 지정한 예외는 롤백하지 않음 |
트랜잭션이 지정된 시간(초) 안에 완료되지 않으면 자동으로 롤백한다.
기본값: -1 (제한 없음)
여러 트랜잭션 매니저가 등록되어 있을 때, 어떤 매니저를 사용할지 명시적으로 지정할 수 있다.
@Transactional("jpaTransactionManager")
public void processOrder() { ... }
스프링 공식 문서(Spring Framework Reference)에 따르면
프로젝트 전체를 XML 기반으로 관리하든지, 어노테이션 기반(@Transactional) 으로 통일해야 한다고 한다.
"You should choose one declarative transaction management approach:
either annotation-driven (@Transactional) or XML-based (<tx:advice>).
Mixing both on the same beans is not recommended."
XML<aop:advisor>와 @Transactional의 포인트컷이 서로 다른 대상인 경우 각자 독립적으로 잘 작동하겠지만,
두 설정 모두 AOP 기반 트랜잭션 프록시를 만들기 때문에 같은 메서드에 중복 적용되었을 때
트랜잭션 경계가 이중으로 생성되어 예측 불가능한 동작이 일어 날 수 있어 위험해진다.