[JPA] JPA 기초 1
20 Nov 2022JPA 기초 1
JPA란?
- Java Persistence API
- 자바 진영의 ORM 기술 표준
- ORM : Object-relational mapping(객체 관계 매핑)
JPA를 왜 사용해야 하는가?
- 생산성
- CRUD가 다 정의되어 있어서 편리함
- 저장: jpa.persist(member)
- 조회: Member member = jpa.find(memberId)
- 수정: member.setName(“변경할 이름”)
- 삭제: jpa.remove(member)
- CRUD가 다 정의되어 있어서 편리함
- 패러다임의 불일치 해결
- 객체는 상속관계가 있지만, 관계형 데이터베이스는 상속 관계가 없다.
- 객체는 참조(reference)를 가지고 있다.
- 관계형 데이터베이스는 PK나 FK로 join을 해서 필요한 데이터를 찾을 수 있다.
JPA의 동작

- 저장

- JPA에게 Member객체를 넘기면 JPA가 객체를 분석
- INSERT query 생성 (JPA가 query 생성)
- JPA가 JDBC API를 사용해서 INSERT query를 DB에 보냄
- 패러다임의 불일치 해결
- 조회

- JPA에게 PK값으로 find 요청
- JPA는 SELECT query 생성
- JDBC API를 통해 DB에 보내고 결과를 받음
- ResultSet 매핑
- 패러다임 불일치 해결
JPA의 성능 최적화 기능
- 1차 캐시와 동일성(identity) 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
- DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
- 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
- JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
- 지연 로딩 (lazy loading)
- 지연 로딩: 객체가 실제 사용될 때 로딩
- 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회
엔티티 매니저 팩토리 & 엔티티 매니저

- 엔티티 매니저 팩토리
- 엔티티 매니저를 만드는 공장
- 엔티티 매니저 팩토리는 생성하는 비용이 커서 한 개만 만들어 애플리케이션 전체에서 공유
- 여러 스레드가 동시에 접근해도 안전
- 엔티티 매니저
- 엔티티의 CRUD 등 엔티티와 관련된 모든 일을 처리
- 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안됨
영속성 컨텍스트란?
- 엔티티를 영구 저장하는 환경
- ex) EntityManger.persist(member);
- 이 코드는 member 엔티티를 저장한다. 하지만 정확하게 얘기하면 데이터베이스에 저장하는게 아니라 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장하는 코드다. (트렌잭션이 commit하는 시점에 영속성 컨텍스트에 있는 엔티티들에 대한 쿼리가 실행된다.)
- 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나만 만들어진다. 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있다.
엔티티의 생명주기

- 비영속(new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 엔티티 객체가 생성된 순수 객체 상태
- 아직 영속성 컨텍스트나 데이터베이스와는 전혀 관계가 없는 상태

- 영속(managed)
- 영속성 컨텍스트에 관리되는 상태
- 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장하면, 영속성 컨텍스트가 엔티티를 관리하므로 영속 상태가 된다.

- 준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
- 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 됨
- 엔티티를 준영속 상태로 만드는 방법
- EntityManager.detach(entity);
- 엔티티를 준영속 상태로 만든다.
- 영속성 컨텍스트가 지원하는 어떤 기능도 동작하지 않는다.
- EntityManager.close();
- 영속성 컨텍스트를 닫는다.
- 해당 영속성 컨텍스트가 관리하던 영속 상태의 모든 엔티티가 준영속 상태가 된다.
- EntityManager.clear();
- 영속성 컨텍스트를 초기화한다.
- 초기화된 수정 사항은 변경 감지가 동작하지 않는다.
- EntityManager.detach(entity);
- 삭제(removed)
- 삭제된 상태
- 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
- ex) EntityManager.remove(entity);
영속성 컨텍스트의 특징
- 영속성 컨텍스트와 식별자 값
- 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
- 영속성 컨텍스트와 데이터베이스 저장
- JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다. 이를 플러시(flush)라고 한다.
-
영속성 컨텍스트가 엔티티를 관리하면 얻게되는 장점
-
1차 캐시
- 영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 한다. 영속 상태의 엔티티는 모두 이곳에 저장된다. 쉽게 말해 영속성 컨텍스트 내부에 Map이 하나 있는데, 키는 @Id로 매핑한 식별자고 값은 엔티티 인스턴스다.
- 엔티티를 조회하는 find 메서드 실행 순서
- 1차 캐시에서 식별자 값으로 엔티티를 찾는다.
- 만약 찾는 엔티티가 1차 캐시에 있으면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회
- 1차 캐시에 찾는 엔티티가 없으면 데이터베이스에서 조회
- 1차 캐시는 한 트랜잭션 안에서만 효과가 있기 때문에 성능상 이점이 크지는 않다.
-
동일성 보장
Member a = em.find(Member.class, "member1"); Member b = em.find(Member.class, "member1"); System.out.println(a == b);- 위 코드의 a == b 는 참이다.
- 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환하기 때문에 참이다. 따라서 영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.
-
트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다 transaction.begin(); // 트랜잭션 시작 em.persist(memberA); em.persist(memberB); // 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다. transaction.commit(); // 트랜잭션 커밋- 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내서 저장시킨다. 이를 ‘transactional write-behind’라 한다.
- 코드가 실행되는 과정
- em.persist(memberA)가 실행되면, 영속성 컨텍스트는 1차 캐시에 memberA에 대한 엔티티를 저장하면서 동시에 JPA가 이 entity를 분석해서 쓰기 지연 SQL 저장소에 INSERT 쿼리를 저장한다.
- em.persist(memberB)가 실행되면, memberA와 같은 작업이 실행된다.
- 트랜잭션을 commit하면, 엔티티 매니저는 영속성 컨텍스트를 플러시(flush) 한다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.
-
변경 감지(Dirty Checking)
EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); transaction.begin() // 트랜잭션 시작 // 영속 엔티티 조회 Member memberA = em.find(Member.class, "memberA"); // 영속 엔티티 데이터 수정 memberA.setUsername("hi"); memberA.setAge(10); transaction.commit(); // 트랜잭션 커밋- 변경 감지란 엔티티의 변경사항을 DB에 자동으로 반영하는 기능
- 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시(flush())가 호출된다.
- 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
- 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
- 데이터베이스 트랜잭션을 커밋한다.
- 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
- 비영속, 준영속처럼 영속성 컨텍스트의 관리를 받지 못하는 엔티티는 값을 변경해도 DB에 반영되지 않는다.
- JPA는 엔티티의 모든 필드를 업데이트 한다.
- 장점 - 모든 필드를 사용하면 수정 쿼리가 항상 같다. 따라서 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있으며, DB에 동일한 쿼리를 보내면 DB는 이전에 한 번 파싱된 쿼리를 재사용할 수 있다.
- 단점 - 데이터 전송량이 증가한다.
- 지연 로딩(Lazy Loading)
-
“[JPA] JPA 기초” 시리즈는 인프런 김영한 강사님의 강좌를 기반으로 쓰여졌습니다.
(참고 : https://www.inflearn.com/users/@yh)



