앞 선 포스트에서 트랜잭션을 사용하기 위해서는 동일한 Connection을 사용해야 한다고 했다.
그러나 동일한 Connection을 코드로 작성하기에 처리하는 코드도 길고 지저분하다.
그래서 스프링은 트랜잭션 동기화 매니저를 제공한다.
DataSourceUtils.releaseConnection() : 트랜잭션을 사용하기 위해 동기화된 커넥션은 커넥션을 닫지 않고 그대로 유지해준다.
@Slf4j
public class MemberRepositoryV3 {
// ... Parameter로 전달하는 Connection 제거 메서드들
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.releaseConnection(con, dataSource);
}
}
커넥션을 파라미터로 전달하는 부분을 모두 제거할 수 있었다.
PlatformTransactionManager transactionManager : 트랜잭션 매니저를 주입 받는다.
@Slf4j
@RequiredArgsConstructor
public class MemberServiceV3_1 {
// private final DataSource dataSource;
private final PlatformTransactionManager transactionManager;
private final MemberRepositoryV3 memberRepository;
public void accountTransfer(String formId, String toId, int money) throws SQLException {
//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
bizLogic(formId, toId, money);
transactionManager.commit(status);// 성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status);// 실패시 롤백
throw new IllegalStateException(e);
} // commit or rollback 시 자동으로 release 됨
}
트랜잭션 매니저를 통해 훨씬 간결하게 코드를 작성 했지만, 아직도 Service 계층에 트랜잭션을 수행하는 코드가 남겨져 있다. 서비스 로직만을 처리해야 하는데 트랜잭션을 처리하는 기술 로직이 함께 포함되어 있다.
=> 스프링 AOP를 통해 프록시를 도입해 문제를 해결할 수 있다.
트랜잭션 프록시가 트랜잭션 처리 로직을 모두 처리한 후 실제 서비스를 대신 호출하기 때문에, 서비스 계층에는 순수한 비즈니스 로직만 남길 수 있다.
@Transactional 어노테이션만 붙여주면 해결된다.
AOP관련 내용인 어드바이저, 포인트컷, 어드바이스 등은 추후에 학습 예정이다.
@Slf4j
public class MemberServiceV3_3 {
private final MemberRepositoryV3 memberRepository;
public MemberServiceV3_3(MemberRepositoryV3 memberRepository) {
this.memberRepository = memberRepository;
}
@Transactional
public void accountTransfer(String formId, String toId, int money) throws SQLException {
bizLogic(formId, toId, money);
}
}
스프링 AOP를 적용하기 위해서는 스프링 컨테이너가 필요하므로 @SpringBootTest를 사용한다.
DataSource와 DataSourceTransactionManager는 application.properties 에 다음 정보가 있다면 자동으로 스프링 빈으로 등록해준다.
// application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=
@Slf4j
@SpringBootTest
class MemberServiceV3_4Test {
public static final String MEMBER_A = "memberA";
public static final String MEMBER_B = "memberB";
public static final String MEMBER_EX = "ex";
@Autowired
private MemberRepositoryV3 memberRepository;
@Autowired
private MemberServiceV3_3 memberService;
@TestConfiguration
static class TestConfig {
private final DataSource dataSource;
public TestConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
MemberRepositoryV3 memberRepositoryV3() {
return new MemberRepositoryV3(dataSource);
}
@Bean
MemberServiceV3_3 memberServiceV3_3() {
return new MemberServiceV3_3(memberRepositoryV3());
}
// TestLogic
// ...
}
}
이제 Service 계층에 비즈니스 로직만 남길 수 있었고, 적절히 추상화도 했다. 그러나 아직 예외 처리가 JDBC에서 날라오는 예외라 JPA로 변경 시 코드의 수정이 필요하다. 다음 강의부터는 예외 처리 문제를 해결한다고 한다!