@RequiredArgsConstructor
public class MemberServiceV2 {
private final DataSource dataSource;
private final MemberRepository memberRepository;
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
Connection con = dataSource.getConnection();
try {
con.setAutoCommit(false); // 트랜잭션 시작
bizLogic(con, fromId, toId, money); // 비즈니스 로직
con.commit(); // 성공시 커밋
} catch (Exception e) {
con.rollback(); // 실패시 롤백
throw new IllegalStateException(e);
} finally {
release(con);
}
}
private void bizLogic(Connection con, String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(con, fromId);
Member toMember = memberRepository.findById(con, toId);
memberRepository.update(con, fromId, fromMember.getMoney() - money);
memberRepository.update(con, toId, toMember.getMoney() + money);
}
}
현재 해당 코드에 어떤 문제가 있을까.
트랜잭션은 비즈니스 로직이 있는 서비스 계층에서 시작하는 것은 좋아 보인다.
하지만 트랜잭션을 사용하기 위해서 DataSource, Connection, SQLException과 같은 JDBC 기술에 의존하고 있다.
나중에 JDBC말고 다른 기술을 사용하게 된다면 서비스 코드들도 모두 변경을 해줘야한다.
하지만 그건 조금 흔히 말하는 OCP 원칙에 어긋나 보인다. 핵심 비즈니스 로직과 JDBC 기술이 섞여있으므로 유지보수가 어렵다.
하나씩 해결해보자.
// jdbc transaction
Connection connection = dataSource.getConnection();
connection.setAutoCommit(false); // 트랜잭션 시작
connection.commit(); // 성공시 커밋
connection.rollback(); // 실패시 롤백
// jpa transaction
EntityManagerFactory emf = Persistence.createEntityManagerFactory("...");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
tx.commit(); // 성공시 커밋
tx.rollback(); // 실패시 롤백
우선 보다시피 트랜잭션을 사용하는 코드는 사용하는 기술마다 다르다.
하지만 해당 기술이 무엇이든 간에, 하는 역할은 같다.
트랜잭션을 시작하고, 커밋을 하거나 롤백을 하면 된다.
그러니 인터페이스를 만들고 각각의 기술에 맞는 구현체를 만들면 된다.
스프링은 이미 해당 인터페이스와 구현체를 제공하고 있다.
핵심은 PlatformTransactionManager 라는 인터페이스이다.
트랜잭션 매니저가 하는 역할은 크게 2가지이다.
* 트랜잭션 추상화 (이건 인터페이스를 통해 구현완료)
* 리소스 동기화
트랜잭션을 유지하려면 트랜잭션의 시작부터 끝까지 같은 DB의 connection을 유지해야한다.
방금 첫 코드 화면을 보면 같은 connection을 사용하기 위해서 파라미터에 connection을 전달해주는 방법을 사용했다.
하지만 파라미터로 connection을 넘겨주는 방법은 코드가 지저분해지고, connection을 넘기는 메서드와 connection을 넘겨주지 않는 메서드가 중복되서 생성되므로 단점이 분명해보인다.
스프링은 트랜잭션 동기화 매니저라는 것을 제공한다.
얘는 쉽게 생각해서 현재 사용하고 있는 connection을 보관하고 있다라고 생각하면 된다.
동작 과정을 그림으로 설명하면 다음과 같다.
트랜잭션 매니저는 dataSource를 통해 connection을 만들고 transaction을 시작한다.
트랜잭션 매니저는 transaction이 시작된 connection을 트랜잭션 동기화 매니저에 보관한다.
Repository는 트랜잭션 동기화 매니저에 보관된 connection을 꺼내서 사용한다. 그렇기에 이제 connection을 인자로 전달해주지 않아도 된다.
transaction이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 보관된 connection을 통해 transaction을 종료하고, connection도 닫는다.
@RequiredArgsConstructor
public class MemberRepository {
private final DataSource dataSource;
public void close(Connection con, Statement stmt, Resultset rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
// 트렌젝션 동기화 사용
DataSourceUtils.releaseConnection(con, dataSource);
}
private Connection getConnection() throws SQLException {
Connection con = DataSourceUtils.getConnection(dataSource);
return con;
}
}
이제 repository에서는 인자로 connection을 받는 것이 아닌, 동기화 매니저로부터 커넥션을 받게 된다.
DataSourceUtils.getConnection()은
트랜잭션 동기화 매니저가 관리하는 connection이 있으면 해당 connection을 반환하고,
매니저가 관리하는 connection이 없다면 새로운 connection을 생성해서 반환한다.
단순히 connection.close()를 하게 되면 connection이 유지가 되지 않으므로 transaction을 사용할 수 없다.
트랜잭션을 종료(commit, rollback)할 때까지 connection은 살아있어야한다.
DataSourceUtils.releaseConnection은 바로 connection을 닫지 않는다.
트렌젝션을 사용하기 위해 동기화된 connection은 그대로 유지해주고, 동기화 매니저가 관리하는 connection이 없는 경우에 해당 connection을 닫게 한다.
@Slf4j
@RequiredArgsConstructor
public class MemberService {
private final PlatformTransactionManager transactionManager;
private final MemberRepository memberRepository;
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
bizLogic(fromId, toId, money);
transactionManager.commit(status); // 성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); // 실패시 롤백
throw new IllegalStateException(e);
}
}
현재 PlatformTransactionManager을 주입받고 있다. 지금은 JDBC 기술을 사용한다 가정하고 DataSourceTransactionManager를 주입받아야 한다.
'스프링 강의 필기' 카테고리의 다른 글
db 2편 - 스프링 트랜잭션 전파 (1) | 2023.11.27 |
---|---|
DB 2편 - 스프링 트랜잭션 이해 (1) | 2023.11.22 |
DB 강의 1편 - 트랜잭션 문제 해결(2) (1) | 2023.11.09 |