commit, rollback을 진행하지 않으면 DB에 반영되지 않음.
반영되지 않으므로 해당 쿼리를 실행한 세션이 아닌 다른 세션에는 반영이 안됨.
쿼리를 실행한 세션은 적용된 것처럼 보이지만 적용된게 아님.
트랜잭션 기능을 제대로 수행하려면 수동커밋을 사용해야한다.
수동커밋모드로 설정하는 것을 트랜잭션을 시작한다고 표현할 수 있다.
세션1이 트랜잭션을 시작하고 데이터를 수정하는 동안 세션2가 같은 데이터를 수정하게 되면 문제가 발생하고, 트랜잭션ACID의 원자성이 깨지는 것이다.
이런 문제를 해결하기 위해 트랜잭션 시작하고 commit 또는 rollback 전까지
해당 데이터를 수정할수 없게 막는기능
일반적으로 조회(select)는 락을 사용하지 않는다.
select for update 구문을 사용하면 조회에서도 락 기능 사용가능
@RequiredArgsConstructor
@Slf4j
public class MemberServiceV2 {
private final DataSource dataSource;
private final MemberRepositoryV2 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);
validation(toMember);
memberRepository.update(con, toId, toMember.getMoney() + money);
}
private static void release(Connection con) {
if (con != null) {
try {
con.setAutoCommit(true); //자동커밋으로 변경 후 풀로 돌려주기
con.close();
} catch (Exception e) {
log.info("error",e);
}
}
}
private static void validation(Member toMember) {
if (toMember.getMemberId().equals("ex")) {
throw new IllegalStateException("이체중 예외 발생");
}
}
}
public interface PlactformTransactionManager extends TransactionManager{
//트랜잭션 시작
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
//커밋
void commit(TransactionStatus status) throws TransactionException;
//롤백
void rollback(TransactionStatus status) throws TransactionException;
}
트랜잭션을 유지하려면 트랜잭션의 시작부터 끝까지 같은 데이터베이셔 커넥션을 유지해야한다.
트랜잭션 매니저는 데이터소스를 통해 커넥션을 만들고 트랜잭션 시작.
트랜잭션 매니저는 트랜잭션 시작된 커넥션을 트랜잭션 동기화 매니저에 보관.
리포지토리는 태랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용.
트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션동기화매니저에 보관된 커넥션을 통해 트랜잭션을 종료하고 커넥션도 종료.
리포지토리
...
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
//트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용
DataSourceUtils.releaseConnection(con,dataSource);
//JdbcUtils.closeConnection(con);
}
private Connection getConnection() throws SQLException {
//트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용
Connection con = DataSourceUtils.getConnection(dataSource);
//Connection con = dataSource.getConnection();
log.info("get connection={},class={}",con,con.getClass());
return con;
}
서비스
...
//트랜잭션 매니저 주입
private final PlatformTransactionManager transactionManager;
private final MemberRepositoryV3 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);
}
}
@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
bizLogic(fromId, toId, money);
}
데이터소스 자동등록
스프링부트는 데이터소스(DataSource)를 스프링빈에 dataSource라는 이름으로 자동등록한다.
자동등록된 DataSource는 application.properties에 설정된 속성을 사용해서 생성한다.application.properties spring.datasource.url= spring.datasource.username= spring.datasource.password=
트랜잭션매니저 자동등록
스프링부트는 트랜잭션 매니저(PlatformTransactionManager)를 스프링 빈에 taransactionManager라는 이름으로 자동등록한다.