플러시
플러시(flush()
)는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
영속성 컨텍스트를 플러시 하는 방법은 3가지다.
em.flush()
를 직접 호출한다.- 엔티티 매니저의
flush()
메소드를 직접 호출해서 영속성 컨텍스트를 강제로 플러시 한다. 테스트나 다른 프레임워크와 JPA를 함께 사용할 때를 제외하고 거의 사용하지 않는다.
- 엔티티 매니저의
- 트랜잭션 커밋 시 플러시가 자동 호출된다.
- 데이터베이스에 변경 내용을 SQL로 전달하지 않고 트랜잭션만 커밋하면 어떤 데이터도 데이터베이스에 반영되지 않는다. 따라서 트랜잭션을 커밋하기 전에 꼭 플러시를 호출해서 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영해야 한다. JPA는 이런 문제를 예방하기 위해 트랜잭션을 커밋할 때 플러시를 자동으로 호출한다.
- JPQL 쿼리 실행 시 플러시가 자동 호출된다.
- JPQL이나 Criteria 같은 객체지향 쿼리를 호출할 때도 플러시가 실행된다. 왜 JPQL 쿼리를 실행할 때 플러시가 자동 호출될까?
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
먼저 em.persist()를 호출해서 엔티티 memberA, memberB, memberC를 영속 상태로 만들었다.
이 엔티티들은 영속성 컨텍스트에는 있지만 아직 데이터베이스에는 반영되지 않았다. 이때 JPQL을 실행하면 SQL로 변환되어 데이터베이스에서 엔티티를 조회한다.
하지만 memberA, memberB, memberC는 아직 데이터베이스에 없으므로 쿼리 결과로 조회되지 않는다.
따라서 쿼리를 실행하기 전에 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영해야 한다.
JPA는 이런 문제를 예방하기 위해 JPQL을 실행할 때도 플러시를 자동 호출한다.
플러시 모드 옵션
엔티티 매니저에 플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType
을 사용하면 된다.
FlushModeType.AUTO
: 커밋이나 쿼리를 실행할 때 플러시(기본값)FlushModeType.COMMIT
: 커밋할 때만 플러시
대부분 AUTO 기본 설정을 그대로 사용한다. COMMIT 모드는 성능 최적화를 위해 사용할 수 있다.
em.setFlushMode(FlushModeType.COMMIT) //플러시 모드 직접 설정
혹시라도 플러시라는 이름으로 인해 영속성 컨텍스트에 보관된 엔티티를 지운다고 생각하면 안 된다. 다시 한번 강조하지만 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이 플러시다.
준영속
영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detach)된 것을 준영속 상태라 한다. 따라서 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.
영속 상태의 엔티티를 준영속 상태로 만드는 방법은 크게 3가지다.
em.detach(entity)
: 특정 엔티티만 준영속 상태로 전환한다.em.clear()
: 영속성 컨텍스트를 완전히 초기화한다.em.close()
: 영속성 컨텍스트를 종료한다.
엔티티를 준영속 성태로 전환 : detach()
em.detach()
메소드는 특정 엔티티를 준영속 상태로 만든다.
public void testDetached() {
...
// 회원 엔티티 생성, 비영속 상태
Member member = new Member();
member.setId("memberA");
member.setUsername("회원A");
// 회원 엔티티 영속 상태
em.persist(member);
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
transaction.commit(); //트랜잭션 커밋
}
회원 엔티티를 생성하고 영속화 한 다음 em.detach(member)
를 호출했다.
영속성 컨텍스트에서 더는 해당 엔티티를 관리하지 말라는 것이다.
이 메소드를 호출하는 순간 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 제거된다.
이렇게 영속 상태였다가 더는 영속성 컨텍스트가 관리하지 않는 상태를 준영속 상태라 한다.
영속성 컨텍스트 초기화 : clear()
em.detach()
가 특정 엔티티 하나를 준영속 상태로 만들었다면 em.clear()
는 영속성 컨텍스트를 초기화해서 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만든다.
//엔티티 조회, 영속 상태
Member member = em.find(Member.class, "memberA");
em.clear(); //영속성 컨텍스트 초기화
//준영속 상태
member.setUsername("changeName");
영속성 컨텍스트 종료 : close()
영속성 컨텍스트를 종료하면 해당 영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 모두 준영속 상태가 된다.
public void closeEntityManager() {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] - 시작
Member memberA = em.find(Member.class, "memberA");
Member memberB = em.find(Member.class, "memberB");
transaction.commit(); // [트랜잭션] = 커밋
em.close(); // 영속성 컨텍스트 닫기(종료)
}
병합 : merge()
준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 병합을 사용하면 된다.
merge()
메소드는 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티를 반환한다.
준영속 병합
public class ExamMergeMain {
static EntityManagerFactory emf =
Persistence.createEntityManagerFactory("jpa-study");
public static void main(String[] args) {
Member member = createMeber("memberA", "username1", 20);
member.setUsername("회원 명 변경");
mergeMember(member);
}
static Member createMember(String id, String username, int age) {
// 영속성 컨텍스트1 시작 //
EntityManager em1 = emf.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
// 엔티티 생성
Member member = new Member(id, username, age);
// 영속성 컨텍스트에 엔티티 등록
tx1.begin();
em1.persist(member);
tx1.commit();
em1.close(); // 영속성 컨텍스트 1 종료
// member 엔티티는 준영속 상태가 됩니다.
// 영속성 컨텍스트 1 종료 //
return member;
}
static void mergeMember(Member member) {
EntityManager em2 = emf.createEntityManager();
EntityTransaction tx2 = em2.getTransaction();
tx2.begin();
// 준영속 상태의 member를 영속상태의 mergeMember로 리턴합니다.
Member mergeMember = em2.merge(member);
tx2.commit();
// 준영속 상태
System.out.println("member = " + member.getUsername());
// 영속 상태
System.out.println("mergeMember = " + mergeMember.getUsername());
System.out.println("em2 contains member = " + em2.contains(member));
System.out.println("em2 contains mergeMember = " + em2.contains(mergeMember));
em2.close();
}
}
출력 결과는 다음과 같다.
member = 회원 명 변경
mergeMember = 회원 명 변경
em2 contains member = false
em2 contains mergeMember = true
소스 코드에 대한 설명은 다음과 같다.
createMember
메소드에서 영속성 컨텍스트1(em1
)을 생성한다.createMember
메소드에서 member를persist()
한 후close()
한다.main
메소드에서member.setUsername()
을 호출하여 회원명을 변경한다.mergeMember
메소드에서 영속성 컨텍스트2(em2
)를 생성한다.mergeMember
메소드에서 준영속상태의 member를merge()
하여 영속상태의 mergeMember를 리턴한다.
merge()
의 동작 방식은 아래와 같다.
merge()
를 실행한다.- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
- 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고 1차 캐시에 저장한다.
- 조회한 영속 엔티티(mergeMember)에 member 엔티티의 값을 채워 넣는다.(member 엔티티의 모든 값을 mergeMember에 밀어 넣는다. 이때 mergeMember의 "회원1"이라는 이름이 "회원명변경"으로 바뀐다.)
- mergeMember를 반환한다.
비영속 병합
병합은 비영속 엔티티도 영속 상태로 만들 수 있다.
Member member = new Member();
Member newMember = em.merge(member); //비영속 병합
tx.commit();
병합은 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 찾는 엔티티가 없으면 데이터베이스에서 조회한다. 만약 데이터베이스에서도 발견하지 못하면 새로운 엔티티를 생성해서 병합한다.
병합은 준영속, 비영속을 신경 쓰지 않는다. 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합하고 조회할 수 없으면 새로 생성해서 병합한다. 따라서 병합은 save or update 기능을 수행한다.
참고 자료 :
https://product.kyobobook.co.kr/detail/S000000935744
'백엔드 > JPA' 카테고리의 다른 글
[JPA] 상속 관계 매핑 (0) | 2023.05.30 |
---|---|
[JPA] 엔티티 매핑 (0) | 2023.05.03 |
[JPA] 엔티티 조회, 등록, 수정, 삭제 (0) | 2023.05.02 |
[JPA] 영속성 컨텍스트(persistence context)란? (0) | 2023.05.02 |
[JPA] JPA란? (0) | 2023.05.01 |