JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계 부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있습니다.
여기서는 매핑한 엔티티를 EntityManager를 통해 사용하는 법을 정리하려합니다.
여기서는 Hibernater를 기준으로 정리합니다.
구조
- 요청이 올 때마다 EntityManagerFactory를 통해 EntityManager를 생성합니다.
- EntityManager는 내부적으로 DB 커넥션을 통해 DB를 사용합니다.
Entity Manager Factory
- EntityManager를 생성합니다.
- 생성 비용이 크기 때문에 하나만 생성해 애플리케이션 전체에서 공유하여 사용합니다.
- 설정 정보를 읽어 JPA를 동작시키기 위한 기반 객체 생성 및 커넥션 풀 생성 등으로 생성 비용이 큽니다.
- J2SE 환경에서는 커넥션 풀을 생성하고, J2EE 환경에서는 스프링 컨테이너가 제공하는 데이터소스를 사용합니다.
- 데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 하나만 생성합니다.
- 여러 스레드가 동시에 접근해도 안전합니다.
- 설정 정보를 읽어 JPA를 동작시키기 위한 기반 객체 생성 및 커넥션 풀 생성 등으로 생성 비용이 큽니다.
Entity Manager
- 엔티티를 저장, 수정, 삭제, 조회하는 등 엔티티와 관련된 모든 일을 처리합니다.
- EntityManagerFactory를 통해서 생성합니다.
- 생성 비용이 거의 들지 않습니다.
- 여러 스레드가 동시에 접근하면 동시성 문제가 발생하기 때문에 스레드 간에 절대 공유하면 안됩니다.
- 데이터베이스 연결이 꼭 필요한 시점에 데이터베이스 커넥션을 얻습니다.
- 보통 트랜잭션을 시작할 때 커넥션을 획득합니다.
// EntityManagerFactory 생성
// 파라미터로 persistence.xml 파일의 persistence-unit name 을 넣어줍니다.
EntityManagerFactory emf = Persistence.createEntityMangerFactory("persistence-unit name");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
Persistence Context
- 영속성 컨텍스트라는 의미로 엔티티를 영구 저장하는 논리적인 환경을 뜻합니다.
- EntityManager를 통해 Persistence Context에 접근하고 관리합니다.
- EntityManager로 Entity를 저장하거나 조회하면 EntityManager는 Persistence Context에 Entity를 보관하고 관리합니다.
- EntityManager를 생성할 때마다 하나씩 만들어집니다.
- 엔티티 매니저 안에 논리적인 저장소가 하나 있다고 생각하면 됩니다.
- 여러 EntityManager가 같은 Persistence Context에 접근할 수도 있습니다.
Entity LifeCycle
Entity는 4가지 상태를 갖습니다.
- 비영속 (new / transient)
- Persistence Context와 전혀 관계가 없는 상태
// 객체를 막 생성한 상태 Member member = new Member();
- 영속 (managed)
- Persistence Context에 저장된 상태
- persist()를 통해서 엔티티를 Persistence Context가 관리하도록 합니다.
// Persistence Context가 객체를 관리 em.persist(member);
- 준영속 (detached)
- Persistence Context에 저장되었다가 분리된 상태
- Persistence Context가 엔티티를 관리하지 않습니다.
- 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않습니다.
- 이미 한번 영속상태였기 때문에 식별자 값을 가지고 있습니다.
- 영속성 컨텍스트가 관리하지 않으므로 지연로딩도 할 수 없습니다.
- detach() , clear() , close() 로 엔티티를 준영속 상태로 만들 수 있습니다.
- merge() 를 통해 준영속 상태의 엔티티를 다시 영속 상태로 변경할 수 있습니다.
// 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태 em.detach(member); // 준영속 상태 -> 영속 상태 em.merge(member); // 영속성 컨텍스트를 초기화하거나 em.clear(); // 영속성 컨텍스트를 닫으면 영속성 컨텍스트가 관리하던 엔티티는 준영속 상태가 됩니다. em.close();
- 삭제 (removed)
- 삭제된 상태
// 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제 em.remove(member);
Persistence Context 특징
- 엔티티를 식별자 값으로 구분합니다.
- 영속 상태는 반드시 식별자 값이 있어야 합니다.
- 트랜잭션을 커밋할 때 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영합니다.
Persistence Context 이점
- 1차 캐시
- 영속성 컨텍스트는 내부에 캐시를 가지고 있습니다.
- 영속 상태의 엔티티는 모두 이곳에 저장됩니다.
- 1차 캐시의 키는 식별자 값이고 식별자 값은 데이터베이스 기본 키와 매핑되어있습니다.
- 엔티티 조회 시, 먼저 1차 캐시에서 찾아 반환합니다.
- 없으면 데이터베이스에서 조회하여 엔티티를 생성하고 1차 캐시에 저장한 후 영속 상태의 엔티티를 반환합니다.
- 성능상 이점은 크게 없다고 합니다.
- EntityManager를 트랜잭션 단위로 만들고 트랜잭션이 종료되면 삭제되는데, 1차 캐시도 EntityManager와 생명주기를 같이 합니다.
- 많은 요청이 들어오더라도 요청들은 각자의 트랜잭션을 가지면서 처리되기 때문에 1차 캐시로 데이터를 재사용하는 것은 트랜잭션 별로 찰나의 순간이라 크게 성능상 이점은 없다고 볼 수 있습니다.
// 먼저 1차 캐시에서 엔티티를 찾습니다.
// 없으면 데이터베이스에서 조회하여 엔티티를 생성하고 1차캐시에 저장한 후 영속 상태의 엔티티를 반환합니다.
Member member = em.find(Member.class,"member1");
- 동일성 보장
- 동일한 식별자로 조회하면 1차 캐시에 있는 같은 엔티티 인스턴스를 반환합니다.
- 동일성 : 참조 값 비교, 실제 인스턴스가 동일
- 동등성 : 값 비교, 실제 인스턴스는 다를 수도
- 동일한 식별자로 조회하면 1차 캐시에 있는 같은 엔티티 인스턴스를 반환합니다.
Member a = em.find(Member.class,"member1");
Member b = em.find(Memeber.class,"member1");
System.out.println(a == b) // true
- 트랜잭션을 지원하는 쓰기 지연
- 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 SQL을 모아두었다가 커밋할 때 모아둔 쿼리를 데이터베이스로 보냅니다.
- 트랜잭션을 커밋하면 엔티티 매니저는 우선 영속성 컨텍스트를 flush 합니다.
- flush 란 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업을 뜻합니다.
- 그 후, 실제 데이터베이스 트랜잭션을 커밋하며 데이터베이스에 반영됩니다.
- 트랜잭션을 커밋하면 엔티티 매니저는 우선 영속성 컨텍스트를 flush 합니다.
- 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 SQL을 모아두었다가 커밋할 때 모아둔 쿼리를 데이터베이스로 보냅니다.
// 엔티티 매니저로 트랜잭션을 생성합니다.
EntityTransaction transaction = em.getTransaction();
// 데이터 변경을 트랜잭션 내에서 처리되어야하기 때문에 트랜잭션을 시작합니다.
transaction.begin();
// 1차 캐시에 저장만 해두고 데이터베이스로 보낼 SQL은 따로 보관합니다.
em.persist(memberA);
em.persist(memberB);
// 트랜잭션을 커밋합니다. 데이터베이스로 SQL이 전송되며 반영됩니다.
transaction.commint();
Tip. 후에 다루겠지만 hibernate 설정에 insert/update 쿼리를 원하는 개수만큼 모아서 DB에 날릴 수 있는 옵션이 있습니다.
스프링 부트 application.yml
spring: jpa: properties: hibernate: jdbc: batch_size: 100
- 변경 감지(Dirty Checking)
- 엔티티 수정 시,자바 컬렉션 다루듯 단순히 엔티티를 조회해서 데이터만 변경하면 자동으로 데이터베이스에 반영됩니다.
- JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이를 스냅샷이라 합니다.
- flush 시점에 스냅샷과 엔티티를 비교하여 변경된 엔티티를 찾고 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보냅니다.
- 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됩니다.
- 수정 시, 엔티티의 모든 필드를 업데이트 합니다.
- 하나의 필드를 변경하더라도 모두 업데이트합니다.
- 모든 필드를 대상으로한 쿼리문이 나갑니다.
- 모든 필드를 업데이트하면 수정 쿼리가 항상 같기때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있으며 데이터베이스는 동일한 쿼리를 받으면 이전에 한번 파싱된 쿼리를 재사용할 수 있습니다.
- 필드가 많거나 저장되는 내용이 너무 크면 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성하는 전략을 선택하면 됩니다.
- @DynamicUpdate : 데이터 저장 시, 수정된 데이터만으로 sql을 동적 생성
- 컬럼이 30개 이상일 때 사용하면 정적 수정 쿼리보다 더 빠름
- @DynamicInsert : 데이터 저장 시, null이 아닌 필드만으로 sql을 동적 생성
- @DynamicUpdate : 데이터 저장 시, 수정된 데이터만으로 sql을 동적 생성
- 하나의 필드를 변경하더라도 모두 업데이트합니다.
- 엔티티 수정 시,자바 컬렉션 다루듯 단순히 엔티티를 조회해서 데이터만 변경하면 자동으로 데이터베이스에 반영됩니다.
Member member = em.find(Member.class,"member1");
// 변경 감지로 엔티티 수정
member.setName("dokuny");
@Entity
@org.hibernate.annotation.**DynamicUpdate** // 수정된 데이터만 사용해서 동적으로 SQL을 생성하는 어노테이션
public class Member{}
- 지연 로딩
- 자바 객체 내의 다른 객체를 실제로 사용할 때 불러오는 것을 지연 로딩이라 합니다.
- 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법입니다.
- 쿼리가 많이 발생합니다.
- 즉시로딩은 join을 통해서 연관된 객체를 다같이 가져오는 방식입니다.
- 실무에서는 일단 지연로딩으로 설정해둔뒤 최적화가 필요한 부분에 즉시로딩을 사용합니다.
- 자바 객체 내의 다른 객체를 실제로 사용할 때 불러오는 것을 지연 로딩이라 합니다.
Flush
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것을 의미합니다.
- flush()를 실행하면 변경 감지가 동작한 후, 모아둔 쿼리를 데이터베이스에 전송합니다.(등록, 수정 , 삭제 쿼리)
- flush하는 방법은 3가지 입니다.
- 직접 호출
- 트랜잭션 커밋시 자동 호출
- JPQL 쿼리 실행 시 자동 호출
- FlushModeType으로 모드를 설정할 수 있습니다.
// FlushModeType.AUTO : 커밋,쿼리 실행 시 플러시 (기본값)
//FlushModeType.COMMIT : 커밋 시에만 플러시
// 모드 설정 방법
em.setFlushMode(FlushModeType.COMMIT);
- flush를 하더라도 1차 캐시는 유지됩니다.
'Spring Framework > JPA' 카테고리의 다른 글
Transaction (트랜잭션) (0) | 2022.06.10 |
---|---|
엔티티 매핑 (0) | 2022.06.10 |
데이터베이스 방언 (Dialect) (0) | 2022.06.10 |
JPA 소개 (0) | 2022.06.10 |
Test용 H2 DB application.yml 설정 (0) | 2022.06.09 |
댓글