트랜잭션은 원자성
일관성
격리성
지속성
을 다 보장해야함
원자성
: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 실패해야함
일관성
: 모든 트랜잭션은 일관성 있는 데이터베이스 상태로 유지해야함
격리성
: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야함
-> 격리성
은 동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준을 선택해야 한다.
트랜잭션 격리 수준
READ UNCOMMITED
(커밋되지 않은 읽기)
READ COMMITTED
(커밋된 읽기)
REPEATABLE READ
(반복 가능한 읽기)
SERIALIZABLE
(직렬화 가능)
일반적으로는 READ COMMITTED
를 많이 사용
지속성
: 트랜잭션을 성공적으로 끝내면 결과가 항상 기록되어야함 (내용을 복구하기 위해서)
설정
커넥션
을 맺음세션
을 만듬세션
은 트랜잭션을 시작하고, COMMIT
또는 ROLLBACK
을 함커넥션
을 닫거나 DBA 세션을 강제로 종료하면 세션은 종료됨수동 커밋
vs 자동 커밋
자동 커밋
과 수동 커밋
을 이해해야 한다자동 커밋
으로 설정하면 쿼리 실행 직후에 따로 COMMIT
이나 ROLLBACK
명령어를 호출하지 않아도 되지만, 원하는 트랜잭션 기능을 제대로 사용할 수 없다.수동 커밋
으로 설정하면 쿼리 직후에 꼭 COMMIT
이나 ROLLBACK
을 호출해야한다.수동 커밋
모드로 전환하는 것을 트랜잭션을 시작한다라고 표현할 수 있다.수동 커밋
으로 했을 경우 세션 1에서 쿼리 작업을 진행 후 COMMIT
명령어를 호출하지 않으면
세션 1에서는 쿼리 작업의 결과를 볼 수 있지만, 세션 2에서는 아직 반영이 되지 않았기 때문에 확인할 수 없다.
자동커밋 설정 : set autocommit true;
수동커밋 설정 : set autocommit false;
DB 락
: 세션 1과 세션 2가 동시에 같은 데이터를 수정하면 원자성
의 문제점이 발생한다. 이러한 문제점을 막기 위해서 세션이 트랜잭션을 시작하고 데이터를 수정할 때 COMMIT
이나 ROLLBACK
을 하지 않았으면, 다른 세션에서 해당 데이터를 수정할 수 없도록 막는 것을 DB 락
이라고 한다.
세션1에서 COMMIT
이나 ROLLBACK
을 수행하면, 세션 2에서는 락
을 얻으면서 쿼리 진행이 가능하다
락 타임아웃
: 다른 세션에서 락
을 얻을 때 까지 기다리는데, 그 락을 타임아웃으로 설정해서
시간이 넘어도 락
을 얻지 못하면 락 타임아웃 오류가 발생
트랜잭션은 비즈니스 로직이 있는 서비스 계층
에서 시작해야 한다.
DB 트랜잭션을 사용하려면 트랜잭션을 사용하는 동안 같은 커넥션
을 유지해야 한다.
트랜잭션 매니저
: 데이터베이스 시스템에서 트랜잭션을 관리하는 중요한 구성 요소
-> Oracle, MySQL, PostgreSQL 등에서 트랜잭션 매니저가 내장되어 있음
-> 트랜잭션 매니저
는 크게 트랜잭션 추상화
와 리소스 동기화
의 역할을 담당한다
트랜잭션 추상화
: 데이터 접근 기술에 따라서 (JPA, JDBC 등등) 트랜잭션 구현체를 만들어두었기 때문에 가져다 사용하기만 하면 된다리소스 동기화
: 커넥션
을 파라미터로 전달 할 필요 없이 트랜잭션 동기화 매니저
를 통해서 커넥션을 보관해서 커넥션이 필요할 때 갖다 씀 (커넥션
유지가 쉬워짐)서비스에서 트랜잭션을 시작하면 try
catch
finally
포함한 commit
rollback
코드가 반복됨
달라지는 부분은 서비스 비즈니스 로직만 달라짐
이런 반복되는 부분을 템플릿 콜백 패턴
을 활용하면 해결 가능함
-> 스프링에서 제공하는 TransactionTemplate
클래스
-> 언체크 예외가 발생하면 rollback
처리, 체크 예외의 경우 commit
처리
트랜잭션 템플릿
덕분에 반복되는 코드는 제거 할 수 있다
하지만 서비스 로직 뿐 아니라 트랜잭션을 처리하는 기술 로직도 포함되어 있다.
//ex)
txTemplate.executeWithoutResult((status) -> { // 트랜잭션 처리 기술
try {
bizLogic(fromId, toId, money); //비즈니스 로직
}
catch (SQLException e) {
throw new IllegalStateException(e);
}
});
이러한 문제점을 해결하기 위해 트랜잭션 AOP
를 사용한다
트랙재션 처리가 필요한 곳에 @Transactional
어노테이션을 붙여주면 됨
이러한 방법을 선언적 트랜잭션 관리
라고 불린다.
위에서 설명한 트랜잭션 매니저
또는 트랜잭션 템플릿
등을 사용해서 트랜잭션 관련 코드를 직접 작성하는 것을 프로그래밍 방식의 트랜잭션 관리
라고 한다.
대부분 실무에서는 선언적 트랜잭션 관리
를 많이 사용
스프링 트랜잭션 AOP
는 이 어노테이션을 인식해서 트랜잭션 프록시
를 적용시켜줌
프록시
: 다른 객체에 대한 대리자 역할을 하는 디자인 패턴
프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 분리 가능하다
즉 서비스 계층에서는 순수한 비즈니스 로직만 남길 수 있음
스프링 부트가 등장하면서
DataSource
랑 트랜잭션 매니저
를 직접 빈으로 등록할 필요 없이 자동 등록이 가능해짐
기존에는 다음과 같이 스프링 빈
으로 직접 등록했었음
@Bean
DataSource dataSource() {
return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
}
@Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
하지만 요즘 스프링부트에서는 application.properties
에 있는 속성을 이용해서 DataSource
생성
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password