앞 선 포스트에서 트랜잭션을 사용하기 위해서는 동일한 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로 변경 시 코드의 수정이 필요하다. 다음 강의부터는 예외 처리 문제를 해결한다고 한다!