자동 커밋
set autocommit true; //자동 커밋 모드 설정, 이게 기본임
수동 커밋
set autocommit false; //수동 커밋 모드 설정
set lock_timeout 60000; //단위는 ms, 60s
위 코드가 필수는 아니고, DB마다 설정된 기본 lock 대기 시간이 있다.
※ 트랜잭션, lock은 DB마다 동작하는 방식이 조금씩 다르기 때문에, 해당 DB가 의도한대로 동작하는지 테스트한 이후에 사용하도록 하자.
참고) 트랜잭션 시작, 트랜잭션 종료의 의미
DB에서,
애플리케이션에서,
@RequiredArgsConstructor
public class MemberServiceV1 {
private final MemberRepositoryV1 memberRepository;
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); //IllegalStateException o/x
memberRepository.update(toId, toMember.getMoney() + money);
}
private void validation(Member toMember) {
if (toMember.getMemberId().equals("ex")) {
throw new IllegalStateException("이체 중 예외 발생");
}
}
}
service의 accountTransfer()를 트랜잭션 단위로 잡을 것인데, findById(), findById(), update(), update()는 각각 커넥션 풀에서 커넥션 획득, 커넥션을 반납하고 있다. 그런데, 하나의 트랜잭션이므로 동일한 커넥션을 사용해야 한다.
Repository(변화1~3 위주로 볼 것)
public Member findById(Connection conn, String memberId) throws SQLException { //변화1. 매개변수에 Connection 추가
String sql = "select * from member where member_id = ?";
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//변화2. conn = dataSource.getConnection(); 코드 없음
pstmt = conn.prepareStatement(sql);
...
} catch (SQLException e) {
...
} finally {
//변화3. conn 반납 x
close(null, pstmt, rs);
}
}
public void update(Connection conn, String memberId, int money) throws SQLException { //변화1. 매개변수에 Connection 추가
String sql = "update member set money = ? where member_id = ?";
PreparedStatement pstmt = null;
try {
//변화2. conn = dataSource.getConnection(); 코드 없음
pstmt = conn.prepareStatement(sql);
...
} catch (SQLException e) {
...
} finally {
//변화3. conn 반납 x
close(null, pstmt, null);
}
}
Service
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
Connection con = dataSource.getConnection(); //커넥션 획득
con.setAutoCommit(false); // 트랜잭션 시작
try {
bizLogic(con, fromId, toId, money); //비지니스 로직: SQLException, IllegalStateException
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 void release(Connection con) {
if (con != null) {
try {
con.setAutoCommit(true);
con.close();
} catch (SQLException e) {
log.info("error", e);
}
}
}
ⅰ.
set autocommit false;
select
select
update
update
commit;
ⅱ.
set autocommit false;
select
select
update
rollback;