트랜잭션 사용 이유?
두 개 이상의 쿼리를 수행했을 때, 데이터의 일관성을 보장하기 위함이다.
예를 들어, A통장에서 B통장으로 1만원을 송금하는 기능을 구현한다고 가정하자.
필요한 쿼리는
1) A통장 잔고에서 1만원 빼기
2) B통장 잔고에 1만원 더하기
이다.
이때, 1) 쿼리가 성공한 후 2) 쿼리가 실패하면 어떻게 해야할까?
송금 한 사람만 있고, 받은 사람은 없는 상황이 나오지 않으려면 1) 쿼리도 실행취소(rollback)을 해 주어야 한다. 이때 필요한게 트랜잭션이다.
트랜잭션 이란?
트랜잭션은 업무의 논리적 단위를 의미한다. 위의 예시에서는 한 단위가 1) 출금, 2) 입금이 된다. 이 단위에서 하나라도 실패가 발생 시 전체 쿼리를 rollback시켜 주어야 한다.
자바에서의 트랜잭션 구현
자바에서 Connection
, PreparedStatement
을 이용하여 쿼리를 실행한 경우 자동으로 commit
을 수행한다.
따라서, 트랜잭션을 구현하기 위해서는 자동 커밋 옵션을 false
로 바꾸어 주어야 한다. (setAutoCommit(false)
).
또한 동일한 Connection객체를 사용하여야 하나의 트랜잭션으로 만들 수 있다.
트랜잭션 구현 코드
// 비밀번호 변경 public void changePassword(int userId, String oldPwd, String newPwd) { Connection conn = null; try { conn = DatabaseUtil.getConnection(); // 트랜잭션 시작 conn.setAutoCommit(false); // 비밀번호 확인 - 비밀번호가 일치하지 않을 경우 리턴 // - getUserByuserId()로 사용자정보를 조회하여 비밀번호 확인 UserVO user = userRepository.getUserByuserId(conn, userId); if(!user.getPassword().equals(oldPwd)) { System.out.println("비밀번호가 일치하지 않습니다."); return; } // 비밀번호 변경 - 비밀번호 변경 실패 시 롤백 // - updatePassword()를 호출하여 비밀번호 변경 userRepository.updatePassword(conn, userId, newPwd); System.out.println("비밀번호 변경 완료"); // 예외 발생 테스트 // if (true) { // throw new SQLException("임의로 예외 발생"); // } // 비밀번호 변경 로그 기록 // - 데이터베이스에 직접 접근하여 insert문 수행 // - 차후에 PwdChangeLogRepository클래스로 책임 분리 String logSql = "INSERT INTO password_change_log(USER_ID, DESCRIPTION) VALUES (?, ?)"; PreparedStatement pstmt = conn.prepareStatement(logSql); pstmt.setInt(1, userId); pstmt.setString(2, "비밀번호를 변경하였습니다"); pstmt.executeUpdate(); conn.commit(); // 트랜잭션 커밋 } catch (SQLException e) { try { conn.rollback(); // 예외발생시 롤백 } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { try { conn.setAutoCommit(true); // 자동 커밋 복원 conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }