본문 바로가기

FastCampus - 한번에 끝내는 Java|Spring 웹 개발/04 JPA

JPA Ch 7 영속성 - 트랜젝션 - 패스트캠퍼스 챌린지 32일차

드디어... 며칠만에... 좀 편한 강의에 도달했다... 이번 강의 시리즈 자체는 좀 깁니다. 그런데 앞에 두 개만 봐서 양이 좀 적나보네요.

"트랜젝션 매니저" 강의 시리즈에서는 이전부터 설명은 않고 계속 써오던 @Transactional 과 트랜젝션, 그리고 그걸 관리하는 트랜젝션 매니저에 대해서 다룹니다. 강의 하나가 길어서 그냥 n조각된 거 같아보이네요. 그래서 두 개 봤습니다.

[ 그러면 안 데 캘리코 짤 (을 상상해주세요) ]

트랜젝션

  • 트랜젝션은 DB의 개념입니다. 아시다시피요.
  • @Transactional 이 있는 범위가 트랜젝션의 시작과 끝입니다. 중첩되면 어떻게 되는지도 이미 아시죠?
  • 트랜젝션은 트랜젝션 매니저가 관리해주는 모양입니다. 아직 강의에서 실체가 나오지는 않았습니다.

트랜젝션의 특징

ACID

오랜만이네요.

https://ko.wikipedia.org/wiki/ACID

  • 원자성 - atomicity: 모 아니면 도입니다. 전부 반영하던지 전부 반영하지 않던지.
  • 일관성 - consistency: 일관성에 해당하는 룰이 지켜졌는지. f.key 같은거나 A+B = 100원 같은 커스텀 룰... 커스텀 룰이 되나 잠깐 검색해봤는데 DB에서 제공하는 기능이 있나보네요.
  • 독립성 - isolation: 다른 트랜젝션이 끼어들어서 롤백이 불가능해진다던가 하는 일이 있으면 안 됩니다. A 트랜젝션을 돌리는 도중에는 잔액이 충분히 남아있었지만, A가 도는 사이에 B 트랜젝션이 통장 잔고를 음수로 만들었다: 이러면 롤백할 수가 없죠.
  • 지속성 - durability: 성공적인 트랜젝션은 영원히 반영. 로그 같은 걸로 시스템이 터지더라도 복구가 가능해야한다네요.

JPA의 트랜젝션은 @Transactional 로

예상하셨겠지만 지금까지 써 온 @Transactional 으로 관리합니다.

두 가지 @Transactional

근데 이게 또 두 가지가 있습니다.

  • Spring 네임스페이스의 @Transactional
  • JPA / Jakarta 네임스페이스의 @Transactional

스프링 쪽이 좀 더 기능이 많은 모양입니다. 뭘 쓰던 상관은 없지만 스프링걸 쓰면 다른 컨테이너랑 호환이 안 될 거라고... 아무렴머어때요

강의에서는 Spring 쪽의 어노테이션을 사용합니다.

DB로 직접 확인하는 @Transactional 의 효과
: all or nothing (atomicity)

이번 강의 두 개에서는 트랜젝션이 실제 DB에 반영되는지 보기 위해서 mysql 클라이언트로 접속해서 SQL을 입력해보는 걸로 확인을 진행합니다. 저야 그냥 IDE에서 새로고침해서 확인했구요...

현재 DDL 관련 설정이 create-drop 으로 설정되어 있기에 디버그 브레이크포인트를 잡아서 확인해야합니다. 마치 디버그를 처음 다루는 어린양같이 설명해주실 필요는 없었는데... 아무튼,

예상하신 대로 동작합니다. @Transactional 을 벗어나지 않으면 DB에 반영되지 않습니다. 심지어 flush() 를 한 뒤어도 @Transactional 을 벗어나지 못했다면 외부 도구로 DB에 직접 쿼리해서 확인해보면 반영되지 않았음을 알 수 있습니다. 1차 캐시가 아니라 DB상의 트랜젝션에 막힌 거죠.

@Service
@RequiredArgsConstructor
public class BookService {
    private final BookRepo bookRepo;
    private final AuthorRepo authorRepo;

    @Transactional
    public void putBookAndAuthor() throws Exception {
        Book book = new Book();
        book.setName("JPA 시ㅏㅈㄱ하기");

        bookRepo.save(book);

        Author author = new Author();
        author.setName("marting");
        authorRepo.save(author);

        System.out.println(bookRepo.findAll());
        bookRepo.flush();
        // flush 를 해도 반영되지 않습니다. DB상의 트랜젝션이 아직 안 끝났기 때문입니다.
    } // 함수가 끝나야 반영되겠죠.
}
@SpringBootTest
class BookServiceTest {
    생략
    @Test
    void transactionTest() {
        try {
            bookService.putBookAndAuthor();
        } catch (Exception e) {
            System.out.println(e);
        }

        System.out.println("books: " + bookRepo.findAll());
        System.out.println("autho: " + authorRepo.findAll());
    }
}

예외: checked exception 은 트랜젝션이 반영되니 주의!

현업에서 실수하는 케이스로 checked exception 을 발생시키는 경우를 보여주셨습니다. checked exception 이 발생되면, 개발자가 직접 예외를 처리하였을 것으로 가정하므로 자동으로 트랜젝션이 취소되고 롤백되지 않습니다.

반면, RuntimeException 을 상속받아서 메서드에 throws 를 표기할 필요가 없는 예외는 알아서 롤백이 됩니다.

@Transactional
public void putBookAndAuthorException() throws Exception {
    Book book = new Book();
    book.setName("JPA 시ㅏㅈㄱ하기");

    bookRepo.save(book);

    bookRepo.flush();

    // checked exception - 롤백이 안 됩니다.
    throw new Exception("asdf");
}

@Transactional
public void putBookAndAuthorRuntimeException() {
    Book book = new Book();
    book.setName("JPA 시ㅏㅈㄱ하기");

    bookRepo.save(book);

    bookRepo.flush();

    // unchecked exception - 트랜젝션이 취소되고 이전 상태로 롤백됩니다.
    throw new RuntimeException("asdf");
}

Checked Exception? Uncheced Exception?

어... 그래서 checked exception 이라고 하셨나요? 저는 처음 들어보는데요? 제가 학교에서 자바를 (검열됨)으로 배웠나보네요.

공식은 뭐라고 말할까요?

https://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html#jls-11.1.1

아래는 말로 설명된 상속구조를 트리로 표현한 것입니다.

  • Throwable : 모든 throw 가능한 것의 조상
    • Error: 보통 복구가 불가능 (unchecked)
    • Exception: 보통 프로그램들이 복구하길 원할 거 같은 오류들
      • RuntimeException: 여러가지 이유로 발생할 수는 있지만 아직은 복구가 가능한 것들 (unchecked)
      • 그 외 Exception 을 상속받는 것들

unchecked 가 아닌 예외 클래스는 모두 checked 이며, checked의 목적은 컴파일 타임 체킹, 즉 컴파일 시점에 오류가 발생할 여지가 있는지 확인하기 위해서입니다. 또한 API contract의 일부가 되기도 하죠.

컴파일 타임 체킹에 관해서:
https://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html#jls-11.2

그럼 컴파일 타임에 확인을 해주는 checked exception 이 더 좋은 거 아닐까요? 왜 runtime exception 만 롤백을 해줄까요? checked 이면서 롤백을 해주는 방법은 없을까요? 설정은?

@Transactional은 "Declarative transaction management" 다

https://stackoverflow.com/a/44899098

https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s05.html

설정가능하지 않을까? 해서 찾다보게 되면 금방 스프링 문서에 도달하게 됩니다.

메서드에 @Transactional 을 정의하는 거 자체가 선언적 정의입니다. 그리고 문서를 보니... 이거 AOP 로 중간에 메소드 콜을 낚아채는 방식으로 되어있군요.

위 링크 스프링 문서의 그림과 실제 테스트코드에서 직접 호출한 함수가 직접 호출되지 않았음을 확인할 수 있는 콜 스택

S/O를 보니 설정도 가능한 거 같은데... 뭐 필요하다면 다음 강의 어딘가에서 나오겠죠?

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스: https://bit.ly/37BpXiC

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는JavaSpring웹개발마스터초격차패키지Online

아 학교에서 자바 (검열됨)으로 배워서 30분짜리 학습이 1시간 반까지 늘었자나

어째 매번 꿀빨자는 목표가 허공으로 사라지고 저는 열심히 검색을 해보고 있는걸까요...