비즈니스 로직과 데이터 액세스 로직을 분리하기 위해서 서비스 계층을 따로 둔다. 이 때 서비스 계층에서 다양한 트랜잭션을 사용할 경우 원자성에 따라서 해당 로직을 하나의 트랜잭션으로 묶는 작업이 필요하다. 즉 트랜잭션 경계 설정이 필요하다!
이 때 트랜잭션에 대한 정보를 dao에 그 때 그 때 전달하는 방식은 매우 비효율적이다. 그렇기에 우리는 트랜잭션 동기화 기법이라는 것을 사용한다. 이 기능은 스프링에서 제공하는 트랜잭션 동기화 관리자를 이용해서 쉽게 사용할 수 있다.
private DataSource dataSource;
// dataSource를 DI 받는다.
public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
}
public void upgradeLevels() throws Exception
{
// 트랜잭션 동기화 관리자를 통해서 동기화 작업 초기화
TransactionSynchronizationManager.initSynchronization();
// DB 커넥션 생성 및 동기화
// 이후 모든 작업은 이 트랜잭션 안에서 진행한다.
Connection c = DataSourceUtils.getConnection(dataSource);
c.setAutoCommit(false);
try
{
.
.
.
}
catch (Exception e)
{
// 오류 발생하면 롤백
c. rollback();
throw e;
}
finally
{
// DB 커넥션 닫고 동기화 작업 종료
DataSourceUtils.releaseConnection(c, dataSource);
TransactionSynchronizationManager
.unbindResource(this.dataSource);
TransactionSynchronizationManager
.clearSynchronization();
}
}
그런데 위와 같이 DB에 접근하는 트랜잭션 코드가 클라이언트에 따라서 달라질 수 있다. 예를 들어서 갑자기 새롭게 등장한 클라이언트는 다중 DB를 사용할 수도 있다. 그러면 또 새로운 경계 설정이 필요하다. 이를 위해서 JTA
가 존재한다. Java Transaction API의 줄임말로 트랜잭션을 관리하는 API이다. 트랜잭션별로 알맞은 리소스를 가져오는 역할을 한다.
하지만 또 새로운 문제가 발생할 수 있다. 이번에 등장한 새로운 클라이언트는 하이버네이트를 사용한다고 한다. 이거는 JTA로 커버칠 수가 없다! 🤦♀️ 어떻게 할까???
하지만 위의 예시에서 클라이언트는 모두 디비에 접근해야 한다는 공통적인 특징을 가진다. 이 특징을 빼내서 추상화된 트랜잭션 관리 계층을 만들 수 있지 않을까? 이것이 트랜잭션 서비스 추상화의 첫걸음이다.
public void upgradeLevels()
{
// 추상 오브젝트 생성
PlatformTransactionalManager transactionManager
= new DataSourceTransactionManager(dataSource);
// 위의 코드처럼 추상 오브젝트로 디비 접근 작업 이어서 진행 ...
}
추상 오브젝트는 빈으로 만들어서 DI를 받아 보다 편리하게 이용할 수 있다.
여기서 DI를 이용하면 확실한 장점이 생긴다. 바로 단일 책임 원칙을 지킬 수 있다는 것이다. 외부에서 dataSource를 지정하기 때문에 함수는 이에 대한 책임을 지지 않고 서로 영향을 주지 않게 된다.
테스트 대역 (test double)
: 테스트할 기능에만 집중할 수 있도록 의존 오브젝트를 대체할 수 있도록 만든 오브젝트
테스트 스텁 (test stub)
: 테스트 대역 중 하나로 테스트 동안에 코드가 정상적으로 동작하게 하는 의존 객체
그런데 테스트 스텁 중에서 리턴값을 가지는 것도 있을 것이다. 해당 리턴 값을 저장해뒀다가 테스트 결과와 일치하는지 확인하기 위해서는 목 오브젝트
를 사용해야 한다.