web/JPA

JPA 영속성 컨테이너에서 엔티티 사용하기

반응형

JPA에서 사용하는 영속성 컨테이너에서 사용되는 엔티티에 대해 정리해보자.


엔티티 생명주기

영속성 컨테이너(Persistent Context)에서 존재하는 엔티티의 생명주기를 정리해보자. 
엔티티는 4가지 상태가 존재한다.

이름 

특징 

 비영속

 영속성 컨텍스트와 전혀 관계가 없는 상태 (엔티티 객체가 생성만 되고 컨텍스트와 아무런 연관이 없는경우)

 영속

 영속성 컨텍스트에 저장된 상태 (엔티티 매니저를 통해서 영속성 컨텍스트에 저장되고 영속성 컨텍스트가 관리한다.)

 준영속

 영속성 컨텍스트에 저장되었다가 분리된 상태 (영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다. close(), clear(), detach() 메소드를 사용하면 준영속 상태가 된다.

 삭제

 삭제된 상태 (remove() 메소드를 통해 영속성 컨텍스트와 데이터베이스에서 엔티티를 제거한다.)


엔티티 조회

- 영속성 컨텍스트 매니저는 내부에 캐시를 가지고 있는데 이를 1차캐시라고 한다. 영속 상태에 들어간 엔티티는 1차 캐시에 먼저 저장되고 키는 엔티티의 @Id가 사용되고 값은 엔티티이다. entityManager.persist(member); 를  하게 되면  영속성  컨텍스트에  엔티티가 1 캐시에 들어가게 된다.

- find(Class<T> entityClass, Object primaryKey)를 통해 데이터를 조회하는데 파라미터에서  첫번째는  엔티티 클래스 번째  파라미터는  키의  값이다.

1
2
3
4
5
6
7
8
9
Member member = new Member();
member.setId("member1");
member.setAge(30);
 
// 1차 캐시에 저장
em.persist(member);
 
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member");
cs


- 데이터 조회  1 캐시 내부에 데이터가 존재하면 데이터베이스를 뒤지지 않고 1차캐시에서 데이터를 가지고온다. 만약 1차 캐시에 데이터가 없으면 데이터를 DB에서 조회한 후 반환하면서 1차캐시에 삽입한다. 다음부터는 1차 캐시에서 동일한 데이터가 있으면 그곳에서 가져온다.

- 영속성 컨텍스트에서 꺼낸 같은 키의 데이터는 항상 같다. (meber1 == member1)


엔티티 삽입

Persistence Context에 엔티티가 들어가고 1차 캐시에 저장되고 데이터베이스에 값이 저장되기 위해서내부에 INSERT SQL을 내부 쿼리 저장소에 저장해두고 커밋되는 순간에 데이터 베이스에서 처리한다. 이 과정을  쓰기지연(transactional write-behind)이라 한다.

1
2
3
4
5
6
Member member = new Member();
member.setId("member1");
member.setAge(30);
 
// 1차 캐시에 저장 (이 부분 까지는 데이터가 저장되지 않는다.)
em.persist(member);
cs


엔티티 수정
기본적으로 SQL 쿼리로 업데이트 진행 시 수정되는 필드가 많으면 그만큼 쿼리가 많아지고 복잡해진다.

1
2
3
4
5
6
7
8
9
10
11
update
    member
set
    name = 'cjung',
    AGE = 22,
    ADDRESS = 'Seoul'
    ..
    ..
    ...
WHERE
    id = 'weduld'
cs

JPA로 기존 데이터를 수정하려고 할 때는 단순하게 데이터를 조회하고 데이터를 변경한 뒤에 commit만 하면된다. 추가적으로 update() 명령이 필요가 없다. 왜냐하면 데이터베이스가 엔티티의 변경사항을 자동으로 감지하는 dirty checking을 하기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * springboottest
 *
 * @author wedul
 * @since 03/10/2018
 **/
@Service
@Slf4j
public class MemberServiceImpl implements MemberService {
 
  @PersistenceContext
  private EntityManager entityManager;
 
  @Override
  @Transactional
  public void jpaService() {
 
    try {
      login(entityManager);
    } catch (Exception ex) {
      log.error(ex.getMessage());
    }
  }
 
  private void login(EntityManager em) {
    String id = "wedul";
        
    // 사용자 찾기
    Member findMember = em.find(Member.class, id);
 
    member.setUserName("weduls");
    member.setAge(2);
 
    // 수정
    findMember.setAge(28);
  }
}
cs

위에 작업이 종료되면 UserName과 Age만 수정될 것 같지만 전체 모든 필드가 업데이트 된다. 모든 필드가 업데이트 되어서 데이터 전송량이 문제가 될 수 있지만 장점도 있다. 데이터베이스는 동일한 쿼리에 대해서는 파싱된 쿼리를 재사용하기 때문에 바인딩된 데이터만 바뀐 쿼리가 실행되기 때문에 성능적 효과가 있을 수 있다.

만약 수정된 부분만 업데이트 하고 싶으면 @DynamicUpdate 어노테이션을 붙히면 된다. Insert에서도 null이 아닌 필드만 삽입 하고 싶은 경우에는 @DynamicInsert를 사용할 수 있다.


엔티티 삭제

엔티티를 삭제하려면 업데이트와 동일하게 데이터를 조회하고 remove 명령어를 사용하여 엔티티를 삭제할 수 있다. 입력과 마찬가지로 바로 삭제하는 것이 아니라 삭제 쿼리를 모으는 저장소에 보관했다가 커밋을 하게 되면 수행된다.



Flush

플러시는 영속성 컨텍스트의 변경내용을 데이터베이스에 반영한다. 영속성 컨텍스트의 내용을 데이터베이스에만 반영할 뿐 컨텍스트에서 지우는 것은 아니다. 플러시가 호출되는 방법은 세가지가 있다.
1. flush() 메서드를 직접 호출해서 강재로 flush한다.
2. 트랜잭션이 커밋될때 자동으로 flush()가 호출된다.
3. JPQL 쿼리 실행시 JPA와 다르게 flush가 바로 호출된다.


옵션

- FlushModeType.AUTO : 커밋과 쿼리를 실행할 때 기본 옵션
- FlushModeType.COMMIT : 커밋할 경우에만 플러시


상태

연속성 상태를 준영속 상태로 변경하는 방법 (준영속 상태는 연속성 컨텍스트로 부터 엔티티가 분리된 상태로 더이상 관리 하지 않는 것을 의미한다.)

em.detach(entity) : 특정 엔티티만 준영속 상태로 전환 
-> 1차 캐시와 쓰기 지연 SQL 저장소에서 해당 엔티티가 관리하는 모든 정보 제거

em.clear() : 영속성 컨텍스트를 완전히 초기화
em.close() : 영속성 컨텍스트를 종료
=> 개발자가 준영속성 상태로 만들일이 거의 없다.

다시 준영속 또는 비용속 상태에서 영속 상태로 바꾸기 위해서 merge 메스드를 이용하면 된다. merge는 준영속 엔티티를 새롭게 병합된 영속 엔티티로 반환해준다. merge를 호출할때 넘긴 파라미터에 엔티티의 식별자의 값으로 찾는 엔티티가 없으면 데이터베이스에서 조회하고 만약 없으면 새로운 엔티티를 생성해서 병합한다.

1
2
3
4
@PersistenceContext
private EntityManager entityManager;
 
Member member = em.merge(member);
cs


반응형