[Spring] 트랜잭션 동기화 / 트랜잭션 AOP

Manx·2022년 7월 14일
0

spring

목록 보기
22/24

인프런 '스프링 DB 1편' - 김영한님의 강의 내용입니다.

트랜잭션 동기화

앞 선 포스트에서 트랜잭션을 사용하기 위해서는 동일한 Connection을 사용해야 한다고 했다.
그러나 동일한 Connection을 코드로 작성하기에 처리하는 코드도 길고 지저분하다.
그래서 스프링은 트랜잭션 동기화 매니저를 제공한다.

  • 트랜잭션을 시작할 때 비즈니스 로직에서 트랜잭션 매니저를 통해 커넥션을 생성한다.
  • 트랜잭션 동기화 매니저가 생성된 트랜잭션을 보관한다.
  • Repository에서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용하므로 파라미터로 커넥션을 전달하지 않아도 된다.
  • 트랜잭션이 종료되면 트랜잭션 동기화 매니저에 보관된 커넥션을 종료하고, 커넥션을 닫는다.
  • 만약 트랜잭션 동기화 매니저에 생성된 커넥션이 없다면, Repository는 새로운 커넥션을 생성하고 사용한다.

Repository

  • 커넥션 동기화를 사용할 경우 DataSourceUtils를 사용해야 한다.
  • 트랜잭션 동기화 매니저를 통해 관리하는 트랜잭션이 있는지 검사한다.
  • 관리하는 트랜잭션이 없는 경우 트랜잭션을 생성해 리턴한다.
  • 커넥션을 릴리즈 할 때 DataSourceUtils.releaseConnection()을 사용해야 한다. 만약 con.close()를 사용ㅎ서 닫아버리면, 커넥션이 유지되지 않는다.

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);
    }
}

커넥션을 파라미터로 전달하는 부분을 모두 제거할 수 있었다.


Service

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 됨
    }

트랜잭션 AOP

트랜잭션 매니저를 통해 훨씬 간결하게 코드를 작성 했지만, 아직도 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);
    }
}

TestCode

스프링 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로 변경 시 코드의 수정이 필요하다. 다음 강의부터는 예외 처리 문제를 해결한다고 한다!

profile
백엔드 개발자

0개의 댓글