자바 서버 개발을 위한 JPA에 대해서 알아보고자 했다.
세 번째, 영속성에 대해서 알아보자.
학습 내용은 자바 ORM 표준 JPA 프로그래밍 교재를 기반으로 작성했다.
영속성 컨텍스트는 JPA에서 주요 기능이라고 볼 수 있다. JPA가 데이터베이스에 접근하기 전 엔티티는 영속성 컨텍스트로 관리하며 commit
이 수행되면 데이터베이스에 해당 내용을 전달한다. 영속성 컨텍스트는 엔티티 매니저를 생성할 때 생성되며 엔티티 매니저를 통해 관리할 수 있다.
비영속 상태는 엔티티를 생성하기만 하고 영속성 컨텍스트에 저장하는 등의 동작을 수행하지 않은 상태이다.
// Member 객체 생성
Member member = new Member();
member.setId("id");
member.setUsername("CrackCo");
Member
객체인 member
를 생성하고 값을 초기화 후 아무런 동작을 수행하지 않은 비영속 상태이다.
영속 상태는 EntityManager
를 통해 영속성 컨텍스트에 저장한 상태이다. 해당 엔티티는 EntityManager
에 의해 관리된다.
// 객체를 영속성 컨텍스트에 저장
em.persist(member);
member
는 EntityManager
의 API persist()
를 통해 영속성 컨텍스트에 저장되어 영속 상태이다.
기존에
persist()
API는 데이터베이스에 저장한다고 하였지만 확실히 말하자면 영속성 컨텍스트에 저장하는 것이다.
영속 상태였던 엔티티를 영속성 컨텍스트에서 분리하거나 영속성 컨텍스트가 초기화되면 해당 엔티티는 준영속 상태가 된다.
// 객체를 영속성 컨텍스트에서 분리
em.detach(member);
// 영속성 컨텍스트 초기화
em.clear();
member
는 EntityManager
의 API detach()
를 통해 영속성 컨텍스트에서 분리되어 준영속 상태이다.
영속 상태인 객체를 영속성 컨텍스트와 데이터베이스에서 삭제한다.
em.remove(member);
member
는 EntityManager
의 API remove()
를 통해 영속성 컨텍스트에서 삭제되며 데이터베이스에서 삭제되는 SQL
을 작성한다.
🚨 영속성 컨텍스트의 내용으로 데이터베이스에 접근할 때 엔티티는
@ID
로 식별하기 때문에 엔티티 클래스를 정의할 때 반드시 지정해야 한다.
영속성 컨텍스트에서는 엔티티를 관리할 때 1차 캐시라는 공간에서 관리한다. EntityManager
의 find()
API를 호출하면 먼저 1차 캐시에서 엔티티를 찾아 반환하고 없으면 데이터베이스를 조회, 엔티티를 생성하여 1차 캐시에 저장하고 반환한다.
영속된 엔티티는 동일성을 보장한다.
Member a = em.find(Member.class, "member1"); Member b = em.find(Member.class, "member1"); System.out.println(a == b); // true
tx.begin(); // 트랜잭션 시작
// 영속성 컨텍스트에 저장
em.persist(memberA);
em.persist(memberB);
// 데이터베이스에 저장
tx.commit(); // 트랜잭션 커밋
엔티티 매니저는 트랜잭션에 커밋하기 전까지 영속성 컨텍스트 관리를 하며 해당 동작을 내부 쿼리 저장소에 INSERT SQL
을 모아두고 트랜잭션의 commit()
API를 수행하면 데이터베이스에 쿼리를 요청한다.
JPA에서 엔티티의 수정은 1차 캐시를 통해 수행된다.
tx.begin(); // 트랜잭션 시작
// member를 ID로 같고 있는 Member 엔티티 반환
Member member = em.find(Member.class, "member");
// 영속 엔티티 수정
member.setUsername("CrackCo1");
member.setAge(3);
tx.commit(); // 트랜잭션 커밋
위에서 작성했든 JPA는 영속성 컨텍스트를 통해 엔티티를 관리하는데 영속성 컨텍스트에 엔티티를 저장할 때 상태를 저장한다. 이를 스냅샷이라고 하는데 트랜잭션이 커밋되면 스냅샷과 현재 영속된 객체의 상태를 비교(변경 감지)하여 변경됐으면 내부 쿼리 저장소에 UPDATE SQL
을 작성한다. 그 후 데이터베이스에 SQL을 보내고 데이터베이스의 트랜잭션을 커밋한다.
🚨 변경 감지는 영속 상태의 엔티티만 적용된다.
Member memberA = em.find(Member.class, "memberA"); // 엔티티 조회
em.remove(memberA); // 엔티티 삭제
EntityManager
의 remove()
API를 사용하여 해당 엔티티를 삭제하면 다른 동작과 같이 내부 쿼리 저장소에 DELETE SQL
을 작성한다. 단, 영속성 컨텍스트에서는 즉시 삭제되기 때문에 엔티티를 수정하거나 할 때 주의해야 한다.
플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 즉각 반영하는 EntityManger
의 API이다. 트랜잭션의 commit()
API를 수행하면 데이터베이스에 SQL이 전달된다고 하였는데 마지막에 EntityManager
의 flush()
API가 자동으로 수행되어 데이터베이스에 전달되는 것이다.
JPQL 쿼리 실행 시에도 플러시는 자동 호출된다.