스프링이 제공하는 트랜잭션 매니저는 2가지 역할을 한다.
- 트랜잭션 추상화
- 리소스 동기화
리소스 동기화
트랜잭션을 유지하려면 트랜잭션의 시작부터 끝까지 같은 데이터베이스 커넥션을 유지해아한다.
스프링은 트랜잭션 동기화 매니저를 제공한다
이것은 쓰레드 로컬( ThreadLocal )을 사용해서 커넥션을 동기화해준다. 트랜잭션 매니저(트랜잭션 인터페이스)는 내부에서 이 트랜잭션 동기화 매니저를 사용한다.
트랜잭션 동기화 매니저는 쓰레드 로컬을 사용하기 때문에 멀티쓰레드 상황에 안전하게 커넥션을 동기화 할 수 있다
따라서 커넥션이 필요하면 트랜잭션 동기화 매니저를 통해 커넥션을 획득하면 된다.
리소스 동기화 동작 방식
- 트랜잭션을 시작하려면 커넥션이 필요한데 트랜잭션 매니저는 데이터소스를 통해 커넥션을 만들고 트랜잭션을 시작
- 트랜잭션 매니저는 트랜잭션이 시간된 커넥션을 트랜잭션 동기화 매니저에 보관
- 리포지토리는 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다. 따라서 파라미터로 커넥션을 전달하지 않아도 된다.
- 트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 보관된 커넥션을 통해 트랜잭션을 종료하고, 커넥션도 닫는다.
애플리케이션 코드에 트랜잭션 매니저 적용
private Connection getConnection() throws SQLException {
//주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
Connection con = DataSourceUtils.getConnection(dataSource); //트랜잭션 동기화 매니저에서 커넥션 꺼내기
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
// 주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
DataSourceUtils.releaseConnection(con, dataSource);
}
connection을 파라미터로 안 넘겨도 된다. 트랜잭션 동기화 매니저를 통해 getConnection을 해서 동일한 커넥션을 사용할 수 있다.
DataSourceUtils.getConnection() 동작 방식
- 트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환한다
- 트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우 새로운 커넥션을 생성해서 반환한다.
DataSourceUtils.releaseConnection() 동작 방식
- con.close() 를 사용해서 직접 닫아버리면 커넥션이 유지되지 않는 문제가 발생한다. 이 커넥션은 이후 로직은 물론이고, 트랜잭션을 종료(커밋, 롤백)할 때 까지 살아있어야 한다.
- DataSourceUtils.releaseConnection() 을 사용하면 커넥션을 바로 닫는 것이 아니라
- 트랜잭션을 사용하기 위해 동기화된 커넥션은 커넥션을 닫지 않고 그대로 유지해준다.
- 트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우 해당 커넥션을 닫는다.
트랜잭션을 사용하는 서비스 로직
// 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 DefaultTransactionAttribute());
try {
//비즈니스 로직 시작
bizLogic(fromId, toId, money);
transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}
}
private final PlatformTransactionManager transactionManager
- 트랜잭션 매니저를 주입 받는다. 지금은 JDBC 기술을 사용하기 때문에 DataSourceTransactionManager 구현체를 주입 받아야 한다.
transactionManager.getTransaction()
- 트랜잭션을 시작한다
- TransactionStatus status 를 반환한다. 현재 트랜잭션의 상태 정보가 포함되어 있다. 이후 트랜 잭션을 커밋, 롤백할 때 필요하다.
new DefaultTransactionDefinition()
- 트랜잭션과 관련된 옵션을 지정할 수 있다.
transactionManager.commit(status)
- 트랜잭션이 성공하면 이 로직을 호출해서 커밋하면 된다.
transactionManager.rollback(status)
- 문제가 발생하면 이 로직을 호출해서 트랜잭을 롤백하면 된다.
트랜잭션 매니저의 전체 동작 흐름 정리
클라이언트의 요청으로 서비스 로직을 실행
- 서비스 계층에서 transactionManager.getTransaction() 을 호출해서 트랜잭션을 시작 (transcationManager는 서비스 테스트 코드에서 jdbc에서 사용하는 transactionManager 구현체를 주입해준다.)
- 트랜잭션을 시작하려면 먼저 데이터베이스 커넥션이 필요하다. 트랜잭션 매니저는 내부에서 데이터소스를 사용해서 커넥션을 생성한다. (transactionManager를 생성할 때 전달한 dataSource 참고해서..)
- 커넥션을 수동 커밋 모드로 변경해서 실제 데이터베이스 트랜잭션을 시작한다.
- 커넥션을 트랜잭션 동기화 매니저에 보관
- 트랜잭션 동기화 매니저는 쓰레드 로컬에 커넥션을 보관한다. 따라서 멀티 쓰레드 환경에 안전하게 커넥션을 보관할 수 있다.
- 서비스는 비즈니스 로직을 실행하면서 리포지토리의 메서드들을 호출한다. 이때 커넥션을 파라미터로 전달하지 않는다.
- 리포지토리 메서드들은 트랜잭션이 시작된 커넥션이 필요하다. 리포지토리는 DataSourceUtils.getConnection() 을 사용해서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다.
- 획득한 커넥션을 사용해서 SQL 데이터베이스에 전달해서 사용한다.
- 비즈니스 로직이 끝나고 트랜잭션을 종료한다. 트랜잭션을 커밋하거나 롤백하면 종료된다.
- 트랜잭션을 종료하려면 동기화된 커넥션이 필요하다. 트랜잭션 동기화 매니저를 통해 동기화된 커넥션을 획득한다.
- 획득한 커넥션을 통해 데이터베이스에 트랜잭션을 커밋하거나 롤백한다.
- 전체 리소스를 정리한다.
전체 리소스 정리
- 트랜잭션 동기화 매니저를 정리한다. 쓰레드 로컬은 사용후 꼭 정리해야 한다.
- con.setAutoCommit(true) 로 되돌린다. 커넥션 풀에 돌려 놓기 전에 commit 모드를 ture로하고 돌려놓기
- con.close() 를 호출해셔 커넥션을 종료한다. 커넥션 풀을 사용하는 경우 con.close() 를 호출하 면 커넥션 풀에 반환된다.
- 지금은 DataSourceUtils를 사용해서 커넥션을 얻어서 사용하므로 커넥션 풀을 사용하지 않는다.
'Spring > Spring DB' 카테고리의 다른 글
트랜잭션 템플릿 (0) | 2023.10.03 |
---|---|
트랜잭션 추상화 (0) | 2023.10.03 |
JDBC에 의존하는 트랜잭션을 적용하면서 나타나는 문제점 (0) | 2023.10.03 |
트랜잭션 (1) | 2023.10.02 |
커넥션 풀과 데이터소스 이해 (0) | 2023.09.29 |