Spring DB - 트랜잭션 with 스프링

SeungTaek·2022년 7월 11일
0
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!

트랜잭션 with 스프링

여기의 마지막 트랜잭션 사용 예시를 보자. 트랜잭션 기능을 사용하기 위해 서비스 계층의 클래스에 비즈니스 코드와 트랜잭션 관련 코드가 함께 섞여있다. 해당 코드의 상황은 다음과 같다.

  1. 해당 코드의 트랜잭션은 JDBC 기술에 의존한다.
  2. 트랜잭션은 비즈니스 로직이 있는 서비스 계층에서 시작하는 것이 좋다.
  3. 결국 서비스 계층이 JDBC 등 다른 기술들과 섞여 있게 된다. 즉, 유지보수 하기 어려워진다.

서비스 계층은 다른 기술들에 의존하는 것이 아닌, 최대한 순수하게 구성해야 한다. 예를 들어 순수 JDBC 기술만 사용하다 Jpa를 사용한다 하더라도, 서비스 계층의 코드는 변경되서는 안된다.

JDBC 트랜잭션 코드 예시

public void serviceSomething(){
	...
  con.setAutoCommit(false); //트랜잭션 시작
	//비즈니스 로직
	con.commit(); //성공시 커밋  
	...
}

JPA 트랜잭션 코드 예시

public void serviceSomething(){
	...
  tx.begin(); //트랜잭션 시작
	//비즈니스 로직
	tx.commit(); //성공시 커밋  
	...
}

이제부터 스프링이 제공하는 트랜잭션 기술들을 살펴보자.

1. 스프링 트랜잭션 추상화

스프링은 트랜잭션을 추상화한 PlatformTransactionManager 인터페이스를 제공한다. 그리고 대부분의 구현체도 제공한다.

즉, JDBC 트랜잭션을 사용하든, JPA 트랜잭션을 사용하든 개발자는 PlatformTransactionManager 인터페이스만 의존하면된다.

PlatformTransactionManager

package org.springframework.transaction;

public interface PlatformTransactionManager extends TransactionManager {
  
  TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
  
  void commit(TransactionStatus status) throws TransactionException;
  
  void rollback(TransactionStatus status) throws TransactionException;

}
  • getTransaction() : 트랜잭션을 시작한다.
  • commit() : 트랜잭션을 커밋한다.
  • rollback() : 트랜잭션을 롤백한다.

해당 기능은 트랜잭션 추상화 뿐 아니라, 트랜잭션의 시작부터 끝까지 같은 DB의 커넥션을 유지해주는(동기화) 기능도 제공한다. 이는 트랜잭션 동기화 매니저를 사용한다는 의미인데, 내부에서 쓰레드 로컬(ThreadLocal)을 사용한다.



2. 트랜잭션 AOP, @Transactional

위에서 스프링이 제공하는 기능을 사용해 트랜잭션 추상화와 동기화를 편리하게 할 수 있다는 것을 알았다. 하지만 아직 서비스 계층에 순수한 비즈니스 로직뿐 아니라 트랜잭션 관련 코드가 함께 섞여 들어가는 문제는 해결하지 못했다.

이 역시 스프링 AOP를 통해 문제를 해결할 수 있다. 단지, 트랜잭션이 필요한 곳에 @Transactional 애노테이션만 붙여주면 된다. 실행에 문제가 없으면 commit, 예외가 발생하면 rollback을 자동으로 수행해준다.

// Service class
@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
  Member fromMember = memberRepository.findById(fromId);
  Member toMember = memberRepository.findById(toId);

  memberRepository.update(fromId, fromMember.getMoney() - money);
  validation(toMember); // 여기서 예외가 터진다면 rollback 해준다.
  memberRepository.update(toId, toMember.getMoney() + money);
}

@Transactional은 메서드 위에 붙이면 해당 메서드에만 적용, 클래스 위에 붙이면 외부에서 호출 가능한 public 메서드가 적용범위가 된다.



cf) 데이터소스와 트랜잭션 매니저 with 스프링 부트

스프링 부트는 데이터 소스와 트랜잭션 매니저를 자동으로 스프링 빈으로 등록해준다. 따라서 개발자가 직접 등록할 필요가 없다.

데이터소스

자동으로 dataSource라는 이름으로 빈 등록을 해준다. 이때 스프링 부트는 application.properties 속성을 사용해서 생성하는데, 예를 들어 다음과 같이 설정하면 된다.

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

이때 spring.datasource.url 속성이 없으면 내장 데이터베이스(메모리 DB)를 생성한다. 또한 자동으로 등록해주는 데이터소스는 커넥션풀을 제공하는 HikariDataSource이다.

트랜잭션 매니저

자동으로 transactionManager라는 이름으로 빈 등록을 해준다. 스프링 부트는 현재 등록된 라이브러리를 보고 트랜잭션 매니저를 선택하는데, 예를들어 JDBC 기술을 사용하면 DataSourceTransactionManager를, Jpa를 사용하면 JpaTransactionManager을 빈으로 등록해준다.




Reference

인프런 '스프링 DB - 데이터 접근 핵심 원리'(김영한)

profile
I Think So!

0개의 댓글