스프링이 제공하는 트랜잭션 매니저의 역할
- 트랜잭션 추상화
- 리소스 동기화
트랜잭션 매니저와 트랜잭션 동기화 매니저

트랜잭션 매니저는 내부적으로 트랜잭션 동기화 매니저를 사용
트랜잭션 동기화 매니저는 쓰레드 로컬을 사용하여 커넥션을 동기화하기 때문에 멀티스레드 상황에서 안전하게 커넥션을 동기화 할 수 있음
따라서, 커넥션이 필요하면 트랜잭션 동기화 매니저를 통해 커넥션을 획득
동작 방식
- 트랜잭션 매니저는 데이터 소스를 통해 커넥션을 만들고 트랜잭션을 시작
- 트랜잭션 매니저는 트랜잭션이 시작된 커넥션을 트랜잭션 동기화 매니저에 보관
- 리포지토리는 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용
- 트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 보관된 커넥션을 통해 트랜잭션을 종료하고 커넥션을 닫음
스프링 트랜잭션 매니저를 적용한 애플리케이션 코드
MemberRepository
public class MemberRepositoryV3 {
private final DataSource dataSource;
public Member save(Member member) throws SQLException {
String sql = "insert into member (member_id, money) values (?, ?)";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
rs = pstmt.executeQuery();
if (rs.next()) {
Member member = new Member();
member.setMemberId(rs.getString("member_id"));
member.setMoney(rs.getInt("money"));
return member;
} else {
throw new NoSuchElementException("member not found memberId=" + memberId);
}
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, rs);
}
}
public void update(String memberId, int money) throws SQLException {
String sql = "update member set money=? where member_id=?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
int resultSize = pstmt.executeUpdate();
log.info("result size = {}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, rs);
}
}
public void delete(String memberId) throws SQLException {
String sql = "delete from member where member_id=?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
int resultSize = pstmt.executeUpdate();
log.info("result size = {}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, rs);
}
}
private Connection getConnection() throws SQLException {
// 트랜잭션 동기화를 사용하려면 DataSoruceUtils를 사용해야 함
Connection conn = DataSourceUtils.getConnection(dataSource);
log.info("conn = {}, conn.class = {}", conn, conn.getClass());
return conn;
}
private void close(Connection con, Statement stmt, ResultSet rs) throws SQLException {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
// 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 함
DataSourceUtils.releaseConnection(con, dataSource);
}
}
DataSourceUtils.getConnection()- 내부적으로
TransactionSynchronizationManager(트랜잭션 동기화 매니저)를 사용 - 트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환, 없으면 새로운 커넥션 생성하여 반환
- 내부적으로
DataSourceUtils.releaseConnection()- 내부적으로
TransactionSynchronizationManager(트랜잭션 동기화 매니저)를 사용 - 트랜잭션 동기화 매니저를 통해 파라미터로 받은 커넥션이 현재 스레드에서 트랜잭션과 연결되어 있는지 확인한 후, 연결되어 있으면 재사용하기 위해 닫지 않고 연결되어 있지 않으면 커넥션을 닫거나 커넥션 풀에 반환
- 내부적으로
MemberService
public class MemberServiceV3_1 {
// private final DataSource dataSource;
private final PlatformTransactionManager transactionManager;
private final MemberRepositoryV3 memberRepository;
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
businessLogic(fromId, toId, money);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
}
private void businessLogic(String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(fromId);
Member toMember = memberRepository.findById(toId);
memberRepository.update(fromId, fromMember.getMoney() - money);
validation(toMember);
memberRepository.update(toId, toMember.getMoney() + money);
}
private void release(Connection con) {
if (con != null) {
try {
con.setAutoCommit(true);
con.close();
} catch (Exception e) {
log.info("error", e);
}
}
}
private void validation(Member member) {
if (member.getMemberId().equals("ex")) {
throw new IllegalStateException("이체중 예외 발생");
}
}
}
private final PlatformTransactionManager transactionManager- 트랜잭션 매니저를 주입받음
- 현재
MemberRepository에서 JDBC 기술을 사용하고 있기 때문에DataSourceTransactionManager구현체를 주입 받아야 함 - 만약 JPA로 변경되면
JpaTransactionManager구현체를 주입 받으면 됨
transactionManager.getTransaction()- 트랜잭션 시작
- 현재 트랜잭션의 상태 정보가 포함된
TransactionStatus status를 반환, 이후 트랜잭션을 커밋 또는 롤백할 때 필요함
new DefaultTransactionDefinition()- 트랜잭션과 관련된 옵션을 지정할 수 있음
transactionManager.commit(status)- 트랜잭션이 성공하면 이 로직을 호출하여 커밋
transactionManager.rollback(status)- 문제가 발생하면 이 로직을 호출하여 롤백
정리
트랜잭션 시작

- 서비스 계층에서
transactionManager.getTransaction()를 호출하여 트랜잭션 시작 - 트랜잭션 매니저는 내부에서 데이터소스를 사용하여 커넥션 생성
- 커넥션을 수동 커밋 모드로 변경하여 실제 데이터베이스 트랜잭션 시작
- 커넥션을 트랜잭션 동기화 매니저에 보관
- 트랜잭션 동기화 매니저는 쓰레드 로컬에 커넥션을 보관한다. 따라서 멀티 쓰레드 환경에서 안전하게 커넥션을 보관할 수 있음
로직 실행

- 서비스 계층은 비즈니스 로직을 실행하면서 리포지토리의 메서드를 호출한다. 이때 트랜잭션을 유지하기 위해 커넥션을 파라미터로 전달하지 않음
- 리포지토리 메서드들은 트
DataSourceUtils.getConnection()를 사용하여 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용 - 획득한 커넥션을 사용하여 SQL을 실행
트랜잭션 종료

- 비즈니스 로직이 끝나면 커밋 또는 롤백하여 트랜잭션을 종료
- 트랜잭션을 종료하려면 동기화된 커넥션이 필요하기 때문에 트랜잭션 동기화 매니저를 통해 동기화된 커넥션을 획득
- 획득한 커넥션을 통해 데이터베이스에 트랜잭션을 커밋하거나 롤백
- 전체 리소스 정리
- 트랜잭션 동기화 매니저를 정리
con.setAutoCommit(true)로 되돌림con.close()를 통해 커넥션을 종료한다. 만약 커넥션 풀을 사용하는 경우라면 커넥션 풀에 반환
'Spring' 카테고리의 다른 글
| [Spring] Spring DB : Spring 트랜잭션 적용 - 트랜잭션 AOP (0) | 2024.11.04 |
|---|---|
| [Spring] Spring DB : Spring 트랜잭션 적용 - 트랜잭션 템플릿 (0) | 2024.10.28 |
| [Spring] Spring DB : Spring 트랜잭션 적용 - 트랜잭션 추상화와 리소스 동기화 (0) | 2024.08.18 |
| [Spring] Spring DB : Spring 트랜잭션 적용 - 기존 코드의 문제점 (0) | 2024.08.17 |
| [Spring] Spring DB : 애플리케이션 코드에 트랜잭션 적용 (0) | 2024.08.07 |