[JPA]영속성 컨텍스트란?
1. 영속성 컨텍스트란?
- 영속성 컨텍스트는 JPA에서 가장 중요한 용어입니다.
- 영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻입니다.
- 엔티티 매니저 팩토리로 생성한 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리합니다.
2. EntityManagerFactory, EntityManager, EntityTransaction
EntityManagerFactory
- 엔티티 매니저 팩토리는 이름 그대로 엔티티 매니저 공장을 뜻합니다.
- 엔티티 매니저 팩토리는 많이 생성하면 안 되고 애플리케이션 전체에서 공유하도록 설계되어야 합니다. 이유는 엔티티 매니저 팩토리는 생성하는데 비용이 매우 크기 때문에 많이 생성되면 안 됩니다.
- 여러 스레드가 동시에 접근해도 안전하고 서로 스레드 간에 공유를 해도 됩니다.
EntityManagerFactory 사용방법
* persistence.xml
<persistence-unit name="jpa">
.
설정
.
.
</persistence-unit>
* JpaMain
import javax.persistence.EntityManagerFactory;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
//엔티티 매니저, 트랜잭션, 등록, 수정, 삭제 로직
.
.
.
.
emf.close();
}
}
- persistence.xml 파일에서 설정한 persistence-unit name을 엔티티 매니저 팩토리를 생성할 때 넣어줍니다.
- 애플리케이션을 종료할 때 엔티티 매니저 팩토리도 종료해야 합니다.
EntityManager
- 엔티티 매니저 팩토리에서 생성됩니다.
- 엔티티 매니저를 사용해서 엔티티를 데이터 베이스에 등록, 수정, 삭제, 조회할 수 있습니다.
- 엔티티 매니저 팩토리와 달리 엔티티 매니저는 여러 개 생성해도 되지만 스레드 간에 공유하거나 재사용하면 안 됩니다. 왜냐하면 엔티티 매너저는 데이터베이스 커넥션과 밀접하게 관계가 있기 때문입니다.
- 마지막으로 사용이 끝난 엔티티 매니저는 반드시 종료해야 합니다.
EntityManager 사용법
* JpaMain
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
// 등록, 조회, 삭제 로직
.
.
.
.
em.close();
emf.close();
}
}
EntityTransaction
- 트랜잭션 역할을 합니다.
EntityTransaction 사용법
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin() // 트랜잭션 시작
transaction.commit() // 커밋
transaction.rollback() // 롤백
}
}
3. 엔티티의 생명주기
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속(managed) : 영속성 컨텍스트에 저장된 상태
- 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(remove) : 삭제된 상태
@Entity
@Table(name="MEMBER")
@Getter @Setter
public class Member {
@Id
private String id;
private String username;
private int age;
}
※ 비영속 상태
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
}
※ 영속 상태
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
em.persist(member);
}
- em.persist(member) : 영속성 컨텍스트 1차 캐시에 저장을 합니다. 저장을 함으로써 영속성 컨텍스트에서 보관하고 관리합니다.
※ 준영속 상태
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
em.persist(member);
// 준영속
em.detach(member);
em.clear();
em.close();
//준 영속 -> 영속
em.merge(member);
}
- em.detach(member) : member만 영속석 컨텍스트에서 분리하여 준영속 상태로 만듭니다.
- em.clear() : 영속성 컨텍스트에 있는 초기화 하여 준영속 상태로 만듭니다.
- em.close() : 영속성 컨텍스트를 닫습니다.
- em.merge(member) : 준영속 상태인 member를 매개변수로 넣어 새로운 영속상태인 객체를 만듭니다.
※ 삭제
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
em.persist(member);
em.remove(member);
}
- em.remove(member) : 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제를 합니다.
4. 영속성 컨텍스트 특징
- 영속 상태는 식별자 값이 반드시 필요합니다. 식별자 값이 없을 경우 예외가 발생합니다.
- 영속성 컨텍스트에 저장을 한다고 데이터 베이스에 저장이 되는 것이 아닙니다. 트랜잭션이 커밋하는 순간에 데이터베이스에 반영을 하는데 이것을 flush라고 합니다.
- 영속성 컨텍스트가 엔티티를 관리하면 좋은 점 : 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩
※ 1차 캐시
위에 em.persist(member)에서도 말했듯이 em.persist(member)을 한다고 데이터베이스에 바로 반영이 되는 것이 아닙니다. em.persist(member)을 하면 영속성 컨텍스트에 있는 1차 캐시에 보관이 됩니다. 데이터베이스에 반영이 되려면 커밋이나 플러시를 해주어야 합니다. 그래서 em.persist(member)을 하면 쿼리문은 커밋을 한 후에 실행됩니다.
1차 캐시에 보관되는 경우
- em.persist(member)
- em.find(엔티티 타입, 엔티티 식별 값) : em.find는 1차 캐시에 엔티티 값이 있으면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회하고 없으면 데이터베이스에서 조회하여 1차 캐시에 조회한 엔티티를 저장을 한 후 영속 상태의 엔티티를 반환합니다.
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
// 1차 캐시에 저장
em.persist(member);
// 1차 캐시에 있는 엔티티 조회
em.find(Member.class, member.getId());
}
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
em.persist(member);
em.flush();
em.clear();
//em.clear()로 영속성 컨텍스트를 초기화하였기 때문에
// 1차캐시에는 비어있습니다.
// 1차 캐시가 비어있어 데이터베이스에서 엔티티를 조회후
// 1차캐시에 저장을 하고 조회한 엔티티를 반환합니다.
Member findMember =em.find(Member.class, member.getId());
}
※ 동일성 보장
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member = new Member();
member.setId("member1");
member.setUsername("hoestory");
member.age(25);
em.persist(member);
em.flush();
em.clear();
Member findMember1 =em.find(Member.class, member.getId());
Member findMember2 =em.find(Member.class, member.getId());
System.out.println(findMember1 == findMember2);
}
- findMember1 == findMember2는 true입니다. 왜냐하면 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환하기 때문입니다.
※ 트랜잭션을 지원하는 쓰기 지연
1차 캐시에 있는 엔티티 값들은 바로 쿼리문을 실행하지 않습니다. 커밋이나 플러시를 하기 전까지 쓰기 지연 SQL 저장소에 쿼리문을 보관을 해놓고 있기 때문입니다. 이 기능을 활용하면 성능을 최적화할 수 있습니다.
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member1 = new Member();
member1.setId("member1");
member1.setUsername("hoestory");
member1.age(25);
//쓰기지연 SQL 저장소에 저장
em.persist(member1);
Member member2 = new Member();
member2.setId("member2");
member2.setUsername("HOE");
member2.age(12);
// 쓰기 지연 SQL 저장소에 저장
em.persist(member2);
// 쓰기지연 SQL 저장소에 있던 쿼리문을 한번에 데이터 베이스에 반영
transaction.commit();
// em.commit() 외에 다른 방법
em.flush();
}
※ 변경 감지
JPA는 엔티티를 수정할 때 특별한 메서드는 없습니다. 저장할 땐 em.persist, 찾을 땐 em.find를 사용하는데 엔티티를 수정할 때는 member.setXxx(변경할 내용)을 작성해주면 됩니다. 엔티티 데이터만 변경을 해도 데이터 베이스에 반영이 됩니다. 왜냐하면 변경사항을 데이터베이스에 자동으로 반영해주는 기능이 변경 감지 기능이기 때문입니다.
- 스냅샷 : 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태로 복사해서 저장
- 스냅샷과 플러시 시점의 엔티티를 비교해서 변경된 엔티티를 찾습니다
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보내고 쓰기 지연 SQL을 데이터베이스에 보낸 후 트랜잭션을 커밋합니다.
- 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티만 적용이 됩니다. 비영속, 준영속 상태에서 데이터를 아무리 변경을 계속해도 데이터베이스에 반영되지가 않습니다.
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Member member1 = new Member();
member1.setId("member1");
member1.setUsername("hoestory");
member1.age(25);
// 데이터 수정
member1.setAge(20);
transaction.commit();
}
5. 엔티티 조회, 등록, 수정, 삭제 예제
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
try{
transaction.begin();
Member member1 = new Member();
member1.setId("member1");
member1.setUsername("hoestory");
member1.age(25);
// 등록
em.persist(member1);
// 조회
Member findMember = em.find(Member.class, member1.getId());
// 수정
member1.setAge(20);
// 삭제
em.remove(member1);
transaction.commit();
}catch(Exception e) {
e.printStackTrace();
transaction.rollback();
finally{
em.close();
}
emf.close();
}
6. 플러시(flush)
- 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다.
영속성 컨텍스트를 플러시 하는 방법
1. 플러시하고 싶은 곳에 직접 em.flush()를 호출합니다.
2. 트랜잭션 커밋 시 자동으로 flush를 호출합니다.
3. JPQL 쿼리 실행 시 자동으로 flush를 호출합니다.
※ 직접 호출
- 강제로 플러시를 하는데 테스트나 다른 프레임워크와 JPA 사용할 때 말고는 거의 사용하지 않습니다.
※ 트랜잭션 커밋 시 자동호출
- 데이터베이스 변경내용을 SQL로 전달하지 않고 트랜잭션만 커밋하면 데이터는 반영되지 않습니다. 그래서 데이터를 반영하고 싶으면 커밋 전에 flush를 해주어야 해서 JPA에서 이런 문제를 예방하기 위해 자동으로 플러시를 해줍니다.
플러시 모드 옵션
- FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시를 해줍니다(기본값)
- FlushModeType.COMMIT: 커밋할 때만 플러시를 해줍니다.
em.setFlushMode(FlushModeType.AUTO);
'JPA' 카테고리의 다른 글
[JPA] N+1 원인 및 해결방법 (0) | 2023.01.18 |
---|---|
[Querydsl] 스프링 부트에서 Querydsl 설정 (0) | 2022.06.14 |
[JPA] 프록시, 즉시 로딩, 지연 로딩 (0) | 2022.01.30 |
[JPA] 단방향, 양방향, 연관관계 주인 (0) | 2022.01.26 |
[JPA] 엔티티 매핑 (0) | 2022.01.25 |
댓글
이 글 공유하기
다른 글
-
[Querydsl] 스프링 부트에서 Querydsl 설정
[Querydsl] 스프링 부트에서 Querydsl 설정
2022.06.14 -
[JPA] 프록시, 즉시 로딩, 지연 로딩
[JPA] 프록시, 즉시 로딩, 지연 로딩
2022.01.30 -
[JPA] 단방향, 양방향, 연관관계 주인
[JPA] 단방향, 양방향, 연관관계 주인
2022.01.26 -
[JPA] 엔티티 매핑
[JPA] 엔티티 매핑
2022.01.25