엔티티 매니저 팩토리와 엔티티 매니저
- 엔티티 매니저 팩토리는 엔티티 매니저를 만드는 공장으로,
여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유 가능 - 엔티티 매니저란 엔티티를 관리하는 관리자로, 엔티티를 저장, 수정, 삭제, 조회하는 등 엔티티와 관련된 모든 일을 처리하며
여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유 금지 - 데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 엔티티 매니저 팩토리를 하나만 생성
이후 필요할 때마다 엔티티 매니저 팩토리에서 엔티티 매니저를 생성
// JpaMain.java
// META-INT/persistence.xml에 있는 정보를 바탕으로 엔티티 매니저 팩토리 생성
// 공장 만들기, 비용이 아주 많이 듦
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
// 공장에서 엔티티 매니저 생성, 비용의 거의 들지 않음
EntityManager em = emf.createEntityManager();
- JPA 구현체들은 엔티티 매니저 팩토리를 생성할 때 커넥션 풀을 만들며,
엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않으며 트랜잭션을 시작할 때 커넥션을 획득
영속성 컨텍스트란?
- 영속성 컨텍스트란 엔티티를 영구 저장하는 환경이라는 뜻
- 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 접근, 관리
- 엔티티 매니저를 생성할 때 하나의 영속성 컨텍스트가 만들어짐
// JpaMain.java
// persist() 메소드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장
em.persist(member);
엔티티의 생명주기
- 엔티티에는 4가지 상태가 존재
1. 비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
엔티티 객체를 생성했으며, 이는 순수한 객체 상태이며 아직 저장하지 않아 영속성 컨텍스트나 데이터베이스와 전혀 관계가 없음
// 객체를 생성한 상태 (비영속)
// em.persist() 호출 전, 비영속 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
2. 영속 (managed) : 영속성 컨텍스트에 저장된 상태 (영속성 컨텍스트에 의해 관리되는 상태)
엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장하게 되면 영속성 컨텍스트가 관리하는 엔티티가 되어 영속 상태가 됨
em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리하는 영속 상태
// 객체를 저장한 상태 (영속)
// em.persist() 호출 후, 영속 상태
em.persist(member);
3. 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 됨
em.detach()로 특정 엔티티를 준영속 상태로 만들 수 있으며
이외에도 em.close()로 영속성 컨텍스트를 닫거나, em.clear()로 영속성 컨텍스트를 초기화해도 준영속 상태가 됨
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
4. 삭제 (removed) : 삭제된 상태
엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
// 객체를 삭제한 상태 (삭제)
em.remove(member);
영속성 컨텍스트의 특징
- 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블 기본 키와 매핑한 값)으로 구분하므로
영속 상태는 식별자 값이 반드시 있어야 함 - 영속성 컨텍스트에 엔티티를 저장하면
보통 트랜잭션을 커밋하는 순간 새로 저장된 엔티티를 데이터베이스 반영하여 저장하며 이를 플러시(flush)하고 함 - 영속성 컨텍스트가 엔티티를 관리할 때의 장점으로 5가지가 존재
1) 1차 캐시
2) 동일성 보장
3) 트랜잭션을 지원하는 쓰기 지연
4) 변경 감지
5) 지연 로딩
엔티티를 CRUD하면서 이러한 이점과 영속성 컨텍스트의 필요성을 알아보자
1. 엔티티 조회
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있으며, 영속 상태의 엔티티는 모두 이곳에 저장
즉, 영속성 컨텍스트 내부에 Map이 하나 있고 Map의 키는 @Id로 매핑한 식별자이며 값은 엔티티 인스턴스
persist() 메소드를 호출해 1차 캐시에 비영속 상태였던 회원 엔티티를 저장하게 되며, 아직 데이터베이스에는 저장되지 않음
// 1차 캐시에 회원 엔티티를 저장
// 엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 엔티티를 영속
em.persist(member);
1차 캐시의 키는 식별자 값이며, 식별자 값은 데이터베이스 기본 키와 매핑되어 있으므로
영속성 컨텍스트에서 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값
// 엔티티를 조회
// 첫 번째 파라미터는 엔티티 클래스의 타입, 두 번째는 조회할 엔티티의 식별자 값(즉, @Id인 Id 값)
Member member = em.find(Member.class, "member1");
// EntityManager.find() 메소드 정의
public <T> T find(Class<T> entityClass, object primaryKey);
em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없다면 데이터베이스에서 조회
1차 캐시에서 조회
em.find()를 호출하면 우선 1차 캐시에서 식별자 값으로 엔티티를 찾음
만약 찾는 엔티티가 있으면 데이터베이스에서 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 1차 캐시에 저장됨
em.persist(member);
// 1차 캐시에 있는 엔티티를 조회
Member findMember = em.find(Member.class, "member1");
데이터베이스에서 조회
em.find()를 호출했는데 엔티티가 1차 캐시에 없으면,
엔티티 매니저는 데이터베이스를 조회해서 얻은 데이터로 엔티티를 생성한 후 1차 캐시에 저장하고 영속성 상태의 엔티티를 반환
Member findMember2 = em.find(Member.class, "member2");
그렇게 되면 이제 1차 캐시에 있으므로 이 엔티티들을 또다시 조회하면 1차 캐시에서 바로 불러오므로 성능상 이점 존재
영속 엔티티의 동일성 보장
식별자가 같은 엔티티 인스턴스를 조회해서 비교해볼 경우,
같은 것을 반복해서 호출하므로 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환하므로 둘은 같은 인스턴스
즉, 영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // 동일성 비교이며 결과는 참
2. 엔티티 등록
엔티티 매니저를 사용해서 엔티티를 영속성 컨텍스트에 등록하며
엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소(쓰기 지연 SQL 저장소)에 INSERT SQL을 모아둔 후,
커밋할 때 모아둔 쿼리를 데이터베이스에 보내는데 이를 쓰기 지연이라고 함 (transactionl write-behind)
EntityManage em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 함
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않음
// 커밋하는 순간 데이터베이스에 INSERT SQL을 보냄
transaction.commit(); // [트랜잭션] 커밋
위의 경우,
회원 A를 영속화하면 영속성 컨텍스트는 1차 캐시에 회원 엔티티를 저장하면서
동시에 회원 엔티티 정보로 등록 쿼리를 생성하여 등록 쿼리를 쓰기 지연 SQL 저장소에 보관함
그 후 마찬가지로 회원 B를 영속화하고 회원 엔티티 정보로 등록 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보관함
그런 후 마지막에 트랜잭션을 커밋하면 엔티티 매니저는 영속성 컨텍스트를 플러시하여
영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업(등록, 수정, 삭제한 엔티티를 데이터베이스에 반영)을 수행함
동기화 이후에는 실제 데이터베이스 트랜잭션을 커밋
트랜잭션을 지원하는 쓰기 지연이 가능한 이유
begin(); // 트랜잭션 시작
save(A);
save(B);
save(C);
commit(); // 트랜잭션 커밋
위 로직을 2가지 경우로 생각하면
1. save() 메소드를 호출할 때마다 즉시 데이터베이스에 등록 쿼리를 보낸 후 마지막에 트랜잭션을 커밋
2. 등록 쿼리를 모아둔 후 커밋할 때 모아둔 쿼리를 데이터베이스에 보낸 후에 커밋
두 가지 모두 트랜잭션 안에서 실행되므로 결과가 같으며, 트랜잭션을 커밋하면 A, B, C 모두 저장되고, 롤백하면 저장되지 않음
즉, 미리 데이터베이스에 등록 쿼리를 잔달한다고 해도 트랜잭션이 커밋하지 않으며 아무 소용이 없으므로
트랜잭션 커밋 직전에만 데이터베이스에 SQL을 전달해도 되므로 트랜잭션을 지원하는 쓰기 지연이 가능한 이유이며
모다운 등록 쿼리를 한 번에 전달할 경우 성능을 최적화할 수 있음
3. 엔티티 수정
SQL 수정 쿼리의 문제점
SQL을 사용하면 수정 쿼리를 직접 작성해야 하므로 프로젝트가 점점 커져 요구사항이 늘어나게 되면 수정 쿼리도 점점 추가됨
이런 개발 방식은 수정 쿼리가 많아지는 것은 물론이고 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 하므로
직접적이든 간접적이든 비즈니스 로직이 SQL에 의존하게 됨
변경 감지
JPA로 엔티티를 수정할 때는 단순히 엔티티를 조회해서 데이터만 변경하면 됨
엔티티의 변경사항을 데이터베이스에서 자동으로 반영하는 변경 감지 기능으로 인해 데이터베이스에 반영
EntityManage em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 함
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
// em.update(member) 이런 코드가 있어야 하지 않을까? (X)
transaction.commit(); // [트랜잭션] 커밋
JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 스냅샷을 저장하며
트랜잭션이 커밋되어 플러시가 호출되는 시점에 스냅샷과 엔티티를 비교해 변경된 엔티티를 찾음
만약 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보내 저장 후 데이터베이스에 보내 트랜잭션을 커밋
그러므로 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용되며, 그 외에는 값을 변경해도 반영되지 않음
변경 감지로 인해 UPDATE SQL이 실행될 때 변경 부분만으로 수정 쿼리가 생성되는 것이 아닌, 엔티티의 모든 필드를 업데이트
모든 필드를 사용하면 데이터베이스에 보내는 데이터 전송량이 증가하는 단점을 가지는 반면, 다음과 같은 장점을 가짐
1. 모든 필드를 사용하면 수정 쿼리가 항상 같으므로 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성한 후 재사용 가능
2. 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용 가능
// 수정된 데이터만 반영할 것으로 예상
UPDATE MEMBER
SET
NAME=?,
AGE=?
WHERE
id=?
// 실제는 엔티티의 모든 필드를 수정에 반영
UPDATE MEMBER
SET
NAME=?,
AGE=?,
GRADE=?,
...
WHERE
id=?
만약 필드가 많거나 저장되는 내용이 너무 크면 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성할 수 있음
이때는 하이버네이트 확장 기능을 사용
@Entity
// 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성
@org.hibernate.annotation.DynamicUpdate
@Table(name = "Member")
public class Member {...}
// 이외에도 데이터를 저장할 때 데이터가 존재하는 필드만으로 INSERT SQL을 동적으로 생성하는
// @DynamicInsert도 존재
4. 엔티티 삭제
엔티티를 삭제하려면 삭제 대상 엔티티를 조회하고 이를 em.remove()에 넘겨주어 호출하는 순간 영속성 컨텍스트에서 제거됨
이후 삭제 쿼리는 쓰기 지연 SQL 저장소에 등록되고, 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에서 삭제됨
Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 엔티티 조회
em.remove(memberA); // 엔티티 삭제, 영속성 컨텍스트에서 제거
// 이후 트랜잭션 커밋 및 플러시 호출 후 실제 데이터베이스에서 삭제
플러시
- 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 작업을 진행
1. 플러시가 실행될 경우, 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾음
2. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SLQ 저장소에 등록
3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 - 영속성 컨텍스트를 플러시하는 방법에는 3가지가 존재
1. 직접 호출 : em.flush()를 직접 호출함
엔티티 매니저의 flush() 메소드를 직접 호출해서 영속성 컨텍스트를 강제로 플러시
테스트나 다른 프레임워크와 JPA를 함께 사용할 때를 제외하고 거의 사용하지 않음
2. 트랜잭션 커밋 시 플러시 자동 호출
트랜잭션을 커밋하기 전에 꼭 플러시를 호출해서 데이터베이스에 영속성 컨텍스트의 변경 내용을 SQL로 전달해야만 함
그러므로 JPA는 이런 문제를 예방하기 위해 트랜잭션을 커밋할 때 플러시를 자동으로 호출
3. JPQL 쿼리 실행 시 플러시 자동 호출
아래의 경우 em.persist()를 호출해서 엔티티 memberA, memberB, memberC를 영속 상태로 만들었으나,
아직 데이터베이스에는 반영되지 않은 상태에서 JPQL을 실행할 경우 쿼리 결과로 조회되지 않음
그러므로 쿼리를 실행하기 직전에 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영해야 하므로
JPQL을 실행할 때도 플러시를 자동으로 호출
참고로, 식별자를 기준으로 조회하는 find() 메소드의 경우에는 플러시가 실행되지 않음
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();
- 플러시 모드 옵션
플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType을 사용
1. FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (기본값)
2. FlushModeType.COMMIT : 커밋할 때만 플러시 (주로 성능 최적화를 위해 사용)
// 플러시 모드 직접 설정
em.setFlushMode(FlushModeType.COMMIT)
준영속
- 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 것이 준영속 상태
- 따라서 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없음
- 영속 상태의 엔티티를 준영속 상태로 만드는 방법은 3가지
1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
회원 엔티티를 생성하고 영속화한 다음 em.detach()를 호출하면 영속성 컨텍스트는 해당 엔티티를 관리하지 않으므로
메소드를 호출하는 순간 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 제거됨
그러므로 영속성 컨텍스트가 지원하는 어떤 기능도 동작하지 않음
// detach() 메소드 정의
public void detach(Object entity);
// detach() 테스트 코드
public void testDatach() {
...
// 회원 엔티티 생성, 비영속 상태
Member member = new Member();
member.setId("memberA");
member.setUsername("회원A");
// 회원 엔티티 영속 상태
em.persist(member);
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
transaction.commit(); // 트랜잭션 커밋
}
2. em.clear() : 영속성 컨텍스트를 완전히 초기화
영속성 컨텍스트를 초기화해서 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만듦
즉, 영속성 컨텍스트를 제거하고 새로 만든 것과 같음
// 엔티티 조회, 영속 상태
Member member = em.find(Member.class, "memberA");
em.clear(); // 영속성 컨텍스트 초기화
// 준영속 상태
// 준영속 상태이므로 변경 감지가 동작하지 않아 데이터베이스에 반영되지 않음
member.setUsername("changeName");
3. em.close() : 영속성 컨텍스트를 종료
영속성 컨텍스트를 종료하면 해당 영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 모든 준영속 상태가 됨
public void closeEntityManager() {
EntityManagerFactory emf = Persistence.creatEntityFactory("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(); // 영속성 컨텍스트 닫기 (종료) - memberA, memberB가 관리되지 않음
}
- 준영속 상태의 특징
1. 거의 비영속 상태에 가까움
영속성 컨텍스트가 관리하지 않으므로
1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떤 기능도 동작하지 않음
2. 식별자 값을 가지고 있음
비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 이미 한 번 영속 상태였으므로 반드시 식별자 값을 가짐
3. 지연 로딩을 할 수 없음
지연 로딩은 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러옴
하지만 준영속 상태는 영속성 컨텍스트가 더는 관리하지 않으므로 지연 로딩 시 문제 발생 - 병합 : merge()
준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 병합을 사용
merge() 메소드는 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티를 반환
// merge() 메소드 정의
public <T> T merge(T entity);
// merge() 사용 예
Member mergeMember = em.merge(member);
1. 준영속 병합
준영속 상태의 엔티티를 영속 상태로 변경
즉, 파라미터로 넘어온 준영속 엔티티를 사용해서 새롭게 병합된 영속 상태의 엔티티를 반환하며,
파라미터로 넘어온 엔티티는 병합 후에도 준영속 상태로 남아있음
1. merge()를 실행
2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
3. 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고 1차 캐시에 저장
3. 조회한 영속 엔티티에 member 엔티티의 값을 채워 넣으며, 이 때 mergeMember의 "회원1"이 "updateName"으로 변경
4. mergeMember를 반환
public class ExamMergeMain {
static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
public static void main(String[] args) {
/* (1) member 엔티티는 createMember() 메소드의 영속성 컨텍스트1에서 영속 상태였다가
영속성 컨텍스트1이 종료되면서 준영속 상태가 되어
준영속 상태의 member 엔티티를 반환 */
Member member = createMember("memberA", "회원1");
/* (2) 준영속 상태에서 변경
준영속 상태인 member 엔티티를 관리하는 영속성 컨텍스트는 더는 존재하지 않으므로
수정 사항을 데이터베이스에 반영하지 못함 */
member.setName("updateName");
/* (3) mergeMember() 메소드에서 새로운 영속성 컨텍스트2를 시작하고
em2.merge(member)를 호출해서 준영속 상태의 member 엔티티를 영속 상태로 변경하였으므로
(즉, mmember 엔티티가 mergeMember라는 새로운 영속 상태의 엔티티가 반환)
트랜잭션을 커밋할 때 수정했던 회원명이 데이터베이스에 반영 */
mergeMember(member);
}
static Member createMember(String id, String username) {
// 영속성 컨텍스트1 시작
EntityManager em1 = emf.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
tx1.begin();
Member member = new Member();
member.setId(id);
member.setName(username);
em1.persist(member);
tx1.commit();
em1.close(); // 영속성 컨텍스트1 종료, member 엔티티는 준영속 상태가 됨
return member;
}
static void mergeMember(Member member) {
// 영속성 컨텍스트2 시작
EntityManager em2 = emf.createEntityManager();
EntityTransaction tx2 = em1.getTransaction();
tx2.begin();
Member mergeMember = em2.merge(member);
tx2.commit(); // mergeMember의 이름이 변경되었으므로 변경 감지 기능이 동작하여 데이터베이스에 반영
// 준영속 상태
System.out.println("member = " + member.getName()); // member = updateName
// 영속 상태
System.out.println("mergeMember = " + mergeMember.getName()); // mergeMember = updateName
/* em.contains(entity)를 통해 영속성 컨텍스트가 파라미터로 넘어온 엔티티를 관리하는지 확인
따라서 준영속 상태인 member 엔티티와 mergeMember 엔티티는 서로 다른 인스턴스이므로
준영속 상태인 member는 이제 사용할 필요가 없으므로
준영속 엔티티를 참조하던 변수인 Member mergeMember = em2.merge(member); 대신
member = em.merge(member); 라고 변경하는 것이 안전 */
System.out.println("em2 contains member = " + en2.contains(member)); // em2 contains member = false
System.out.println("em2 contains mregeMember = " + en2.contains(mregeMember)); // em2 contains mregeMember = true
em2.close(); // 영속성 컨텍스트2 종료
}
}
2. 비영속 병합
병합은 비영속 엔티티도 영속 상태로 만들 수 있음
병합은 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 찾는 엔티티가 없으면 데이터베이스에서 조회
만약 데이터베이스에서도 발견하지 못하면 새로운 엔티티를 생성해서 병합
즉, 병합은 준영속, 비영속을 신경 쓰지 않으므로 save or update 기능을 수행
Member member new Member();
Member mergeMember = em.merge(member); // 비영속 병합
tx.commit();
'Java-Spring > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 연관관계 매핑 기초 (0) | 2022.03.29 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 엔티티 매핑 - 실전 예제 (0) | 2022.03.26 |
[자바 ORM 표준 JPA 프로그래밍] 엔티티 매핑 (0) | 2022.03.23 |
[자바 ORM 표준 JPA 프로그래밍] JPA 시작 (0) | 2022.03.15 |
[자바 ORM 표준 JPA 프로그래밍] JPA 소개 (0) | 2022.03.13 |