본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!
여기의 마지막 트랜잭션 사용 예시를 보자. 트랜잭션 기능을 사용하기 위해 서비스 계층의 클래스에 비즈니스 코드와 트랜잭션 관련 코드가 함께 섞여있다. 해당 코드의 상황은 다음과 같다.
서비스 계층은 다른 기술들에 의존하는 것이 아닌, 최대한 순수하게 구성해야 한다. 예를 들어 순수 JDBC 기술만 사용하다 Jpa를 사용한다 하더라도, 서비스 계층의 코드는 변경되서는 안된다.
JDBC 트랜잭션 코드 예시
public void serviceSomething(){
...
con.setAutoCommit(false); //트랜잭션 시작
//비즈니스 로직
con.commit(); //성공시 커밋
...
}
JPA 트랜잭션 코드 예시
public void serviceSomething(){
...
tx.begin(); //트랜잭션 시작
//비즈니스 로직
tx.commit(); //성공시 커밋
...
}
이제부터 스프링이 제공하는 트랜잭션 기술들을 살펴보자.
스프링은 트랜잭션을 추상화한 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
)을 사용한다.
위에서 스프링이 제공하는 기능을 사용해 트랜잭션 추상화와 동기화를 편리하게 할 수 있다는 것을 알았다. 하지만 아직 서비스 계층에 순수한 비즈니스 로직뿐 아니라 트랜잭션 관련 코드가 함께 섞여 들어가는 문제는 해결하지 못했다.
이 역시 스프링 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 메서드가 적용범위가 된다.
스프링 부트는 데이터 소스와 트랜잭션 매니저를 자동으로 스프링 빈으로 등록해준다. 따라서 개발자가 직접 등록할 필요가 없다.
데이터소스
자동으로 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
을 빈으로 등록해준다.
인프런 '스프링 DB - 데이터 접근 핵심 원리'(김영한)