김영한, ⌜자바 ORM 표준 JPA 프로그래밍⌟ 과
인프런 강의 <자바 ORM 표준 JPA 프로그래밍 - 기본편> 으로
공부한 JPA를 정리한 내용이다.
앞선 포스팅에서도 언급했듯 엔티티매니저를 사용해 엔티티와 관련된 일을 처리하는데 엔티티매니저를 사용하려면 엔티티매니저팩토리에서 필요할 때마다 생성해야함.
JPA가 실제 매핑한 엔티티를 사용할 때 엔티티매니저를 통해 영속성 컨텍스트에 접근이 가능하고 엔티티를 영속성 컨텍스트에 저장함.
Persistence Context
em.persist()
-> 영속성컨텍스트에 저장함(영속화)비영속(new/transient), 영속(managed), 준영속(detached), 삭제(removed)
only 객체를 생성만 한 상태
영속성컨텍스트나 데이터베이스와는 전혀 무관련
Member member = new member();
member.setId(100L);
member.setName("A");
영속성컨텍스트가 엔티티를 관리하는 상태
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 비영속. 객체를 생성만 한 상태
Member member = new Member();
member.setId(100L);
member.setName("helloJPA");
// 영속. 객체를 저장한 상태
System.out.println("========= BEFORE =========");
em.persist(member); // 저장
System.out.println("========= AFTER =========");
tx.commit(); // 트랜잭션 커밋
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
em.persist(member)
-> DB 저장은 아님. 커밋이 되야 쿼리가 날라감.
영속 상태 엔티티를 영속성 컨텍스트가 관리하지 않는 상태.
영속성 컨텍스트에서 분리.
-> 영속성 컨텍스트가 제공하는 기능 사용 불가
준영속 상태로 만드는 방법
1. em.detach(entity)
: 특정 엔티티만 준영속 상태로 전환
try {
Member member = em.find(Member.class, 100L);
member.setName("AAAAAA"); // "helloJPA" -> "AAAAAA"
em.detach(member);
tx.commit();
}
위에서 생성된 member를 조회하고 수정하면 업데이트 쿼리가 날라가야하지만 em.detach(member);
로 해당 엔티티를 준영속상태로 전환했기 때문에 업데이트 쿼리가 생성되지 않음. 해당 엔티티를 관리하기 위한 모든 정보가 영속성컨텍스트에서 제거됨.
2. em.clear()
: 영속성컨텍스트 초기화
try {
Member member = em.find(Member.class, 100L);
em.clear();
Member member2 = em.find(Member.class, 100L);
tx.commit();
}
위 경우엔 select 쿼리가 2번 발생함
3. em.close()
: 영속성 컨텍스트 종료
주로 영속성컨텍스트가 종료되면서 준영속상태가 됨.
개발자가 직접 준영속상태로 만드는 경우는 거의 없음
em.remove(member)
엔티티를 영속성컨텍스트와 데이터베이스에서 삭제
@Id
, PK)Member member = new Member();
member.setId(101L);
member.setName("helloJPA");
// 영속. 객체를 저장한 상태
System.out.println("========= BEFORE =========");
em.persist(member); // 1차캐시에 저장
System.out.println("========= AFTER =========");
Member findMember = em.find(Member.class, 101L);
// 1차캐시에 있는 애를 조회. 조회쿼리 안날라감
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
tx.commit(); // 트랜잭션 커밋
기능 호출이 오면 앤티티매니저가 생성되고 엔티티매니저는 트랜잭션 종료 시 지우기 때문에 한 트랜잭션 안에서만 1차캐시가 작동함
그래서 찰나의 순간에만 이득 있기 떄문에 성능적으로는 크게 이점은 없음.
1차캐시의 컨셉이 주는 이점이 있는 것.
비즈니스 로직이 복잡해서 한 트랜잭션이 겁나 복잡하면 성능적으로 이점이 있을수도..
* JPA는 1차캐시를 통해 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 어플리케이션 차원에서 제공함
동일성(identity)
실제 인스턴스가 같음. 참조값, 주소값이 같음.==
비교 값이 같음
Member mem1 = em.find(Member.class, 100L);
Member mem2 = em.find(Member.class, 100L);
System.out.println("result = " + (mem1 == mem2)) // result = true
반복해서 호출하더라도 1차캐시에 있는 엔티티 인스턴스를 반환하기 때문
엔티티매니저는 쓰기지연SQL저장소가 있지
트랜잭션을 커밋하기 직전까지 데이터베이스에 쿼리를 날리지 않고 쓰기지연저장소에 쿼리를 모아둠. 커밋하면 모아둔 쿼리를 한 번에 데이터베이스에 보냄
Member member1 = new Member(140L, "A");
Member member2 = new Member(130L, "B");
em.persist(member1);
em.persist(member2);
System.out.println("=================="); // 이거 이후 쿼리 두개 날라감
tx.commit(); // 트랜잭션 커밋
트랜잭션 범위 내에서 내부 로직은 함께 저장되고 롤백을 할 경우 함께 저장되지 않기 때문에 트랜잭션 켜밋 직전에만 데이터베이스에 쿼리를 전달하면 됨.
성능 최적화 가능
이걸로 버퍼기능도 사용 가능 (하이버네이트 제공 @BatchSize 설정) 등등
엔티티 변경사항을 감지해서 데이터베이스에 자동으로 반영
1차캐시에 있는 스냅샷(최초 상태를 복사해서 저장함)과 비교하여 변경 감지
Member member = em.find(Member.class, 150L);
member.setName("ZZZ"); // 더티체킹으로 업데이트쿼리가 날라가지
// em.persist(member); // 자바 컬렉션에서 값 변경하는 것처럼 해당 코드는 필요없음.
tx.commit(); // 트랜잭션 커밋
트랜잭션 커밋 -> 엔티티매니저 내부에서 플러시 호출 -> 엔티티와 스냅샷 비교 후 변경된 엔티티 검색 -> 변경된 엔티티: 수정쿼리 생성 후 쓰기지연저장소로 전송 -> 쓰기지연저장소의 SQL 데이터베이스로 전송 -> 트랜잭션 커밋