JPA를 공부할 맘이 생기면 보통 '자바 ORM 표준 JPA 프로그래밍'이 가장 유명하고 이를 구매하게 됩니다. 저도 자연스레 책을 샀지만, 매번 프로젝트할 때마다 책 내용을 다시 정리하고 에러를 보면서 이런 사례가 있었지 하곤 합니다. 그래서 진짜 마지막이란 마음으로 정리하고 다시 볼 필요가 있으면 시리즈 정독을 하면 좋겠다 싶어서 만드는 글입니다.
JPA란 무엇인가?
- Java 진영의 ORM 기술 표준
- 하이버네이트 기반의 자바 ORM로 기존의 엔티티 빈이라는 ORM 기술보다 가볍고 실용적
- 어플리케이션과 JDBC 사이에서 동작함
ORM이란?
데이터베이스와 객체 사이의 패러다임 불일치 문제를 개발자 대신 해결해주는 프레임워크
패러다임에 대한 고민을 빼고 각각의 모델링에 집중할 수 있게 도와줌
- 데이터 접근 추상화와 벤더 독립성 제공
- 데이터베이스별 방언 클래스를 제공하여, 특정 데이터베이스에 의존적인 구문은 방언을 통해 처리함
ORM에 대한 궁금증과 오해
- SQL이나 데이터베이스에 대해서 잘 모르고 써도 괜찮은가?
- 아니요. 데이터베이스를 객체지향적으로 개발하는 것을 도와주는 도구로 데이터는 관계형으로 저장됨. 그래서 양측의 매핑이 올바르게 이뤄질 수 있도록 양측 모두에 대한 이해가 요구됨.
- 성능이 느린가요?
- 다양한 성능 최적화 기능을 제공하고 있으며, 잘 이해하고 사용하면 SQL을 직접 사용하는 것보다 좋을 수 있음. 그리고 SQL을 직접 작성하는 기능도 지원함
- 복잡한 쿼리는 어떻게 처리하나요?
- JPA의 경우엔 복잡한 쿼리보단 실시간 처리용 쿼리에 최적화 된 경향이 있음. 그래서 너무 복잡한 경우에는 네이티브 SQL을 사용하거나 SQL 매퍼 프레임워크를 혼용하는 것도 좋은 방법임.
영속성 관리
엔티티 매니저
- 엔티티와 관련된 작업을 진행하는 관리자 객체
- 엔티티 매니저 팩토리를 생성하는 비용은 크나, 매니저 생성 비용이 적으므로 1개의 팩토리를 두고 매니저를 생성하여 사용
- 엔티티 매니저는 여러 스레드에서 동시에 접근하면 동시성 문제가 생기므로 절대 공유되선 안됨
영속성 컨텍스트
- 엔티티를 보관하고 관리하는 환경
- 엔티티 매니저를 생성할 때, 하나 만들어짐
엔티티의 생명주기
- 비영속 : 영속성 컨텍스트와 관계 없는 상태
- 영속성 컨텍스트나 데이터베이스와 관계 없는 상태
- 영속 : 영속성 컨텍스트에 저장된 상태
- 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 영속 상태의 엔티티를 더 이상 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 됨
- 삭제 : 영속성 컨텍스트에서 삭제된 상태
영속성 컨텍스트의 특징
- 영속성 컨텍스트는 엔티티 식별자 값( @Id로 매핑된 필드 )으로 구분
- 영속성 컨텍스트 내에서의 변화는 flush를 통해 데이터베이스에 반영됨
- 영속성 컨텍스트를 통해 아래의 이점을 얻을 수 있음
- 1차 캐시
- 영속성 컨텍스트 내에 1차 캐싱을 하고 있으며, 엔티티 조회 시 먼저 1차 캐싱에서 찾음
- 1차 캐싱에 없는 경우엔 데이터베이스에서 조회해서 1차 캐싱에 저장함
- 동일성 보장
- 동일한 식별자의 엔티티는 1차 캐싱에서 반환하므로, 동일성을 보장함
- 트랜잭션을 지원하는 쓰기 지연
- 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 저장하지 않고, 내부 쿼리 저장소에 저장해두었다가 커밋 시 데이터베이스로 쿼리를 보냄
- 데이터베이스 동기화를 진행한 후 트랜잭션을 커밋함
- 쿼리를 모아서 보내고 커밋을 진행하나, 매번 쿼리를 보낸 뒤 트랜잭션을 커밋하나 결과가 동일하기 때문에 해당 기능이 지원 가능
- 변경 감지
- 수정 쿼리를 보낼 시, 같은 엔티티에 대해서 합쳐서 보낼 수 있음
- 영속성 컨텍스트는 최초 상태를 복사하여 저장하는데, 이를 스냅샷이라고 함
- flush가 호출되면 스냅샷과 현재의 엔티티를 비교하여 변경된 부분에 대해서만 쿼리를 생성함
- 이런 기능을 지원하지만 다음의 이유로 JPA는 전체 교체가 기본 전략임
- 모든 필드를 사용하면 수정 쿼리가 항상 동일하므로, 쿼리를 미리 생성해두고 재사용할 수 있음
- 데이터베이스에서 동일한 쿼리에 대해서 파싱된 쿼리를 캐싱하므로 재사용할 수 있음
- 필드가 너무 많거나 내용이 너무 크면 동적 쿼리 생성 전략을 선택하면 됨
- 수정되는 컬럼이 30개 이상 정도 되면 @DynamicUpdate 어노테이션이 빠르다고 함
- 근데 테이블에 컬럼이 30개 이상 정도 되면 잘못된 설계에 가까움
- 지연 로딩
영속성 컨텍스트는 제 생각에 JPA 작동에 있어서 가장 근간이 되는 부분이라고 생각합니다. 엔티티를 어떻게 영속 상태로 만들고, 컨텍스트 내에서 관리할지 고민하게 되는데 이 부분을 짚어가면서 정리하면 좋을 것 같습니다.
플러시(flush)
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것
- 플러시하는 방법은 3가지가 있음
- 직접 호출
- 트랜잭션 커밋 시 자동 호출
- 트랜잭션만 커밋되면 변경 사항이 저장되지 않으므로 자동 호출됨
- JPQL 쿼리 실행 시 자동 호출
- 쿼리 내에서 반영되지 않은 영속 상태의 엔티티 사용을 방지하기 위해 이전에 영속 상태로 전환한 엔티티에 대해 flush를 실행함
JPA가 데이터 계층에 관여되는 만큼 성능에 대한 고민이 빠질 수 없습니다. 그래서 기본적인 동작 방식과 그 흐름에 따른 사고가 뒷받침 되고 비교가 필요하다고 생각됩니다.