트랜잭션
기능 처리의 최소 단위를 뜻하는데 여기서 말하는 최소 단위란 것은 소작업들을 분리할 수 없으며
전체를 하나로 본다는 개념을 말한다. 트랜잭션은 소작업들이 모두 성공하거나 모두 실패해야 한다.
예를 들자면 계좌 이체가 있다. 계좌 이체는 입금 작업과 출금 작업으로 이루어져 있는데
입금, 출금 작업 중 하나만 성공할 수 없으며 모두 성공하거나 모두 실패해야 한다.
커밋(commit)과 롤백(rollback)
DB는 트랜잭션을 처리하기 위해 커밋과 롤백 기능을 제공한다.
커밋 : 내부 작업을 모두 성공 처리한다.
롤백 : 실행 전으로 돌아간다는 뜻으로 즉, 실패 처리한다.
예제 코드를 보면서 이해해보자.
먼저 계좌에 해당하는 DB를 아래와 같이 생성한다.
create table accounts(
accountNum varchar(20) primary key,
name varchar(10) not null,
balance numeric not null
);
INSERT into accounts (accountNum, name, balance)
VALUES('123-1234-1234', '고길동', 10000),
('321-4321-4321', '둘리', 0);
accounts 테이블은 위의 사진처럼 구성된 것을 확인할 수 있다.
JDBC에서 INSERT, DELETE, UPDATE문을 실행할 때마다 자동으로 커밋이 일어난다.
이 기능은 출금 작업이 성공되면 바로 커밋이 되기 때문에 입금 작업의 성공 여부와 상관없이
출금 작업만 별도 처리된다. 따라서 JDBC에서 트랜잭션을 코드로 제어하려면 자동 커밋 기능을 꺼야 한다.
자동 커밋 설정 여부는 setAutoCommit() 메소드로 할 수 있다.
자동 커밋 기능이 꺼지면 commit과 rollback을 제어할 수 있다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TransactionEx {
private static final String JDBC_DRIVER = "org.mariadb.jdbc.Driver";
private static final String JDBC_URL = "jdbc:mariadb://localhost/java";
private static final String USER = "root";
private static final String PASSWORD = "mariadb";
public static void main(String[] args) {
Connection conn = null;
try {
Class.forName(JDBC_DRIVER);
System.out.println("드라이버 로드 성공");
conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
System.out.println("데이터베이스 연결 성공");
// 트랜잭션 시작
conn.setAutoCommit(false); // 자동 커밋 끄기
// 출금하기
String updateSQL1 = """
UPDATE accounts SET balance = balance - ?
WHERE accountNum = ?;
""";
PreparedStatement pstmt1 = conn.prepareStatement(updateSQL1);
pstmt1.setInt(1, 10000);
pstmt1.setString(2, "123-1234-1234");
int updateCount1 = pstmt1.executeUpdate();
if(updateCount1 == 0) {
System.out.println("출금되지 않았습니다.");
}
pstmt1.close();
// 입금하기
String updateSQL2 = """
UPDATE accounts SET balance = balance + ?
WHERE accountNum = ?;
""";
PreparedStatement pstmt2 = conn.prepareStatement(updateSQL2);
pstmt2.setInt(1,5000);
pstmt2.setString(2, "321-4321-4321");
int updateCount2 = pstmt2.executeUpdate();
// 그냥 System.out.println으로 출력시 계좌 이체 성공까지 출력된다.
// 따라서 throw를 통해 예외를 던져준다.
if(updateCount2 == 0) throw new Exception("입금되지 않았습니다.");
pstmt2.close();
conn.commit();
System.out.println("계좌 이체를 성공했습니다.");
// 트랜잭션 종료
}catch(SQLException se) {
se.printStackTrace();
}catch(Exception e) {
// 출금 또는 입금 중 어디에서 문제가 발생
try {
conn.rollback();
}catch(SQLException se) {
}
System.out.println("계좌 이체를 실패했습니다.");
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.setAutoCommit(true); // 자동 커밋 켜기
conn.close();
}catch(SQLException se) {
}
}
}
}
}
올바르게 입금과 출금이 되었다면 위와 같이 출력된다.
데이터베이스를 확인해보자.
계좌번호 123-1234-1234에 해당하는 계좌에서 10,000을 출금했기 때문에 고길동 계좌는 10,000이 남았고,
321-4321-4321에 해당하는 계좌에 5,000을 입금했기 때문에 둘리 계좌는 5,000이 된 것을 볼 수 있다.
만약에 입금하는 부분을 아래와 같이 고친다면 어떻게 될까?
pstmt2.setString(2, "111-1111-1111");
111-1111-1111에 해당하는 계좌번호가 존재하지 않기 때문에 위와 같이 출력됨을 알 수 있다.
'국비 지원 > JDBC' 카테고리의 다른 글
[JDBC] 간단한 게시판 구현해보기 (0) | 2023.06.11 |
---|---|
[JDBC] PreparedStatement를 사용한 데이터 추가, 삭제, 읽기, 수정하기 (0) | 2023.06.05 |
[JDBC] JDBC와 데이터베이스 연결하기 (0) | 2023.05.28 |