최근에 열심히 이직 준비를 하고 있는 오늘의 키메라!
필자는 이와 같은 질문을 받았다
면접관 : JPA를 사용해 보셨다고요? JPA가 뭐죠?
키메라 : 네! JPA는 Java Persistent API로 ORM을 위해 사용되는 인터페이스입니다.
면접관 : 그러면 JPA에는 왜 save 시에 @Transactional을 붙여야 할까요? 안되다가 마법같이 데이터 조작 작업이 되더라고요.
키메라 : 아... 그건 잘... 모르겠는데요?
면접관 : ???
키메라 : ???
이에 대한 대답을 찾기 위해 글을 정리했다.
막상 쭈욱 다시 정리하니 별거 없는데 참... ㅠㅠ
1. 트랜잭션에 대해서
트랜잭션이란 데이터베이스의 상태를 변화시키기 위해서 수행되는 작업의 단위를 뜻한다.
트랜잭션의 특징은 원자성, 일관성, 독립성, 지속성을 가진다.
원자성
트랜잭션이 데이터베이스에 모두 반영되거나, 전혀 반영되지 않아야 한다.
일관성
트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다.
독립성
둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이라도 다른 트랜잭션의 연산에 끼어들 수 없다.
지속성
트랜잭션이 성공적으로 완료됬을 경우 결과는 영구적으로 반영되어야 한다.
2. 그러면 @Transactional은 무엇인가?
Spring Framework 2.0 이상의 버전에서 지원하는 선언적 데이터베이스 트랜잭션 관리를 위한 Annotation이다.
Spring 에서는 AOP 프레임워크를 이용하여 프록시를 생성하고, 특정 메소드 호출을 가로채서 추가 동작을 수행하는데, Dynamic Proxy이나 CGLIB를 통해서 Proxy를 생성이 가능한다.
고로, @Transactional 어노테이션을 사용하면 원하는 method에 원자성, 일관성, 독립성, 지속성을 지킨 트랜잭션을 적용할 수 있다는 말이다.
3. JPA의 Entity Lifecycle
영속성이란 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 말한다.
JPA에는 영속성 컨텍스트라는 것이 존재한다.
Entity를 영구 저장하는 환경이라는 뜻으로, 눈에 보이지 않는 논리적 개념이다.
여기서의 Entity 는 도메인의 Entity와 약간 다른데, 여기서의 Entity는 물론 JPA가 관리하는 객체를 Entity라고 한다.
JPA는 EntityManager를 통해 Entity를 영속성 컨텍스트에 보관하고 관리하게 된다.
새롭게 Entity를 만드는 경우에는 우리가 저장하고자 하는 Entity가 영속성 컨테이너로 관리가 되지 않는데 이 상태는 비영속 상태(Transient)이다.
Managed(영속) 상태에서는 Hibernate가 그 객체의 변경을 추적하고, 트랜잭션 내에서 변경 사항을 자동으로 SQL로 바꿔준다. 이것을 Dirty checking이라고 한다.
Dirty Checking 이 되는 원리는? 더보기 클릭
JPA에서는 트랜잭션이 끝나는 시점에 변화가 있는 모든 엔티티 객체를 데이터베이스에 자동으로 반영한다.
이때 변화가 있다의 기준은 최초 조회 상태로
JPA에서는 엔티티를 조회하면 해당 엔티티의 조회 상태 그대로 스냅샷을 만들어놓는다.
그리고 트랜잭션이 끝나는 시점에는 이 스냅샷과 비교해서 다른점이 있다면 Update Query를 데이터베이스로 전달한다.
당연히 이런 상태 변경 검사의 대상은 영속성 컨텍스트가 관리하는 엔티티에만 적용된다.
Detached(준영속상태)는 Managed(영속상태)되던 것이 Transaction 이 닫힌(closed) 후 더이상 관리되지 않는다는것을 의미한다. 혹은 detach 메소드를 호출해서도 가능하다. 준영속 상태가 되면 이제 dirty checking이 되지 않는다.
flush는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것이다.
Transaction commit 이 일어날 때 flush가 일어난다.
remove메소드도 있는데 이걸 사용하면 트랜잭션 끝에 delete문이 나간다.
EntityManager는 Transaction단위로 생성되며 일반적으로 transaction이 끝나면 종료된다.
4. 영속성 컨텍스트의 장점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
4.1 1차 캐시?
EntityManager에서 Entity를 persist하면 엔티티가 영속성 컨텍스트에 저장이 된다.
이 Entity는 1차 캐시에 자장이 된다.
1차 캐시는 key-value형식이며 EntityManager내에 내가 조회하고자 하는 Entity가 있으면 이를 DB를 조회하지 않고 1차 캐시에 있는 Entity를 찾아주니 성능성의 이점이 있다. 근데 글쎄... 같은 Entity를 조회할 일이 얼마나 있을까 싶긴 한다. 한 번 조회하면 다시 조회할 일이 있나?
4.2. 동일성 보장?
말 그대로 영속성 컨텍스트 내의 엔티티를 조회했는데 그게 같다면 어떻게 될까?
동일한 transaction내에서 동일한 엔티티가 다시 로드되면 1차 캐시에서 반환이 된다.
1차 캐시는 이렇게 transaction기간 동안 데이터의 일관성을 유지하게 된다. 같은 엔티티를 조회하게 되면?
그 엔티티는 동일성을 보장하게 된다.
여기서 동등성이랑 헷갈리면 안되는데 동승성이란 인스턴스는 다를 수 있지만 인스턴스가 가지고 있는 값이 같은 경우이다.
4.3 트랜잭션을 지원하는 쓰기 지연?
쓰기 지연이란 말 드대로 쓰기를 지연하는것이다. 어디에 뭘 쓰는데 늦게 쓰나? 지각하면 혼나는데! (선생님한테 혼나!)
우리가 Entity들을 영속성 컨텍스트에 올린다고 해서 이게 DB에 바로 저장이 되는건 아니다.
commit이 되면 영속성 컨텍스트에서 관리중인 데이터 관련 SQL들을 그제서야 처리한다.
이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.
트랜잭션을 commit 하면 엔티티 매니저는 우선 영속성 컨텍스트를 플러시한다. 플러시는 영속성 컨텍스트의 변경 내용을 동기화 하는 작업이다.
뭔가 물을 내려서 한 번에 처리한다는 말이 잘 어울리지 않나?
4.4 변경 감지 (Dirty Checking)
엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능이다.
JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 스냅샷에 저장해두는데,
이 스냅샷과 비교해서 다르면 쓰기 수정 쿼리를 지연 SQL 저장소에 보내고
나중에 flush 시점에 update문이 날아갈 것이다!
더티체킹은 영속성으로 관리되는 엔티티만 적용된다. 어찌 보면 당연한게, 관리를 해야 스냅샷을 가지고 있지 않나?
4.5 지연 로딩 (Lazy Loading)
필요한 시점에 연관된 객체의 데이터를 불러오는 것으로, 굳이 당장 사용하지 않는 데이터는 나중에 불러오기 때문에 성능상의 이점이 있다.
장점은 그렇다니는데... 뭐 사실 쓰다보면 더티체킹이고 뭐고 다 싫어!
5. 그래서 이게 뭐?
여기서 중요한 점은 영속성 관리를 하려면 트랜잭션 안에서만 제대로 작동을 한다는 사실이다!
왜냐하면 엔티티 매니저가 관리하는 엔티티의 모든 변경은 트랜잭션 안에서 이뤄져야 하기 때문이다.
그게 어디있냐고 묻는다면... 공식문서에서 다음 부분이 있다. 궁금하면 더보기
By default, a container-managed persistence context is of type SynchronizationType.SYNCHRONIZED.
Such a persistence context is automatically joined to the current JTA transaction, and
updates made to the persistence context are propagated to the underlying resource manager.
- JSR 338: Java Persistence 2.2 Specification중 7.6.1 확인 -
해당 내용을 정리하면...
- 컨테이너가 관리하는 영속성 컨텍스트는 기본적으로 SYNCHRONIZED 타입이고,
- 현재 JTA 트랜잭션에 자동으로 참여(join) 된다.
- 그리고 이 영속성 컨텍스트에서 이루어진 변경(update)은 자동으로 DB에 반영된다.
즉, 트랜잭션이 있어야 변경 사항이 DB로 반영된다는 말이다.
EntityManager는 Transaction단위로 생성되며 일반적으로 transaction이 끝나면 종료된다.
6. SimpleJpaRepository
그래서 @Transactional을 통해서 트랜잭션 관리를 통해 save작업을 할 수 있다는 것을 알았다.
그런데 사실 SimpleJpaRepository라는 것을 Spring Data JPA에서 제공을 한다.
SimpleJpaRepository는 JpaRepository 인터페이스의 기본 구현 클래스로 save에는 @Transactional이 선언이 되어있다.
필자가 여기서 헷갈렸던 것이 회사에서는 기본 JpaRepostory구현체를 SimpleJpaRepository를 사용하지 않고 오버라이드 해버렸기 때문에 save위에 @Transactional을 직접 계속 붙여서 사용해야 했다.
근데 생각해보니 나도 사실 Service단에 전부 트랜잭션을 관리하고 있고, Service에서 이를 관리하는게 어느정도 관행르고 굳어져 있긴 하다고 생각한다.
7. 결론
그럼 나는 뭐라고 답변을 해야 했을까?
다시 글을 보고 공부해보니 이게 완전 잘못된 답변이라는것을 깨달았다.
이전의 답변이 궁금하면 다음을 보면 된다.
면접관 : JPA를 사용해 보셨다고요? JPA가 뭐죠?
키메라 : 네! JPA는 Java Persistent API로 ORM을 위해 사용되는 인터페이스입니다.
면접관 : 그러면 JPA에는 왜 save 시에 @Transactional을 붙여야 할까요? 안되다가 마법같이 데이터 조작 작업이 되더라고요.
키메라 : 아, 그건 SimpleJpaRepository를 사용하지 않아서 그런것 같습니다. 기본적으로 변경사항이 실제로 DB에 반영되기 위해서는 영속성 컨텍스트가 트랜잭션에 참여해야 합니다. SimpleJpaRepository는 Spring Data JPA에서 제공하는 JpaRepository의 기본 구현 클래스로 save위에는 @Transactional이 이미 선언이 되어 있습니다. 하지만 custom 한 JpaRepostiory의 구현 클래스를 사용한다면 save에 @Transactional이 빠져 있다면 에러가 날 수 도 있습니다. 주신 질문은 이러한 상황을 말씀하신거 같습니다.
면접관 : 흐음... 에매한데...
키메라 : 네!? ㅠㅠ
....
.....
면접관 : JPA를 사용해 보셨다고요? JPA가 뭐죠?
키메라 : 네! JPA는 Java Persistent API로 ORM을 위해 사용되는 인터페이스입니다. 하이버네이트 구현체가 가장 유명하며 표준으로 사용되고 있습니다.
면접관 : 그러면 JPA에는 왜 save 시에 @Transactional을 붙여야 할까요? 안되다가 마법같이 데이터 조작 작업이 되더라고요.
키메라 : 그건 기본적으로 하나의 트랜잭션 내에서만 영속성 컨텍스트가 관리되기 때문입니다. 그리고 save를 하면 바로 SQL에 적용되는 것이 아닌 쓰기 지연으로 commit이 되는 순간 flush가 발생하고 실제 SQL을 실행하게 됩니다.
기본적으로 SimpleJpaRepository는 Spring Data JPA에서 제공하는 JpaRepository의 기본 구현 클래스로 save위에는 @Transactional이 이미 선언이 되어 있습니다. 하지만 custom 한 JpaRepostiory의 구현 클래스를 사용한다면 save에 @Transactional이 빠져 있다면 에러가 날 수 도 있습니다.
굳이 JpaRepository를 사용하지 않더라도 대부분 service layer에서 트랜잭션을 관리하므로 service 단의 save메소드에 @Transactional를 하면 좋습니다.
면접관 : 합겨억!
키메라 : 네! 감사합니다!
....
.....
결론적으로는 면접에서 떨어졋지만... 어떻게 보면 깊게 알아봐야 할 내용들이 많았는데
이전에 공부를 했었어도 까먹기도 하고, 이러한 내용도 놓치니 사알~짝 부끄럽기도 하다.
정말 별거 없는데... 참 ㅠㅠ
참고글
도서 : 자바 ORM 표준 JPA 프로그래밍 Chapter 3
Spring Data JPA: The Save Method and Its Scope of Applicability
SpringBoot JPA need no .save() on @Transactional? [duplicate]
Spring Data JPA ( JpaRepository와 SimpleJpaRepository 관계 )
https://bnzn2426.tistory.com/145
https://colevelup.tistory.com/22
'Spring > JPA' 카테고리의 다른 글
@Entity 설정 Annotation 정리 (0) | 2022.03.17 |
---|---|
DB 개념 + JPA concurrency control (2) | 2022.03.14 |