-
5장) 5.2 트랜잭션 서비스 추상화Java & Spring/토비의 스프링 3.1 2021. 6. 26. 18:12반응형
5장 서비스 추상화
5.2 트랜잭션 서비스 추상화
트랜잭션
- 더 이상 나눌 수 없는 단위 작업
- 트랜잭션 커밋
- 모든 SQL 수행 작업이 다 성공적으로 마무리됐다고 DB에 알려줘서 작업을 확정시키는 작업
- 변경 내용이 DB에 반영되도록 설정하는 작업
- 트랜잭션 롤백
- SQL 수행 작업 중 뒤 차례의 수행에 문제가 발생한 경우에 앞에서 처리한 SQL 수행 작업도 취소시키는 작업
- DB에 변경 내용을 변경 이전으로 되돌리는 작업
트랜잭션 경계 설정
트랜잭션 경계
- 트랜잭션이 시작되고 끝나는 위치
- transaction 시작 선언 이후, commit() or rollback() 으로 트랜잭션을 종료하는 작업
JDBC 트랜잭션의 트랜잭션 경계 설정
- 하나의 Connection을 사용하다가 닫는 사이에 일어남
- 트랜잭션의 시작과 종료가 Connection 오브젝트를 통해 이루어짐
- 자동 커밋 옵션을 false로 만들어주어 트랜잭션을 시작
- 로컬 트랜잭션
- 하나의 DB 커넥션 안에서 만들어지는 트랜잭션
UserService와 UserDao의 트랜잭션 문제
- 일반적으로 트랜잭션은 커넥션보다 라이프 사이클이 짧음
- JdbcTemplate 메소드를 사용하는 UserDao는 메소드마다 하나씩 독립적인 트랜잭션으로 실행됨
- 예시
- upgradeLevels() 메소드에서 세 번에 걸쳐 UserDao의 update()를 호출
- 매번 새로운 DB 커넥션과 트랜잭션을 만들어 사용
- 첫 번째 수행이 성공하고, 두 번째 호출 시점에서 오류가 발생해도, 첫 번째 커밋한 트랜잭션의 결과가 DB에 반영됨
- 데이터 엑세스 코드를 DAO로 만들어 분리해놓은 경우, DAO 메소드를 호출할 때마다 하나의 새로운 트랜잭션이 만들어지는 구조가 됨
비즈니스 로직 내의 트랜잭션 경계설정
DAO 메소드 안으로 upgradeLevels() 메소드 내용을 옮기면 하나의 트랜잭션으로 관리가 됨
- 하지만, 비즈니스 로직과 데이터 로직을 한데 묶는 좋지 않은 결과 초래
트랜잭션의 경계설정 작업을 UserService쪽으로 위임하는 방법으로 해결
- UserService에 트랜잭션 시작과 종료를 담당하는 최소한의 코드만 가져오게 만들면, 책임이 다른 코드를 분리해둔 채로 트랜잭션 문제를 해결할 수 있음
public void upgradeLevels() throws Exception { (1) DB Connection 생성 (2) 트랜잭션 시작 try { (3) DAO 메소드 호출 (4) 트랜잭션 커밋 } catch (Exception exception) { (5) 트랜잭션 롤백 throw exception; } finally { (6) DB Connection 종료 } }
- Connection 오브젝트를 가지고 데이터 엑세스 작업을 진행하는 코드가 UserDao의 update 메소드 안에 있어야함
- DAO 메소드 호출마다 Connection 오브젝트를 마라메터로 전달해줘야함
UserService 트랜잭션 경계설정의 문제점
- 위와 같이 코드를 수정하면 아래와 같은 문제가 발생
- 리소스의 깔끔한 처리를 가능하게 했던 JdbcTemplate을 더 이상 활용할 수 없음
- DAO의 메소드와 비즈니스 로직을 담고 있는 UserService의 메소드에 Connection 파라메터가 추가돼야함
- Connection 파라메터가 UserDao 인터페이스 메소드에 추가되면 UserDao는 액세스 기술에 독립적일 수 없음
- UserDao 인터페이스가 바뀌고, 그에 따라 UserService도 함께 수정돼야함
- DAO 메소드에 Connection 파라메터를 받게 하면 테스트 코드에도 영향
- 위와 같이 코드를 수정하면 아래와 같은 문제가 발생
트랜잭션 동기화
스프링이 제공하는 기능을 사용
- 트랜잭션 동기화(Transaction Synchronization)
Connection 파라메터 제거
- upgradeLevels 메소드가 트랜잭션 경계 설정을 해야하기 때문에, 트랜잭션 시작과 종료를 관리
- 트랜잭션 동기화를 사용
- UserService에서 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 호출되는 DAO의 메소드에서는 저장된 Connection을 가져다 사용하게 하는 방식
- UserService에서 Connection 생성 -> 트래잭션 시작 -> update 메소드 호출 -> JdbcTemplate 메소드에서 트랜잭션 동기화 저장소에 현재 시작된 트랜잭션 Connection 오브젝트 존재 확인 후 가져옴 -> 로직 진행 및 반복 -> 모든 작업이 정상적으로 종료되면 commit으로 트랜잭션 완료 / 예외 상황이라면 rollback으로 트랜잭션을 종료
- 트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 오브젝트를 저장하고 관리
- 멀티 쓰레드 환경에서 충돌 우려 X
트랜잭션 동기화 적용
//UserService private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void upgradeLevels() throws Exception { TransactionSynchronizationManager.initSynchronization(); // 동기화 Connection c = DataSourceUtils.getConnection(dataSource); // connection 생성 및 동기화 c.setAutoCommit(false); // connection 생성 및 동기화 try { userDao.getAll() .filter(user -> canUpgradeLevel(user)) .forEach(user -> upgradeLevel(user)); c.commit(); } catch (Exception exception) { c.rollback(); throw exception; } finally { DataSourceUtils.releaseConnection(c, dataSource); // connection close TransactionSynchronizationManager.unbindResource(this.dataSource); // 동기화 작업 종료 TranscationSynchronizationManager.clearSynchronization(); // 동기화 정리 } }
- UserService에서 DB 커넥션을 직접 다룰 때 DataSource가 필요 ~> DI
- 스프링 제공 트랜잭션 동기화 관리 클래스 ~> TransactionSynchronizationManager
- DataSourceUtils의 getConnection 메소드 ~> Connection 객체 생성 및 트랜잭션 동기화에 사용하도록 저장소에 바인딩
JdbcTemplate 트랜잭션 동기화
- JdbcTemplate은 Connection에 대해 영리하게 동작
- 미리 생성돼서 트랜잭션 동기화 저장소에 등록된 DB 커넥션이나 트랜잭션이 없으면 직접 커넥션을 생성하고 트랜잭션을 시작 및 작업 진행
- 트랜잭션 동기화 저장소에 등록되어 있으면 커넥션을 가져와서 사용
- JdbcTemplate은 Connection에 대해 영리하게 동작
트랜잭션 서비스 추상화
기술과 환경에 종속되는 트랜잭션 경계설정 코드
DB 종류를 여러 개 이용하는 경우 트랜잭션 처리 코드를 담은 UserService에서 문제가 발생
글로벌 트랜잭션을 사용해서 트랜잭션을 관리
자바는 JTA(JavaTransactionAPI)를 제공
- DB와 메시징 서버를 제어하고 관리하는 각 리소스 매니저와 XA 프로토콜을 통해 연결
InitialContext context = new InitialContext(); UserTransaction transaction = (UserTransaction)context.lookup(USER_TX_JNDI_NAME); trasaction.begin(); Connection connection = dataSource.getConnection(); try { // Data Access Code taransaction.commit(); } catch (Exception exception) { transaction.rollback(); throw exception; } finally { connection.close(); }
트랜잭션 처리 방법은 별로 달라진게 없지만, JDBC 로컬 트랜잭션을 JTA 글로벌 트랜잭션으로 바꾸는 경우 UserService를 수정해야하는 문제점이 존재
- DB 종류마다 트랜잭션 관리 코드가 다르기 때문에 계속해서 변경해야함
트랜잭션 API의 의존관계 문제와 해결책
- UserDao가 DAO 패턴을 사용해 구현 데이터 엑세스 기술을 유연하게 바꿔 사용하도록 했지만, UserService에 트랜잭션 경계 설정을 하게되면 다시 특정 데이터 엑세스 기술에 종속되는 구조가 존재
- 여러 트랜잭션 경계 설정 기술 사용 방법에 공통점으로 추상화
스프링의 트랜잭션 서비스 추상화
public void upgradeLevels() { PlatformTranscationManager transactionManager = new DataSourceTransactionManager(dataSOurce); TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { userDao.getAll() .filter(user -> canUpgradeLevel(user)) .forEach(user -> upgradeLevel(user)); } catch (RuntimeException e) { transactionManager.rollback(status); throw e; } }
- 스프링이 제공하는 트랜잭션 경계설정 추상 인터페이스 PlatformTransactionManger를 이용
- 필요에 따라 트랜잭션 매니저가 DB 커넥션을 가져오는 작업도 같이 수행
- 트랜잭션은 TransactionStatus 타입 변수에 저장되어 조작이 필요한 경우, PlatformTransactionManger 메소드의 파라미터로 전달
트랜잭션 기술 설정의 분리
JTA를 이용하는 글로벌 트랜잭션으로 변경
- PlatformTransactionManger 구현 클래스를 DataSourceTransactionManager -> JTATransactionManger로 바꿈
어떤 트랜잭션 매니저 구현 클래스를 사용할지 UserService 코드가 알고있는 것은 DI 원칙에 위배
외부에서 스프링 DI를 통해 제공받도록 수정
스프링에서 제공하는 PlatformTransactionManager는 싱글톤으로 사용이 가능
@AllArgsContructor public class UserService { ... private PlatformTransactionManager transactionManger; public void upgradeLevels() { TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); try { userDao.getAll() .filter(user -> canUpgradeLevel(user)) .forEach(user -> upgradeLevel(user)); this.transactionManager.commit(status); } catch (RuntimeException e) { this.transactionManager.rollback(status); throw e; } } }
- PlatformTransactionManager의 구현 클래스에 따라 주입을 다르게 해주면서 사용
정리
- 트랜잭션과 관련해서 spring boot에서는
@Transactional
을 제공해서, 위의 과정을 쉽게 이용 가능
반응형'Java & Spring > 토비의 스프링 3.1' 카테고리의 다른 글
6장) 6.1 트랜잭션 코드의 분리 ~ 6.2 고립된 단위 테스트 (0) 2021.07.11 5장) 5.3 서비스 추상화와 단일 책임 원칙 ~ 5.5 정리 (0) 2021.07.02 5장) 5.1 사용자 레벨 관리 기능 추가 (0) 2021.06.26 4장) 4.2 예외 전환 ~ 4.3 정리 (0) 2021.06.21 4장) 4.1 예외 (0) 2021.06.17