자바에서 JDBC 데이터베이스 접근을 할 때는 자동적으로 트랜잭션이 적용됩니다.
DB는 그 자체로 완벽한 트랜잭션을 제공해서, 다중 데이터의 수정이나 삭제 등을 요청했을 때
중간에 어떠한 오류나 외부 상황에 의해 작업이 종료되더라도 일부만 삭제되거나 하는 일은 없습니다.
이러한 일관성은 DB의 신뢰로 이어지죠.
JDBC로 데이터베이스를 이용할 때 트랜잭션이 나눠지는 경계의 조건은 두가지입니다.
확정 작업의 커밋과 본래의 상태로 돌리는 롤백이죠.
하나의 쿼리 작업(업데이트, 삭제 등)이 끝나면 트랜잭션이 종료 됨과 동시에 새로운 트랜잭션 작업의 실행에 들어갑니다.
트랜잭션을 설명할 때 가장 많이 사용하는 예인 은행 계좌 송금을 예로 들어볼까요
트랜잭션을 통한 한 단위로 묶인 것이 아니라면, JDBC는 이 작업을 2단위로 묶을 것입니다.
A의 계좌에서 100만원을 차감 (update에 해당)
B의 계좌에 100만원을 추가 (update에 해당)
update가 정상적으로 종료되는 순간 커밋이 발생하죠.
그 순간 트랜잭션의 한 단위가 끝나게 됩니다.
만약 저 그림의 빨간선의 순간, 어떤 에러가 나서 작업이 종료 됬다면?
한 단위의 트랜잭션이 끝난 지점으로 롤백되어 돌아 갈 것이고
그 작업순간은 A의 계좌에서 100만원을 차감한 순간일 것입니다.
A의 입장에서는 날벼락이죠.
때문에 우리는 작업단위를 하나로 만들기 위해
트랜잭션의 두 경계를 묶어줄 필요가 있습니다.
자바 JDBC를 사용할 때 가장 사용하기 쉬운 방법은 무엇일까요?
커넥션을에서 커밋설정을 꺼두는 것입니다.
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
커밋 작업을 꺼둔다면?
커밋은 발생하지 않을 것이고
즉 트랜잭션의 경계설정의 종료작업의 순간이 발생하지 않는다는 이야기입니다.
처음 순간에 자동 커밋을 꺼준다면
A 계좌에서 100만원이 차감된 순간 커밋은 일어나지 않고, 그 이후 어떠한 이유로 인해 프로세스가 종료되더라도
롤백의 시점은 처음으로 돌아가게 됩니다.
A의 100만원도 다시 돌아오게 되겠죠.
만약 자신이 어떤 이유로 Connection을 사용하지 않고, JdbcTemplate를 사용한다면
transaction synchronization 을 이용하는 방법이 있습니다.
transaction synchronization 이란 Connection 오브젝트를 관리하는 하나의 저장소입니다.
몇번의 쿼리가 반복되더라도 커넥션을 종료시키지 않고, 보관해둔 뒤 다음 과정에서 계속 이용합니다.
commit이 호출된 순간 트랜잭션 단위가 만들어지고, transactionSysnchronizations 에서 Connection이 제거되는 거죠.
코드로 본다면 아래와 같습니다.
TransactionSynchronizationManager.initSynchronization(); // 동기화 초기화
Connection conn = DataSourceUtils.getConnection(dataSource); // DB 커넥션 생성 및 트랜잭션 시작
conn.setAutoCommit(false);
try{
// 작업
conn.commit();
} catch(Exception e) {
conn.rollback();
} finally {
DataSourceUtils.releaseConnection(conn, dataSource); // DB 커넥션 닫기
TransactionSynchronizationManager.unbindResource(this.dataSource); // 리소스 해제
TransactionSynchronizationManager.clearSynchronization();
}
이런 작업과정과 코드로 트랜잭션에 대한 관리를 해줄 수 있습니다.
하지만 이런 작업도 다수의 DB를 사용할 때는 문제가 생깁니다.
트랜잭션은 하나의 connection에 종속되기 때문에, 다수의 DB를 사용할 때 트랜잭션으로 경계설정이 불가능합니다.
예로, DB 데이터는 JDBC, 메시징 서버는 JMS로 관리한다면 다수의 작업을 묶어줄 트랜잭션이 필요하게 될겁니다.
자바에서는 이런 복수의 트랜잭션(이하 글로벌 트랜잭션)을 관리하는 API인 JTA(Java Transaction API)를 제공합니다.
트랜잭션 메니저는 XA Protocol을 통해 각각의 리소스 어댑터와 연결되서 트랜잭션을 관리합니다.
이렇게 하면 트랜잭션 매니저는 두 관리 작업을 종합적으로 제어할 수 있습니다.
JTA를 이용한 트랜잭션 코드는 일반적으로 아래와 같습니다.
InitialContext initContext = new InitialContext();
UserTransaction userTran = (UserTransaction)initContext.lookup("jta/usertransaction");
userTran.begin();
Connection conn = dataSource.getConnection();
try {
// 작업
} catch (Exception e){
userTran.rollback();
} finally {
conn.close();
}
JNDI를 이용해서 서버의 UserTransaction 객체를 가져온 후
(JDNI를 모른다면 : http://m.egloos.zum.com/bowwowmew/v/455062)
JDNI로 가져온 dataSource를 이용한 코드입니다.
이렇게 하면 로컬 트랜잭션이 필요할 때의 해결 방안(transactionSynchronizations) , 글로벌 트랜잭션이 필요할 때의 해결방안(JTA)이 모두 나왔습니다.
하지만 객체지향에서 얻고자 하는 바는 추상화를 통한 정보은닉 또는 이러한 과정의 효율과 재사용 가능성을 높이는 일입니다.
트랜잭션을 관리하고자하는 목적은 동일한데 해결방안은 다르고, 작업내용이 바뀌는 것은 지양해야 합니다.
이럴 때 인터페이스를 통한 추상화 과정을 통해 객체 별 의존성을 낮출 수 있습니다.
Spring에서는 트랜잭션 경계설정을 위한 추상 인터페이스 PlatformTransactionManager를 제공합니다.
인터페이스를 통해 개별적인 코드의 수정없이 의존성 주입만으로 포괄적인 사용이 가능해집니다.
이 인터페이스를 적용한 코드는 아래와 같습니다.
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(datasource);
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 작업
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
} finally {
transactionManager.rollback(status);
}
transactionManager 객체의 차이만으로 범용적이고, 변경이 필요없는 코드가 완성됐습니다.
JTA를 사용하는 글로벌 트랜잭션을 이용할 경우 new JTATransactionManager(); 로 바꿔주기만 하면 됩니다.
이 부분을 의존성 주입 객체로 다루게 되면 훌륭한 코드가 되겠지요.
이로서 트랜잭션과 추상화에 대한 포스트를 마칩니다.
'프로그래밍 > Web-Spring' 카테고리의 다른 글
SpringFramework(스프링) - 팩토리 빈(FactoryBean) (0) | 2018.02.19 |
---|---|
스프링 프레임워크 - Junit Test (0) | 2018.02.04 |
Spring - 파일 업로드 하기 (1) | 2017.11.27 |