4 분 소요

자바 ORM 표준 JPA 프로그래밍에서 읽은 내용을 정리하는 글입니다😄

JPA가 제공하는 기능은 크게 2가지로 나뉜다.

  • 엔티티와 테이블 매핑하는 설계 부분
  • 매핑한 엔티티 사용 부분

여기서는 매핑한 엔티티를 어떻게 사용하는지 알아보자.
엔티티 매니저는 엔티티와 관련된 모든 일(CRUD 등)을 처리하는 엔티티 관리자다.
개발자 입장에서는 엔티티를 저장하는 가상의 데이터베이스로 생각하면 된다.

3.1 엔티티 매니저 팩토리와 엔티티 매니저

2장에서 언급했듯이 엔티티 매니저 팩토리는 생성시 비용이 아주 많이 든다.
데이터베이스를 1개만 사용하는 애플리케이션은 일반적으로 엔티티 매니저 팩토리를 1개만 사용한다.
인자에 jpabook이 들어가므로 META-INF/persistence.xml에서 persistence-unit이 jpabook인 것들을
찾아 해당되는 내용을 참조해 EntityManagerFactory를 생성한다.

//엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

엔티티 매니저 팩토리와 다르게 엔티티 매니저는 생성 비용이 거의 들지 않는다.
엔티티 매니저 팩토리는 서로 다른 스레드 간 공유가 이뤄져도 상관없지만,
엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 공유하면 안된다.

//엔티티 매니저 생성
EntityManager em = emf.createEntityManager();

3.2 영속성 컨텍스트

영속성 컨텍스트(persistence context)는 엔티티를 영구 저장하는 환경이다.
엔티티 매니저를 생성할 때 하나가 만들어지며 엔티티 매니저를 통해 접근하고 관리할 수 있다.
엔티티 매니저의 영속성 컨텍스트에 엔티티를 저장할 때 다음과 같이 처리한다.

em.persist(member);

3.3 엔티티의 생명주기

엔티티에는 4가지 상태가 존재한다.

  • 비영속(new, transient) : 영속성 컨텍스트와 관계가 없는 상태
  • 영속(managed) : 영속성 컨텍스트에 저장된 상태
  • 준영속(detached) : 영속성 컨텍스트에 저장됐다가 분리된 상태
  • 삭제(removed) : 삭제된 상태
비영속

생성 후 저장되지 않았을 때 상태를 의미한다.
즉, 영속성 컨텍스트, 데이터베이스와 관련이 없다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
영속

비영속 상태인 엔티티를 영속성 컨텍스트에 저장했을 때의 상태이다.
영속 상태라는 건 영속성 컨텍스트에 의해 관리됨을 의미한다.
또한 em.find()나 JPQL을 사용해 조회한 엔티티도 영속 상태이다.

em.persist(member);
준영속

영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다.
em.detach()를 호출하거나, em.close()로 영속성 컨텍스트를 닫거나
em.clear()로 영속성 컨텍스트를 초기화하면 엔티티는 준영속 상태가 된다.

em.detach(Member);
삭제

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제하는 것을 의미한다.

em.remove(Member);

3.4 영속성 컨텍스트의 특징

  • 영속성 컨텍스트는 식별자 값(@Id)으로 엔티티를 구분
  • JPA가 트랜잭션을 커밋해 엔티티를 데이터베이스에 반영하는 것을 플러시(flush)라 함

영속성 컨텍스트가 엔티티를 관리할 때 여러 장점이 존재하는데
엔티티를 CRUD 해보면서 어떤 장점들이 있는지 확인해보자.

3.4.1 엔티티 조회

영속성 컨텍스트 내부에는 1차 캐시데이터베이스가 존재한다.
엔티티를 조회하면 1차 캐시를 먼저 조회하고 그 다음 데이터베이스를 조회한다.
또한 엔티티를 저장(em.persist(Member))하면 1차 캐시에 저장한다.

만약 조회를 했는데 1차 캐시에 없고 데이터베이스에 존재한다면,
데이터베이스에 있는 엔티티를 가져와 1차 캐시에 저장하고 엔티티를 반환한다.
캐시 역할을 통해 성능상 이점을 가져온다.

또한, 영속 컨텍스트는 엔티티 동일성 보장이라는 장점이 존재한다.

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b); // 같은 엔티티 인스턴스를 반환해 1을 출력
3.4.2 엔티티 등록

엔티티 매니저는 DB에 엔티티를 저장하지 않고 내부 쿼리 저장소(쓰기 지연 SQL 저장소)에
INSERT SQL를 모아둔 후 마지막에 트랜잭션 커밋으로 모아둔 쿼리를 DB로 보낸다.
이를 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)라 한다.

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 함

em.persist(member1);
em.persist(member2);
... // DB에 보내지 않고 쓰기 지연 SQL 저장소에 저장

transaction.commit(); // 트랜잭션 커밋. DB에 INSERT SQL보냄 (플러시)
3.4.3 엔티티 수정

요구사항이 늘어나 기존 쿼리에서 수정해야 하는 내용이 많아지는 상황을 생각해보면,
필요한 정보 입력 여부에 따라 필요한 수정 쿼리가 점점 늘어나게 된다.
결국 비즈니스 로직이 SQL에 의존하는 문제점이 발생한다.

JPA에서는 엔티티 수정 시 엔티티 조회 후 데이터만 변경하면 알아서 처리해준다.
즉, 위처럼 특정 상황에 의존하는 쿼리를 계속 만들어내지 않아도 쉽게 해결할 수 있다는 말이다.
이렇게 변경사항을 DB에 자동으로 업데이트 해주는 기능을 변경 감지(dirty checking)이라 한다.
영속 상태의 엔티티에만 적용되며 비영속, 준영속은 해당되지 않아 값 변경 후에도 DB에 반영되지 않는다.

JPA가 동적으로 수정 쿼리를 생성해 처리하기 때문에 데이터 전송량이 증가하는 단점이 있지만
이전에 사용된 쿼리를 재사용할 수 있다는 장점이 존재한다.

필드 또는 저장할 내용이 많으면 하이버네이트 확장 기능을 사용해서 다음과 같이 처리할 수 있다.
이 어노테이션을 사용하면 동적으로 UPDATE SQL을 생성한다.

@Entity
@org.hibernate.annotations.DynamicUplast_modified_at // 참조

null이 아닌, 데이터가 존재하는 필드만으로 INSERT SQL을 동적으로 생성하는 @DynamicInsert도 있다.

3.4.4 엔티티 삭제

삭제할 엔티티 조회 후 em.remove()의 인자로 넘겨주면 엔티티를 삭제한다.

Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 엔티티
em.remove(memberA); // 삭제

엔티티 수정에서 언급한 것처럼 엔티티 삭제도 쓰기 지연 SQL 저장소에 먼저 등록이 되고,
이후 트랜잭션 커밋, 플러시 호출 등이 일어난 뒤에 DB에 삭제 쿼리를 전달하게 된다.
em.remove(memberA) 호출 시 memberA는 영속성 컨텍스트에서 제거된다.

3.5 플러시

영속성 컨텍스트에서 변경된 내용을 DB에 반영하는 것을 플러시(flush)라 한다.
플러시 방법은 3가지로 나뉜다.

  • em.flush()를 사용해 직접 호출
  • 트랜잭션 커밋 시 플러시를 자동 호출
  • JPQL 쿼리 실행 시 플러시를 자동 호출
3.5.1 플러시 모드 옵션

플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType을 사용한다.

  • FlushModeType.AUTO : 커밋 or 쿼리 실행 시 플러시 (default)
  • FlushModeType.COMMIT : 커밋 시 플러시

사용 예시 : em.setFlushMode(FlushModeType.COMMIT)

3.6 준영속

영속성 컨텍스트가 관리하는 엔티티가 영속 상태라면,
영속성 컨텍스트에서 분리된 엔티티를 준영속 상태라 한다.
준영속 상태인 경우 영속성 컨텍스트가 제공하는 기능을 이용할 수 없다.

3.6.1 준영속 상태 전환 : detach()

앞에서 엔티티를 영속 상태로 만들 땐 em.persist(member)를 활용했는데,
비슷하게 준영속 상태로 만들 땐 em.detach(member)를 활용하면 된다.
호출 시 1차 캐시, 쓰기 지연 SQL 저장소에 해당 엔티티에 대한 정보가 제거된다.
즉, 영속성 컨텍스트가 관리하지 않는 상태인 준영속 상태로 만드는 것을 의미한다.

3.6.2 영속성 컨텍스트 초기화 : clear()

em.detach()는 엔티티 하나를 적용하는 방법이라면 em.clear()는 영속성 컨텍스트의
모든 엔티티를 준영속 상태로 만들 때 적용하는 방법이다.

3.6.3 영속성 컨텍스트 종료 : close()

말그대로 영속성 컨텍스트를 종료하는 것으로 관리하던 영속 상태 엔티티들을 모두 준영속 상태로 만든다.

EmtityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager();
...

transaction.commit(); // 커밋

em.close(); // 영속성 컨텍스트 종료
3.6.4 병합 : 준영속 → 영속

merge() 메소드는 준영속 상태의 엔티티를 받아 영속 상태인 엔티티를 반환한다.
이 때, 준영속 엔티티뿐만 아니라 비영속 엔티티도 받아서 처리할 수 있다.
파라미터로 들어오는 엔티티를 영속성 컨텍스트에서 조회하고 없으면 DB에 가서 조회하게 되며
DB에도 없다면 새로운 엔티티를 만들어서 반환하기 때문에 최종적으로 영속 상태인 엔티티를 반환한다.
즉, 병합은 준영속, 비영속을 신경쓰지 않는다.

카테고리:

업데이트:

댓글남기기