[JDBC] 트랜잭션 적용 - Connection 직접 이용
JDBC 코드를 직접 짤 때 트랜잭션을 이용하는 방법이다.
비즈니스 로직을 실행할 때, 하나의 커넥션 내에서 동작해야 하므로 메서드 사용 시 connection을 파라미터로 넘겨야 한다.
따라서 서비스 계층, 데이터접근 계층에 모두 Connection을 파라미터로 받아서 이용해야 한다.
public void loadLotsOfData(DataList dataList) {
String sql = "insert into table values ( /* ... */)";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
conn.setAutoCommit(false); //트랜잭션 시작
/*
DB 관련 로직
*/
conn.commit(); //트랜잭션 종료
} catch (SQLException e) {
e.printStackTrace();
conn.rollback(); //실패 시 롤백
} finally {
close();
}
}
위의 방법은 여러 가지 문제가 있다.
- 트랜잭션을 위해 Connection, SQLException, DataSource와 같은 JDBC 기술에 의존해야 한다는 문제가 있다. 따라서 데이터 접근 기술을 JDBC에서 다른 기술로 바꾸게 되면 서비스 코드를 전부 변경해야 한다.
- 같은 트랜잭션을 유지하기 위해 커넥션을 파라미터로 넘기고, 트랜잭션 유무에 따라 코드를 분리해야 한다는 문제도 있다.
- 트랜잭션 적용 코드를 보면 try, catch, finally 구문을 반복적으로 사용해야 한다.
이 문제를 해결하기 위해 트랜잭션을 추상화 한 TransactionManager 인터페이스가 존재한다.
따라서 서비스계층은 특정 기술에 의존하지 않고 TransactionManager라는 추상화된 인터페이스에 의존하여 원하는 구현체를 DI를 통해 주입하면 된다.
[TransactionManager] 트랜잭션 추상화, 동기화
트랜잭션 매니저는 간단하게 트랜잭션 시작, 커밋, 롤백으로 이루어져 있다.
스프링 트랜잭션 추상화의 핵심은 PlatformTransactionManager 인터페이스이다.
public interface PlatformTransactionManager extends TransactionManager {
//트랜잭션 시작
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
//커밋
void commit(TransactionStatus status) throws TransactionException;
//롤백
void rollback(TransactionStatus status) throws TransactionException;
}
위의 인터페이스를 통해 기존의 복잡하던 코드를 줄일 수 있다.
트랜잭션 동기화
Connection을 직접 사용하는 방식에서는 트랜잭션 동기화를 위해 Connection을 계속 파라미터로 주고받았다.
이를 해결하기 위해 스프링은 트랜잭션 동기화 매니저를 제공한다. 이것은 스레드 로컬(ThreadLocal)을 사용해서 커넥션을 동기화해 주고, TransactionManager 내부에서 이 트랜잭션 동기화 매니저가 동작한다.
트랜잭션 동기화 매니저를 사용하려면 몇 가지 주의해야 할 점이 있다.
커넥션을 close, getConnection 할 때 DataSourceUtils를 사용해야 한다.
//getConnection
Connection con = DataSourceUtils.getConnection(dataSource);
//close
DataSourceUtils.releaseConnection(con, dataSource);
DataSourceUtils.releaseConnection()은 바로 커넥션을 닫아버리는 것이 아니다. 바로 닫아버리면 트랜잭션이 종료되기 전까지 커넥션이 유지되지 않는 문제가 발생한다.
따라서 DataSourceUtils.releaseConnection()은 동기화된 커넥션은 닫지 않고 그대로 유지하고, 트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우는 바로 커넥션을 닫는다.
PlatformTransactionManager
PlatformTransactionManager의 구현체를 주입받아 사용한다.
JDBC 기술 사용 시 DataSourceTransactionManager, JPA 사용 시 JpaTransactionManager를 주입받는다.
pricate final PlatformTransactionManager txManager;
TransactionStatus 클래스
transactionaManager.getTransaction()은 TransactionStatus status를 반환한다. 현재 트랜잭션의 상태정보를 포함하고 있어 커밋, 롤백 시 사용한다.
//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
/*
비즈니스 로직 실행
*/
transactionManager.commit(status); //커밋
}catch(Exception e){
transactionManager.rollback(status); //예외 발생 시 롤백
//예외 처리
}
//commit or rollback 시 커넥션을 자동으로 반납한다.
@Transactional
@Transactional을 사용하면 스프링이 AOP를 통해 프락시를 도입하여 트랜잭션 처리를 모두 해결해 준다.
따라서 서비스 계층에 순수한 비즈니스 로직만 남길 수 있게 된다.
이 애노테이션을 사용하면 단순히 트랜잭션이 필요한 메서드에 @Transactional 애노테이션을 선언해주기만 하면 위에서 알아봤던 복잡하고 지저분한 트랜잭션 관련 코드를 모두 없앨 수 있다.
@Transactional
public void businessLogic(Long id, int money) throws SQLException {
bizLogic(id, money);
}
스프링 부트 자동 리소스 등록
스프링 부트가 없었다면, DataSource, TransactionManager 등을 모두 스프링 빈으로 직접 등록해야 할 것이다. 그러나 이제는 스프링부트가 자동으로 스프링 빈으로 등록해 준다.
DataSource
스프링부트가 dataSource라는 이름의 스프링 빈을 자동으로 등록한다.
또한 application.properties의 속성을 이용한다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=
PlatformTransactionManager
트랜잭션 매니저도 마찬가지로 transactionManager라는 이름의 스프링 빈을 자동으로 등록한다.
어떤 트랜잭션 매니저를 선택할지는 등록된 라이브러리를 보고 판단한다.
JDBC : DataSourceTransactionManager
JPA : JpaTransactionManager
마지막으로 예외 처리를 위한 try - catch - finally 반복 사용 문제와 서비스가 처리할 수 없는 SQLException의 문제가 있다.
이 문제를 해결하려면 SQLException 체크 예외를 런타임 예외로 전환해서 서비스 계층에 던져야 한다. 이 과정은 SQL Exception Translator을 이용해서 해결할 수 있다.
그러나 이 문제를 해결해도 try - catch - finally 문은 계속 사용하여야 한다.
이러한 문제를 해결해 주는 것이 다양한 데이터 접근 기술들이다. JdbcTemplate, MyBatis, JPA 등을 이용하면 이 문제도 해결할 수 있다.
트랜잭션 관련 글
📁[Database] 트랜잭션의 동작 원리와 ACID 속성
📁[Database] 트랜잭션 - 락(Lock) 동작 원리와 동작 과정
📁[Java, Spring] 트랜잭션을 적용하는 여러 가지 방법
📁[Spring] @Transactional 세부 설정 - 격리 수준 / 전파 수준 설정
📁[Database] 트랜잭션의 격리성 문제 - Dirty Read / Non-Repeatable Read / Phantom Read
(참고) 인프런 - 김영한 님 스프링 DB 1편
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1#
'Database > 데이터 접근 기술' 카테고리의 다른 글
[SQL/Mysql, MariaDB] 드라이버 연결 (0) | 2023.07.25 |
---|---|
[MyBatis] 동적쿼리, 기타 문법 (0) | 2023.05.15 |
[MyBatis] 마이바티스 기본 사용법 (2) | 2023.05.15 |
[Jdbc] JdbcTemplate 사용법 및 적용예제 (0) | 2023.05.04 |
[JDBC] 커넥션 풀, DataSource (0) | 2023.05.01 |