엔티티를 영구 저장하는 환경
JPA는 영속성 컨텍스트라는 환경에서 엔티티를 관리한다.
엔티티 매니저를 통해 접근
J2SE환경에서는 엔티티 매니저와 영속성 컨텍스트가 1:1 관계이지만
J2EE, 스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1의 관계이다.
영속성 컨텍스트를 기준으로 다음과 같이 엔티티의 상태를 나눌 수 있다.
Member member = new Member(1L, "회원1");
Member member = new Member(1L, "회원1");
em.persist(member);
em.detach(member);
em.clear(); //영속성 컨텍스트 내 엔티티를 모두 준영속으로 전환함.
em.close();
준영속 상태가 되면 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
em.remove(member);
1차 캐시라는 저장공간을 가지며 이는 말그대로 캐시 기능을 수행한다.Member member = new Member(1L, "회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회, 1차 캐시에 있으니 DB를 거치지 않음
em.find(Member.class, 1L);
//1차 캐시에 없음, DB에서 조회 -> 1차 캐시에 저장 -> 반환
em.find(Member.class, 2L);
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //true
1차 캐시로 반복 가능한 읽기(Repeatable Read)등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
쓰기 지연 SQL 저장소에 차곡차곡 쌓이고 트랜잭션이 커밋되는 순간에 DB로 보내게 된다.
transaction.begin();
em.persist(memberA);
em.remove(memberB);
memberC.changeName("memberD");
//여기까지 SQL를 DB에 보내지 않음
//커밋하는 순산 DB에 쿼리를 보냄
transaction.commit();
스냅샷을 저장해 놓는다.transaction.begin();
Member memberA = em.find(Member.class, "memberA");
memberA.changeName("memberB");
//memberA.persist(memberA); 를 안해도 자동으로 변경 감지 되어 SQL이 생성된다!
transaction.commit();
영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는것
플러시가 호출되면 변경 감지가 실행되어 수정된 엔티티에 대한 SQL이 쓰기 지연 SQL 저장소에 등록되고 등록된 쿼리들이 DB에 전송된다.
transaction.commit()시 자동으로 호출되며 em.flush()를 통해 직접 호출 할 수 도 있다.
JPQL 쿼리를 실행할때도 플러시가 자동 호출된다. (동시성 보장을 위함)
쿼리 호출시에 플러시를 방지하고 싶다면 em.setFlushMode(FlushModeType.COMMIT)로 설정하면된다.
JPA에서 수정을 하는법
다음과 같이 Item 엔티티의 update를 처리하는 서비스가 있다고 해보자.
파라미터로 넘어온 Item 엔티티는 영속인지 준영속인지 알 수 없다고 가정하자.
@Transactional
void update(Item item) {
// ?
}
JPA에서는 어떤 방식으로 엔티티를 수정해야될까?
위에서 알아봤던 변경 감지 기능을 사용하여 수정할 수 있다.
이 경우에는 변경 감지 기능을 사용하기 위해 엔티티를 영속 상태로 만들어 줘야 한다.
@Transactional
void update(Item item) {
Item findItem = em.find(Item.class, item.getId());
findItem.changePrice(item.getprice());
}
이 경우 트랜잭션 커밋 시점에 변경 감지가 동작해서 데이터베이스에 update SQL이 실행된다.
병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용한는 기능이다.
@Transactional
void update(Item item) {
Item mergeItem = em.merge(item);
}
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만
병합을 사용하면 모든 속성이 변경되어 병합시 값이 없으면 null로 업데이트 될 위험이 있다
엔티티를 변경할 때는 항상 변경 감지를 사용하는것이 좋다.
식별자와 변경할 데이터를 명확하게 전달한다. (파라미터 or dto)