트랜잭션 & 데이터베이스 락
트랜잭션
하나의 논리적인 작업 단위
- 예시)
- 계좌 이체는 하나의 트랜잭션이라고 할 수 있다.
왜냐하면 A의 계좌에서 2,000원을 빼고 B의 계좌에 2,000원을 더하는 두 개의 과정을 하나의 작업처럼 처리하기 때문이다.
ACID
트랜잭션 처리에 필요한 네 가지 기본 속성. 트랜잭션은 이 네 가지 속성을 지켜야 한다.
Atomocity (원자성)
트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 모두 실패
해야 한다.
Consistency (일관성)
트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다. 예를 들어 데이터베이스에서 정한 제약 조건을 항상 만족해야 한다.
Isolation (고립성)
여러 트랜잭션이 동시에 실행될 때 각 트랜잭션이 다른 트랜잭션에게 영향을 미치지 않도록 해야한다.
이 때 격리 수준
이라는 것을 설정할 수 있는데 이것은 성능과 관련되어 있다.
격리 수준
- READ UNCOMMITED : 커밋되지 않은 데이터도 읽을 수 있다. 성능은 우수하지만 멀티쓰레드 환경에는 적합하지 않다.
- READ COMMITED : 커밋된 데이터만 읽도록 한다. 일반적으로 많이 사용하는 격리 수준이다.
- REPEATABLE READ : 반복 가능한 읽기
- SERIALIZABLE : 직렬화 가능
Durability (지속성)
트랜잭션이 성공적으로 끝나면 결과가 영구적으로 저장되어야 하고 실패하더라도 데이터는 손실되지 않아야 한다.
하나의 트랜잭션을 데이터베이스에 반영할 때는 commit;
명령을 실행하고 트랜잭션이 실패하여 데이터를 원래 상태로 되돌릴 때는 rollback;
명령을 실행한다.
세션
클라이언트가 데이터베이스에 접근하여 커넥션을 획득하면 데이터베이스에는 커넥션과 연결된 세션이 생성된다.
클라이언트가 커넥션을 이용해 데이터베이스에 요청을 보내면 커넥션과 연결된 세션이 이를 처리한다.
클라이언트가 커넥션을 끊으면 세션도 종료된다.
하나의 세션에서 실행중인 트랜잭션은 커밋하기 전까지 다른 세션에게 영향을 주지 않는다.
데이터베이스 락
하나의 세션이 트랜잭션을 시작하고 커밋 혹은 롤백을 하기 전에는 다른 세션에서 해당 데이터를 수정할 수 없도록 막는 것
일반적인 데이터베이스는 특정 세션에 의해 데이터를 수정할 수 없는 문제를 방지하기 위해 autocommit 을 사용하고 있으며 트랜잭션을 시작한다는 것은 autocommit 을 사용하지 않는다는 것과 같다.
만약 특정 세션이 락이 적용된 데이터를 변경하려고 한다면 락 타임아웃에 설정한 시간만큼 대기하고 시간이 지나면 예외가 발생한다.
일반적으로 조회 작업은 락이 걸려있는 데이터라도 조회가 가능하다.
하지만 조회 작업도 락을 적용하여 다른 세션이 데이터를 수정할 수 없도록 할 수 있는데 이 때는 select … for update
구문을 사용한다.
조회 작업 시 락은 보통 데이터를 조회 후 중요한 연산을 실행하는 동안 데이터가 변경되면 안되는 경우에 사용한다.
트랜잭션 매니저 - PlatformTransactionManager
트랜잭션 매니저는 트랜잭션 추상화를 위해 탄생하였다.
쉽게 말해서, jdbc의 트랜잭션 방법과 jpa의 트랜잭션 방법을 동일한 하나의 방법으로 사용할 수 있도록 한 것이다.(= 사용하는 데이터 접근 기술에 관계없이 트랜잭션을 시작하고 종료할 수 있다.)
트랜잭션 매니저는 내부적으로 DataSource를 사용하기 때문에 생성자에 DataSource 객체를 넘겨줘야 한다.
Service Layer에서 트랜잭션을 시작하면 트랜잭션 매니저는 커넥션을 생성하고 트랜잭션 동기화 매니저에 보관한다.
트랜잭션 동기화 매니저는 ThreadLocal
이라는 기술을 사용해서 멀티쓰레드환경에서 Service Layer와 Repository Layer가 안전하게 동일한 커넥션을 사용할 수 있도록 한다.
트랜잭션 동기화 매니저를 사용함으로써 Service Layer에서 Repository Layer로 커넥션을 넘겨주는 작업을 생략할 수 있다.
트랜잭션 템플릿 - TransactionTemplate
트랜잭션 템플릿은 템플릿 콜백 패턴
을 이용해 트랜잭션을 사용하는 비즈니스 로직에 반복되는 commit() / rollback() 코드를 줄여준다.
트랜잭션 템플릿 클래스는 인터페이스가 아니기때문에 트랜잭션 템플릿을 직접 주입받기보다는 생성자에서 트랜잭션 매니저를 이용해 주입해주는 것이 좋다.
비즈니스 로직에서 런타임 예외가 발생하면 자동으로 롤백을 수행한다.
트랜잭션 AOP - @Transactional
트랜잭션 AOP는 트랜잭션 템플릿을 이용해도 Service Layer에 순수한 비즈니스 로직을 남기지 못한 문제를 해결하기 위해 탄생했다.
트랜잭션 AOP를 사용하려면 단순히 Service의 메서드나 클래스 레벨에 @Transactional
어노테이션을 붙여주면 된다.
다만, 스프링 AOP 기능을 사용하기 때문에 테스트 시 SpringBootTest를 해야한다는 단점이 있다.
예외 추상화 - SQLExceptionTranslator
스프링은 데이터베이스마다 오류코드나 Exception 클래스가 다른 문제를 추상화하여 해결해준다.
쉽게 말해서, 같은 이유로 발생한 오류라고 할지라도 jdbc를 사용할 때 발생하는 Exception 클래스명과 jpa를 사용할 때 발생하는 Exception 클래스명이 다른 문제를 해결해준다.
sql-error-codes.xml
파일에는 데이터베이스마다 어떤 오류코드를 어떤 예외로 처리해야하는지 정의되어 있는데, SQLErrorCodeSQLException
구현체는 이 파일을 기반으로 다양한 데이터베이스의 오류코드를 보고 적절한 Exception 클래스로 변환해준다.