JDBC Transaction Savepoints
JDBC 3.0 사양부터 세이브포인트(저장점, 트랜잭션 내부 경계)가 지원되기 시작했습니다. 트랜잭션 내부의 세이브포인트를 지정함으로서 포인트 이후에 실행한 작업을 전부 롤백할 수 있다. 트랜잭션이라 함은 일반적으로 원자성을 일컫는 것으로 전부 혹은 아무것도 아님(All or Nothing)을 나타낸다. 이러한 원자적 특성을 세이브포인트를 지정함으로서 완화시킬 수 있다. 주의할 점은 세이브포인트는 로컬 트랜잭션에서만 지원된다는 점이다.
먼저 JDBC 세이브포인트에 대해서 알아보기 전에 간단하게 SQL을 이용해서 세이브포인트 사용예를 보도록 하자.
SQL예(오라클)
INSERT INTO TEST_USER (NAME) VALUES ('JOHN'); SAVEPOINT MARK; INSERT INTO TEST_USER (NAME) VALUES ('MARK'); SAVEPOINT JACK; INSERT INTO TEST_USER (NAME) VALUES ('JACK'); SAVEPOINT PAUL; INSERT INTO TEST_USER (NAME) VALUES ('PAUL'); ROLLBACK TO JACK; INSERT INTO TEST_USER (NAME) VALUES ('JANE'); COMMIT;
실행 결과
SQL문은 실행한 결과는 위와 같은데 SAVEPOINT로 특정 세이브포인트까지 롤백을 하게되면 롤백실행한 부분부터 지정한 세이브포인트까지 롤백 되는 것을 알수있다. 구조적으로 나중에 지정한 세이브포인트는 직전 세이브 포인트에 종속적이라고 보면 되겠다. 중첩된 트랜잭션이라고 보면 되겠다. 그리고 지정한 세이브포인트 이전과 롤백을 실행한 이후의 SQL에는 영향을 미치지 않았다는 것을 알 수 있다.
다음으로 jdbc를 이용해서 세이브포인트를 설정하는 소스코드를 살펴보자.
Connection conn = null; Statement stmt = null; try { conn = DriverManager.getConnection(url, id, pw); conn.setAutoCommit(false); stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO TEST_USER (NAME) VALUES ('JOHN')"); Savepoint savepoint = conn.setSavepoint(); try { stmt.executeUpdate("INSERT INTO TEST_USER (NAME) VALUES ('MARK')"); } catch (Exception e) { conn.rollback(savepoint); } conn.commit(); } catch (Exception e) { try { conn.rollback(); } catch (SQLException e1) { } } finally { try { stmt.close(); } catch (SQLException e) { } try { conn.close(); } catch (SQLException e) { } }
일반적인 JDBC를 이용한 소스와 크게 다르지 않다. 소스코드 12라인에 있는 커넥션 인스턴스의 setSavepoint 메소드 호출한 부분을 보면 될 듯 하다. 롤백할 경우는 기존의 rollback메소드 호출시 세이브포인트 인스턴스를 전달해 주면 된다. JDBC스펙에 정의되기 이전부터 각 DBMS별로 세이브포인트 기능이 제공되는 경우도 있었으므로 이경우 표준 JDBC 인터페이스 이외에 구현을 직접 호출하여 실행하는 것도 가능하다. 오라클의 경우를 예를 들어보면 OracleConnection.oracleSetSavepoint을 이용하여 세이브 포인트를 설정할 수 있다.
세이브포인트에 관련된 JDBC인터페이스를 전체적으로 살펴보도록 하자.
public Savepoint setSavepoint() throws SQLException;
public Savepoint setSavepoint(String name) throws SQLException;
세이브포인트를 설정하는 인터페이스는 세이브포인트명을 명시적으로 지정하느냐의 차이에 따라 두가지로 나뉘는데 명시적으로 지정하지 않았을경우 내부적으로는 정수 ID가 할당되는 듯 하다.
public int getSavepointId() throws SQLException;
public String getSavepointName() throws SQLException;
세이브포인트ID혹은 세이브포인트 명을 취득하는 인터페이스이다. 명시적으로 세이브포인트명을 지정하였을 경우 ID취득 인터페이스를 호출하면 SQLException이 발생한다. 역으로 지정하지 않았을 경우 세이브포인트명 취득 인터페이스 호출이 예외를 발생시킨다.
public void rollback(Savepoint savepoint) throws SQLException;
롤백인터페이스는 현재 트랜잭션에서 지정한 세이브포인트까지 롤백한다. 일단 실행한 후에 세이브포인트를 참조할려고 하면 SQLException이 발생한다.
public void releaseSavepoint(Savepoint savepoint) throws SQLException;
지정한 세이브포인트를 제거한다. 일단 제거한 세이브포인트를 참조하려하면 예외가 발생한다.
위의 인터페이스들은 공통적으로 DB에러 발생후나 자동커밋모드, 글로벌 트랜잭션과 관계있는 경우에도 예외가 발생하므로 사용시에 주의하도록 하자.
세이브포인트를 이용하여 트랜잭션 내부에서 실행은 해야하지만 실행 경과가 전체 트랜잭션에 영향을 미치지 않아야 할 경우 등에
사용할 수 있다. 예를 들어 쇼핑몰의 주문 프로세스상에서 주문결과에 영향을 미치지 않는 내부 프로세스에 의한 에러시에도 전체
트랜잭션이 실패로 처리된다면 결과적으로 기회비용의 손실이 발생하게 될수 있다. 이 경우 세이브포인트등을 이용 트랜잭션을 분할하여 별도의 리커버리 프로세스를 진행하면 손실을 줄일수 있을 것이다. 트랜잭션의 경계를 설정하는 일은 개발을 진행함에 있어서 중요한 부분이므로 항상 명확히 하여 구현하는 것이 좋을 듯 합니다.