sql문을 실행했을 때에 몇가지의 로우만 삭제되고 나머지는 안되거나, 일부만 수정되고 나머지 필드는 수정이 안된 채로 실패로 끝나는 경우는 없습니다. 이는 데이터베이스의 ACID 성질을 만족하는 것입니다.
서버에서도 이러한 성질을 요구하는 경우가 있습니다. 계좌이체 작업같은 경우 입금 계좌와 출금 계좌의 변동 총량은 반드시 같아야 하며 이를 보장해줘야 합니다. 하지만 DB의 출금계좌의 상태를 수정하는 것과 입금계좌의 상태를 수정하는 것은 두개의 sql문이 필요합니다. 이렇게 한번에 실행을 보장하는 작업을 트랜잭션이라 말합니다. 앞서 계좌이체 작업 또한 하나의 트랜잭션이 됩니다.
- 트랜잭션 롤백 : 트랜잭션 안에 한가지의 작업이라도 실패했을 경우 앞서 성공한 작업들 모두 취소하는 작업.
- 트랜잭션 커밋 : 트랜잭션 안에 작업이 모두 완료되었다면 작업을 확정시키는 것
JDBC 트랜잭션 경계설정
트랜잭션은 모두 시작 지점과 끝나는 지점이 있습니다. 시작하는 방법은 한가지이지만 끝내는 방법은 두가지입니다.
앞서 말한 것처럼 롤백과 커밋이 있는 것입니다! JDBC의 트랜잭션은 하나의 Connection을 가져와 사용하다가 닫는 사이에서 일어납니다. 이처럼 트랜잭션이 시작되고 끝나는 지점을 트랜잭션의 경계라고 합니다.
트랜잭션을 만들기 위해선 위에 코드와 같이 Connection의 자동커밋 옵션을 false으로 변경해주어야 합니다. 기본적으로 JDBC는 DB 작업을 수행한 직후 자동으로 커밋이 되도록 설정되어 있기에 작업별로 트랜잭션 설정이 되어있습니다. (아마 안정성을 위한 것으로 생각됩니다.)
그럼 트랜잭션의 경계를 긋기 위해서는 아래와 같이 commit 또는 rollback 함수를 써주면 하나의 트랜잭션을 만들 수 있는 것입니다. 아래의 코드는 간단한 트랜잭션 코드입니다. 아래와 같이 하나의 DB 커넥션 안에서 만들어지는 트랜잭션을 로컬 트랜잭션이라고 합니다.
Connection c = dataSource.getConnection();
c.setAutoCommit(false); //트랜잭션 시작
try{
PreparedStatement st1 = c.preparedStatement("Update users ...");
st1.executeUpadte();
PreparedStatement st2 = c.preparedStatement("delete users ...");
st2.executeUpdate();
c.commit(); //트랜잭션 커밋
}
catch(Exception e){
c.rollback(); //트랜잭션 롤백
}
c.close();
JdbcTemplate과 트랜잭션
jdbcTemplate을 사용하게 되면 하나의 메소드를 호출할때 마다 getConnextion()로 Connection 오브젝트 호출과 작업이 완료된 후 close를 호출을 자동으로 해주게 됩니다. 이러한 편의성으로 인해 jdbcTemplate을 많이 사용하지만 이러한 성질로 인해 템플릿 메소드가 호출될 때마다 트랜잭션이 새로 만들어지고 메소드를 빠져나오기 전에 종료되므로 각 메소드마다 하나씩의 독립적인 트랜잭션으로 실행될 수 밖에 없습니다.
그렇다면 여러 번 DB에 업데이트를 해야 하는 작업을 하나의 트랜잭션으로 만들기 위해서는 어떻게 해야될까요???
너무 여러번 말하는 것 같지만 트랜잭션은 Connection 범위안에서 경계가 결정되기에 이를 이용하면 됩니다.
public void upgradeLevels() throws Exception {
DB Connection 생성
트랜잭션 시작
try{
메소드 호출
트랜잭션 커밋
}
catch(Exception e){
트랜잭션 롤백
throw e;
}
finally{
DB connection 종료
}
}
위와 같은 형태는 트랜잭션을 사용하는 전형적인 JDBC 코드의 구조입니다. 위에 메소드를 호출할때에 메소드는 upgradeLevel 메소드에서 만든 DB 커넥션을 사용해야 합니다. 그래야만 같은 트랜잭션 안에서 동작할 수 있습니다. 이를 위해서는 메소드의 파라미터 Connection 오브젝트를 넘겨주면 됩니다. 하지만 이는 매우 번거로운 일이 될 수 있습니다. 메소드안에 다른 메소드가 있다면 이 파라미터는 계속해서 전달되어야 합니다. 이를 해결하기 위해 스프링이 제안하는 방법이 트랜잭션 동기화 입니다.
트랜잭션 동기화란 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 이후에 호출되는 Connection이 필요한 메소드에 저장된 Connection을 가져다가 사용하게 하는 것이다. 정확히는 JdbcTemplate이 트랜잭션 동기화 방식을 이용하도록 하는 것이다. 이렇게 하면 JdbcTemplate은 작업을 마치더라도 가져온 connection을 닫지 않는다.
private DataSource dataSource
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void upgradeLevels() throws Exception {
TransactionSynchronuzationManager.initSynchronization(); //저장소 초기화
Connection c = DataSourceUtils.getConnection(dataSource); //Connection 오브젝트 생성과 저장소 바인딩을 해주는 메소드
c.setAutoCommit(false);
try{
List<User> users = userDao.getAll();
for (User user : users) {
if(canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
c.commit();
}
catch(Exception e){
c.rollback();
throw e;
} finally {
DataSourceUtils.releaseConnection(c, dataSource); //커넥션 닫기
TransactionSynchroniztionManager.unbindResource(this.dataSource); //저장소 작업 종료 및 정리
TransactionSynchroniztionManager.clearSynchronization();
}
}
위 글은 "토비의 스프링 3.1"의 내용을 바탕으로 작성된 글입니다.
'Spring' 카테고리의 다른 글
IoC 컨테이너란 무엇인가 (0) | 2024.04.16 |
---|---|
어노테이션이란? (0) | 2024.04.02 |
트랜잭션 어노테이션 (@Transactional, 속성, 정책, 방법) (0) | 2024.03.28 |
Mockito 프레임워크 정리(TDD 작성, 테스트 코드 작성) (0) | 2024.03.22 |
JAVA Mail을 이용한 메일 발송 (0) | 2024.03.20 |