인턴 근무 중, JPA를 사용하는 것이 아닌 JDBC 를 사용해 직접 데이터베이스 커넥션을 연결해야했다.
평소에 JPA 어노테이션을 즐겨 사용했던 나는 이번 계기로 @Transaction 어노테이션의 정확한 동작원리와 JDBC 를 사용해 RDBMS를 다룰 때 트랜잭션 관리를 어떻게 하는지 궁금했해 알아보기로 했다.
- 예시코드
public String SampleEndPoint(String datasourceKey, String sql) {
HikariDataSource datasource = getDataSourceByKey(datasourceKey); //1
Statement st = null;
Connection con = null;
Integer result = null;
String rtnVal;
try { //2
con = datasource.getConnection(); //3
con.setAutoCommit(false); //4
st = con.createStatement(); //5
result = st.executeUpdate(sql);
con.commit(); //sql 데이터베이스에 반영 //6
} catch (SQLException e) { //7
/**
* SQLException에 대한 대처 방안 필요
*/
log.error("SQLException 발생!");
if (con != null) {
try {
con.rollback();
} catch (SQLException ex) {}
}
e.printStackTrace();
} finally {
if (st != null) {
try {
st.close(); //9
} catch (SQLException ex) {}
}
if (con != null) {
try {
con.close();
} catch (SQLException ex) {}
}
if (result != null) {
rtnVal = "Success";
} else {
rtnVal = "False";
}
}
return rtnVal;
}
코드 플로우는 다음과 같다.
- datasourceKey 를 이용해, 사용하고자 하는 데이터베이스를 바라본다.
- sqlException 에러 처리를 위해, try-catch-finally 문으로 감싼다.
- 바라보는 데이터베이스와 연결한다.
- con.setAutoCommit(false); 를 통해 자동으로 커밋을 남겨주는 옵션을 false로 바꾼다. => autoCommit 을 false 로 해주게 되면 commit 하기 전까지 데이터베이스에 반영하지 않는다. 커밋에 대한 트랜잭션 내용은 영구적이며, 트랜잭션의 주인이 내가 된다. 해당 코드의 실행 부분이 트랜잭션의 시작점이 된다.
- 데이터베이스에 sql을 날리기 위한 sqlServerStatement 개체를 생성한 후, sql을 실행한다. => 이때 실행시키는 함수로는 execute, executeQuery, executeUpdate 가 있다. 각 함수마다 사용하는 상황과 반환값이 다르기 때문에 찾아보는 것을 추천한다. 나는 insert 쿼리였기 때문에 변경되는 row 수를 반환하는 executeUpdate 함수를 실행하였다.
- 변경 사항을 commit (데이터베이스에 반영) 한다.
- 만약 실행하는 과정에서 sqlException 이 발생했다면, 연결을 확인하고 변경사항을 rollback 시킨다. => 사실 실패한 트랜잭션은 반영되지 않는다. sql이 2개 이상일 경우에는 이전에 실행된 sql에 대해서 rollback 처리를 반드시 해야한다. 공부하는 입장이기 때문에 코드를 작성하였다.
- 로직이 끝났다면, statement 값과 커넥션을 끊은 후, 처리 결과에 대한 성공여부를 반환한다.
- 필요성
다음과 같은 방식이 자바에서 유일하게 데이터베이스와 연결하는 방법이며, @Transaction 어노테이션과 TranscationManager 또한 같은 방식으로 이루어진다. 해당 내용은 다음 포스팅에 이어서 진행하겠다.
con.setAutoCommit(false); 를 선언하지 않으면, 하나의 Sql statement 가 하나의 트랜잭션이 된다.
그렇게 되면, 위 코드에서는 문제가 안 생기지만, 실제 서비스 로직에서는 문제가 생긴다.
다음과 같은 예시를 들어보겠다.
A가 B에게 100만원을 송금한다고 가정해보자.
1) 우선 A의 계좌 잔고에서 100만원을 차감해야한다. (UPDATE Query)
2) B의 계좌 잔고에 100만원을 차증해야한다. (UPDATE Query)
만약 트랜잭션이 커밋되게 되면 데이터베이스에 영구 저장된다.
그런데 1번 SQL이 실행되고 난 후, 2번 SQL에서 에러가 발생해 롤백되면 어떻게 될까 ?
A만 100만원 손해보게 되고, 100만원은 사라지게된다.
때문에 상황에 맞게 작업의 단위인 트랜잭션을 설정하고, 직접 관리(Commit, RollBack) 해야한다.
다시 A가 B에게 100만원을 송금한다고 가정해보자.
1) con.setAutoCommit(false);
2) A의 계좌 잔고에서 100만원 차감. (UPDATE Query)
3) B의 계좌 잔고에 100만원 차증. (UPDATE Query)
3번 sql에서 에러가 발생해도, 처음으로 RollBack 하기 때문에 A의 100만원 무사해진다.
추가로 트랜잭션을 사용하는 동안에는 RDBMS 에서는 무결성을 보장하기 위해 lock 을 건다.
트랜잭션 isolation 이 lock 범위를 설정하는데, 이는 다음 포스팅에서 @Transaction 과 함께 다루겠다.
'개인 공부 > Spring' 카테고리의 다른 글
[Spring] Redis vs EHcache vs HashMap (0) | 2022.11.09 |
---|---|
[Spring] @Transactional 파해치기 (0) | 2022.11.08 |
[Spring] #9 JPA 엔티티 매핑 (0) | 2022.04.18 |
[Spring] #8 JPA 동작 원리 (0) | 2022.04.16 |
[Spring] #7 JPA의 필요성 (0) | 2022.04.15 |