- 참조: 자바 ORM 표준 JPA 프로그래밍
- 준영속
여태까지는 영속상태를 중심으로 알아보았다. 이번에는 영속 -> 준영속 상태를 알아보도록 하자.
영속성관리 초반에 보았던 생명주기 그래프의 내용을 떠올려보자. 영속 상태의 엔티티를 준영속 상태로 만드는 방법은 3 가지가 있다.
- em.detach(entity): 특정 엔티티를 준영속 상태로 전환
- em.clear(): 영속성 컨텍스트 초기화
- em.close(): 영속성 컨텍스트 종료
준영속 상태는 거의 비영속과 같다고 할 수 있다. 영속성 컨텍스트가 제공하는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩의 기능이 동작하지 않는다.
비영속은 식별자 값이 없지만 준영속 상태는 이미 한번 영속 상태였으므로 식별자값을 갖고 있다.
준영속 상태에서 지연 로딩시 문제가 발생할 수 있는데, 이는 나중에 알아보도록 한다.
- 엔티티 준영속 상태로 전환
em.detach() 를 사용하면 특정 엔티티를 준영속 상태로 만들 수 있다.
Member member1 = new Member();
member1.setId("ID#1");
member1.setUserName("ocwokocw1");
member1.setAge(31);
em.persist(member1);
em.detach(member1);
위의 코드에서 member1을 영속화 시켰다가 다시 detach 메소드로 준영속 상태로 전환 시켰다면 COMMIT 을 해도 DB 에 반영되지 않는다. COMMIT 시 자동호출되는 플러시 단계에서 영속화된 엔티티를 쓰기 지연 SQL 저장소에 저장해야하는데, "영속화된" 엔티티가 아니기 때문에 SQL을 만들지 않는다.
2021-06-24 22:31:45.172 INFO 33816 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: jpatest]
2021-06-24 22:31:45.215 WARN 33816 --- [ main] org.hibernate.orm.connections.pooling : HHH10001002: Using Hibernate built-in connection pool (not for production use!)
2021-06-24 22:31:45.217 INFO 33816 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: using driver [org.h2.Driver] at URL [jdbc:h2:tcp://localhost/~/test]
2021-06-24 22:31:45.217 INFO 33816 --- [ main] org.hibernate.orm.connections.pooling : HHH10001001: Connection properties: {user=sa, password=****}
2021-06-24 22:31:45.217 INFO 33816 --- [ main] org.hibernate.orm.connections.pooling : HHH10001003: Autocommit mode: false
2021-06-24 22:31:45.220 INFO 33816 --- [ main] .c.i.DriverManagerConnectionProviderImpl : HHH000115: Hibernate connection pool size: 20 (min=1)
2021-06-24 22:31:45.239 INFO 33816 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2021-06-24 22:31:45.298 INFO 33816 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-06-24 22:31:45.395 INFO 33816 --- [ main] org.hibernate.orm.connections.pooling : HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
로그도 SQL을 출력하지 않는다.
- 영속성 컨텍스트 초기화
em.clear() 는 영속성 컨텍스트를 초기화한다.
만약 위와 같이 member 가 기존 DB 에 저장되어 있다고 가정하자. 아래와 같이 find로 ID#1 member를 조회해와서 em.clear() 로 초기화하고 해당 member 의 나이를 20으로 바꾼다고 해도 DB의 값은 변경되지 않는다.
Member findMember = em.find(Member.class, "ID#1");
em.clear();
findMember.setAge(20);
- 영속성 컨텍스트 종료
em.close() 로 영속성 컨텍스트를 종료하면 더이상 엔티티들은 관리되지 않는다. 보통의 경우 트랜잭션을 끝내고 영속성 컨텍스트를 종료하므로 가장 많이 발생하는 형태이다. 개발자가 직접 준영속 상태로 만드는 일은 드물다고 할 수 있다.
- 병합
준영속을 영속상태로 전환할 떄에는 병합을 사용하면 된다. merge() 메소드는 준영속 상태의 엔티티를 받아서 영속 상태의 엔티티를 반환한다. Member 를 만들고 해당 member를 merge 해본다.
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest");
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
Member member = createMember();
mergeMemeber(member);
emf.close();
}
아래는 createMember() 메소드이다. 트랜잭션을 commit 하고 엔티티 매니저를 종료하여 영속을 준영속 상태로 변경하였다. 그 상태에서 member의 이름을 "ocwokocw" -> "ocwokocw1" 로 변경하고 해당 member를 반환한다.
private static Member createMember() {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member();
member.setId("ID#1");
member.setUserName("ocwokocw");
em.persist(member);
tx.commit();
em.close();
member.setUserName("ocwokocw1");
return member;
}
mergeMemeber() 에서는 createMember() 에서 저장한 유저 정보를 조회하여 dbMember 에 담는다. 해당 유저를 출력해보면 "ocwokocw1"이 아닌 "ocwokocw" 로 되어있다. 준영속 상태이기 때문에 setter 로 이름을 변경했어도 적용이 되지 않는다.
그 후 해당 member 를 merge 로 다시 영속상태로 전환한다. merge는 내부적으로 다음과 같은 과정을 수행한다.
- 1차 캐시에서 엔티티 탐색
- 없으면 DB에서 엔티티를 탐색한 후 1차 캐시에 저장
- 조회한 영속 엔티티(mergeMember)에 병합할 엔티티(member)를 채워 넣는다. (예제 에서는 DB에서 조회한 엔티티의 이름이 "ocwokocw" 인데, 새로운 엔티티 "ocwokocw1" 의 이름으로 채워넣으니 스냅샷과 비교하여 "변경 감지" 라고 판단한다.)
contains 는 영속성 컨텍스트에 해당 엔티티가 있는지 확인해볼 수 있는 메소드인데, member 는 없지만 mergeMember 는 존재한다. 여기서 알 수 있는점은 merge 의 메소드는 Key가 같더라도 준영속 상태의 엔티티를 인자로 받아서 새로운 영속 상태의 엔티티를 반환한다는점이다.
private static void mergeMemeber(Member member) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member dbMember = em.find(Member.class, "ID#1");
System.out.println("dbMember: " + dbMember);
Member mergeMember = em.merge(member);
tx.commit();
System.out.println("member's name: " + member.getUserName());
System.out.println("mergeMember's name: " + mergeMember.getUserName());
System.out.println("em.contains(member): " + em.contains(member));
System.out.println("em.contains(mergeMember): " + em.contains(mergeMember));
em.close();
}
위의 설명을 천천히 따라가면서 아래 출력결과와 비교해보길 바란다.
Hibernate:
/* insert com.example.demo.member.Member
*/ insert
into
MEMBER
(age, NAME, ID)
values
(?, ?, ?)
Hibernate:
select
member0_.ID as id1_0_0_,
member0_.age as age2_0_0_,
member0_.NAME as name3_0_0_
from
MEMBER member0_
where
member0_.ID=?
dbMember: Member [id=ID#1, userName=ocwokocw, age=0]
Hibernate:
/* update
com.example.demo.member.Member */ update
MEMBER
set
age=?,
NAME=?
where
ID=?
member's name: ocwokocw1
mergeMember's name: ocwokocw1
em.contains(member): false
em.contains(mergeMember): true
위의 예제를 통해 병합을 알아보면서 준영속의 엔티티를 영속으로 만들었지만 비영속에 대해서도 동일 동작을 한다. 병합은 준영속/비영속을 신경쓰지 않는다.
'Framework and Tool > JPA' 카테고리의 다른 글
JPA - 엔티티 맵핑 - 기본 키 맵핑 (0) | 2021.06.27 |
---|---|
JPA - 엔티티 맵핑 - 기본 (0) | 2021.06.27 |
JPA - 영속성 관리 - 플러시(flush) (0) | 2021.06.24 |
JPA - 영속성 관리 - 영속성 컨텍스트 (0) | 2021.06.24 |
JPA - 영속성 관리 - 엔티티의 생명주기 (0) | 2021.06.23 |
댓글