*Transaction
디비에서는 transaction으로 단위를 묶어서 쿼리를 실행한다. 물건을 구매하고 결제하는 것은 같은 단위(트랜잭션) 안에서 발생해야 한다. 주문은 했는데 돈을 결제가 안되면 문제가 생긴다. 따라서 하나의 트랜잭션 안에서 처리해야 한다. 트랜잭션이 성공적으로 커밋하면 한번에 2가지가 저장되고 실패해서 롤백되면 주문과 결제 모두 취소되어야 한다.
ACID
automicity 원자성(all or nothing)
트랜잭션 관련 작업들이 중간에 중단되지 않는 것을 보장한다. 돈의 송금을 예로 들어, 송금을 했으면 상대방은 돈을 받아야 한다. 돈을 송금하는 것은 성공했는데, 받는 것은 실패해서는 안된다.
consistency 일관성
트랜잭션이 성공적으로 완료되면 언제나 일관성 있는 상태를 유지하는 것이다. 무결성 제약이 모든 계좌는 잔고가 있어야 한다면, 이를 위반하는 트랜잭션은 종료된다. 돈을 보내려면 내 잔고가 0보다 커야한다. 성공을 하고나면 계좌는 0 또는 0보다 더 많아야 한다.
isolation 독립성
트랜잭션 수행 시, 다른 트랜잭션의 연산작업이 끼어들지 못하고 독립적으로 실행되는 것을 보장한다. 트랜잭션 실행내역은 연속적이어야 한다. 트랜잭션 밖에 있는 어떤 연산도 중간 단계의 데이터를 볼 수 없다. 성능관련 이유로 인해 가장 유연성 있는 제약조건이다. A가 B에 송금했을 때, A의 통장에서 돈이 차감되기 전에 B가 인출할 수 없다.
durablity 영구성
성공적인 트랜잭션은 영원히 반여되어야 한다. 송금이 정상적으로 완료되었다면 계속 그 변경된 금액이 유지되어야 한다.
*트랜잭션 설정하기
@Transactional
public void putBookAndAuthor() {
Book book = new Book();
book.setName("JPA");
bookRepository.save(book);
Author author = new Author();
author.setName("bert");
authorRepository.save(author);
}
@Transactional을 설정하면 트랜잭션이 설정된다.
각각의 save에 반응하지 않고 전체 @Transactional의 단위가 끝나고 나서 처리한다.
원자성 원리로 부분적 성공을 허용하지 않고 전체가 하나의 단위로 성공 혹은 실패로 인식한다.
@Transactional
public void putBookAndAuthor() {
Book book = new Book();
book.setName("JPA");
bookRepository.save(book);
Author author = new Author();
author.setName("bert");
authorRepository.save(author);
throw new RuntimeException("오류가 나서 commit이 발생하지 않습니다.");
}
만약에 맨 마지막에 예외가 발생하여 @Transactional이 정상적으로 종료되지 않는다면 이전까지 save()했던 내용들이 모두 rollback 된다.
public void putBookAndAuthor() {
Book book = new Book();
book.setName("JPA");
bookRepository.save(book);
Author author = new Author();
author.setName("bert");
authorRepository.save(author);
throw new RuntimeException("오류가 나서 commit이 발생하지 않습니다.");
}
@Transactional이 없다면 마지막에 예외가 발생해도 이전까지 save()는 정상적으로 저장이 된다.
*잘못된 트랜잭션 사용
1. Checked Exception
RuntimeException의 상위 버전으로, 명시적으로 exception 처리를 해주어야 한다. RuntimeExcpetion이 아닌 Exception을 사용한다면 메서드에 throws Exception을 추가해야 한다.
@Transactional
public void putBookAndAuthor() throws Exception {
Book book = new Book();
book.setName("JPA");
bookRepository.save(book);
Author author = new Author();
author.setName("bert");
authorRepository.save(author);
throw new Exception("오류가 나서 commit이 발생하지 않습니다.");
}
정상적으로 save()가 된다. 왜냐하면 Checked Exception은 "rollback을 하지 않는다." 개발자가 예외처리의 책임을 가진다. 이 부분을 실수를 많이 하기 때문에 Chekced Exception이 발생해도 이전에 save()같은 @Transactional이 발생했는지 꼭 확인하고 코드를 짜는것이 중요하다.
* 롤백처리하는 것은 코드로 어떻게 구현되어 있을까?
2. Unchecked Exception
TransactionAspectSupport 추상클래스의 다음 메서드를 보면 나와있다.
protected Object invokeWithinTransaction(
Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable
Unchecked Exception은 자동으로 "롤백처리가 된다"
RuntimeException 혹은 Error인 경우, rollback을 실행하므로 모든 내용이 취소된다.
*하지만 Checked Exception이라면?
rollBackOn이 fasle이기 때문에 else 문에 오고, 그냥 rollback을 하지 않고 commit을 한다.
*Checked Excception에서도 롤백처리를 하고 싶다면?
@Transactional(rollbackFor = Exception.class)
* 주의
public void put() {
this.putBookAndAuthor();
}
@Transactional
public void putBookAndAuthor() throws Exception {
Book book = new Book();
book.setName("JPA");
bookRepository.save(book);
Author author = new Author();
author.setName("bert");
authorRepository.save(author);
throw new Exception("오류가 나서 commit이 발생하지 않습니다.");
}
내부에서 메서드를 호출하는 경우, @Transactional이 무시된다. 빈으로 진입할 때, put()을 실행하면 그 메서드에 있는 @Transactional 메서드가 무효화된다. 빈 내부에서 호출이 될 때 AOP가 실행되는 시점에 @Transactional이 실행되기 때문이다.
'학습 > DB' 카테고리의 다른 글
@Query 활용하기 1 (0) | 2021.09.26 |
---|---|
Transaction 격리수준 (0) | 2021.09.26 |
JPA 값 타입 (0) | 2021.09.18 |
객체지향 쿼리 언어 (0) | 2021.09.17 |
Oracle DML GRANT, REVOKE 사용하기 (0) | 2021.09.01 |