여태 spring-boot-starter-data-jpa 가 JPA의 전부인 줄 알았지만 아니었다. Java에서 대표적인 ORM이 JPA이고(표준이 되었음) 그 구현체 Hibernate가 있는 것과 객체는 객체답게, RDB는 RDB답게 설계하면 ORM 프레임워크가 자동으로 매핑해주는 것, DB에 종속적이지 않은 것 등은 알고 있었다. 그래서 JPA가 뭔지 더 찾아보니 기존에 내가 사용하던 것들의 원리를 알 수 있었다.
JPA의 원리
JPA 동작
JPA는 어플리케이션과 JDBC 사이에서 동작한다. 어플리케이션에서 JPA로 명령을 하면 JPA가 해석해서 JDBC API를 사용하여 SQL을 실행하고 그 결과를 반환받는다. 그래서 findAll()과 같은 자바스러운 메소드를 사용해도 변환해서 SELECT * ~~ 와 같은 쿼리가 발생하는 것이다.
MyBatis를 사용해 보았으면 알겠지만 쿼리문을 하나하나 작성하면 엄청 힘들다. 이렇게 SQL중심적이 아닌 객체 중심적으로 개발할 수 있는 것이 엄청 큰 장점이라 생각한다.
findAll과 같은 메소드는 spring-boot-starter-data-jpa이다. 실제 jpa는 아래와 같은 메소드로 CRUD 작업을 한다.
- 저장 : jpa.persist(member)
- 조회 : Member member = jpa.find(memberId)
- 수정 : member.setName(”변경할 이름”)
- 삭제 : jpa.remove(member)
원래 알던 모습과는 다르지만 CRUD에 대한 SQL작업은 필요 없고 만들어져 있는 메소드를 사용하는 것은 똑같다. 특히 수정은 내가 원하는 값만 넣으면 DB로 수정하는 쿼리를 보낸다. 기존에 사용할 때도 객체를 변경하고 save()만 호출해도 수정 쿼리가 나간 것 처럼 똑같다. 이것도 나중에 이유를 알 수 있었다.
JPA 구동 방식
JPA의 Persistence 클래스가 META-INF/persistence.xml 설정 파일을 읽어서 EntityManagerFactory 라는 클래스를 생성한다. 여기서 필요할 때마다 EntityManager를 만들어서 사용한다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
Member member = entityManager.find(Member.class, 1L);
member.setName("helloJPA");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
entityManager.close();
}
entityManagerFactory.close();
- 업데이트 쿼리를 따로 작성하지 않아도 member객체의 값이 변경되어 자동으로 update 쿼리가 나간다.
- 주의할 점
- EntityManagerFactory
- 어플리케이션 실행 시점에 하나만 생성해서 어플리케이션 전체에 걸쳐 공유한다
- EntityManager
- 요청이 왔을 때 썼다가 끝나면 버리는 사이클을 반복한다. → 절대 스레드끼리 공유하면 안된다.
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
- 트랜잭션을 실제로 지정하여 사용해보진 않았지만 DB에서 자체적으로 사용하기 때문에 적용해왔을 것이다.
- EntityManagerFactory
JPQL
- entityManager.find()는 복잡하지 않고 단순한 조회만 할 수 있다. 복잡한 조건이 들어간다면 JPQL을 사용해야 한다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
List<Member> result = entityManager
.createQuery("select m from Member as m", Member.class)
// 1번부터 10개 가져오는 설정
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
for (Member member : result) {
System.out.println("name: " + member.getName());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
entityManager.close();
}
entityManagerFactory.close();
- JPA를 사용하면 Entity 객체를 중심으로 개발하게 되며 검색도 테이블이 아닌 객체를 대상으로 한다.
- 필요한 데이터만 DB에서 조회하려면 결국 조건이 포함된 SQL이 필요한데 쿼리를 쓰면 SQL에 종속되는 문제가 발생한다. 이때 객체를 대상으로 검색할 수 있게 하는 기술이 JPQL이다.
- JPQL은 JPA가 제공하는 SQL을 추상화한 객체 지향 쿼리 언어로 추상화했기 때문에 특정 DB에 의존하지 않으며 SQL과 문법이 유사하다.(select, from, where, group by, having, join 을 지원한다)
영속성 컨텍스트
가장 신기했던 부분이다. 간단하게만 말하자면 저장, 업데이트등의 작업이 일어날 때 DB에 직접적으로 바로 저장하는 것이 아닌 영속성 컨텍스트에 저장하고 commit을 하면 DB에 쿼리가 날아가며 저장된다. 일종의 트랜잭션과 같다고 느껴졌다.
영속성 컨텍스트란?
- Entity를 영구 저장하는 환경으로 논리적인 개념이라 눈에 보이지는 않으며 EntityManager를 통해 접근한다.
- Entity를 DB에 저장하는 코드로 Entity를 영속화 한다는 의미
- Entity를 영속화한다는 것은 영속성 컨텍스트에 저장한다는 의미EntityManager.persist(member)
- EntityManager를 생성하면 그 안에 1:1로 영속성 컨텍스트라는 눈에 보이지 않는 공간이 생기는 개념이다.
비영속
- 최초에 객체를 생성한 상태로 아직 영속성 컨텍스트에 들어가지 않은 상태이다.
- 단순히 Member member = new Member(~~~); 한 것과 같은 상태이다.
영속
- persist()한 상태로 영속성 컨텍스트에서 관리되는 상태이다.
- 여기서 commit()을 하면 DB에 반영된다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
Member member = new Member("test");
em.persist(member);
tx.commit();
- 위의 코드에서 tx.commit()을 해야 반영되는 것이다.
영속성 컨텍스트의 이점
1차캐시
- 1차 캐시라는 캐시와 같은 역할로 처음에 persist()로 영속성 컨텍스트에 저장했다면 이후 commit()전에 조회를하면 1차 캐시에 저장된 객체를 읽는 장점이 있다.
Member member = new Member("test");
entityManager.persist(member);
Member cacheMember = em.find(Member.class, "test");
- 위의 cacheMember는 DB대신 영속성 컨텍스트의 1차 캐시에서 읽어온다. 사실상 영속성 컨텍스트라고 생각해도 된다.
- 성능상 큰 이점은 없다.
- EntityManager는 트랜잭션 단위로 만들고 트랜잭션이 종료되면 삭제되는데 이때 1차 캐시도 함께 날아간다.
동일성 보장
- 자바 컬렉션에서 꺼낸 데이터는 레퍼런스가 같은데 JPA도 이와 같은 동일성을 보장해준다.
- 위의 출력 결과는 true가 나온다는 것이다
- Member member1 = em.find(Member.class, "member1"); Member member2 = em.find(Member.class, "member2"); System.out.println(member1 == member2);
Member member1 = em.find(Member.class, "member1");
Member member2 = em.find(Member.class, "member2");
System.out.println(member1 == member2);
트랜잭션을 지원하는 쓰기 지원
- persist()한다고 바로 DB에 저장하는 것이 아니라 JPA가 메모리에 쿼리를 쌓고 있다가 트랜잭션을 커밋하면 DB에 한 번에 보내는 것이다.
- 영속성 컨텍스트에는 쓰기 지연 SQL 저장소가 있다.
- memberA를 persist하면 1차 캐시에 저장되고 JPA가 Entity를 분석해 insert쿼리를 생성만한다. 마찬가지로 memberB를 persist해도 memberA와 같은 일이 발생한다.
- 위와 같이 commit()을 하면 쓰기 지연 SQL 저장소에 만들어 뒀던 쿼리들을 DB에 보내는 것이다.
em.persist(memberA);
em.persist(memberB);
System.out.println("================");
tx.commit();
- 대충 위와 같이 코드를 작성하고 실행하면 쿼리문이 먼저 나가는 것이 아닌 println이 먼저 실행되어 선이 출력된 후 쿼리문이 날아간다.
변경 감지(더티 체킹)
이게 내가 아까 말했던 변경을 감지해서 자동으로 update쿼리를 날려주는 내용이다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
Member member = em.find(Member.class, "member");
member.setUsername("test");
tx.commit();
- DB입장에서 생각하면 commit()전에 em.update(member)와 같은 코드가 있어야 업데이트가 될 것 같지만 그렇지 않다.
- JPA는 자바 컬렉션에 넣은 것처럼 값을 다루는게 목적이고 컬렉션에서 꺼낸 값을 변경했다고 다시 컬렉션에 넣지 않는 것과 같다.
- JPA의 경우 1차 캐시에 스냅샷이라는 것이 있는데 여기에 최초로 영속성 컨텍스트에 들어온 상태를 저장해 두는 것이다.
- 이후 commit()할 때 내부적으로 flush()되면서 Entity와 스냅샷을 비교하고 바뀐 내용이 있다면 update쿼리를 생성한다. 이후 쿼리를 쓰기 지연 SQL 저장소에 저장한 뒤 DB에 반영한다.
- 삭제는 remove(member)와 같이 사용하면 되며 다른 변경들과 같이 쓰기 지연 저장소에 쿼리를 모았다가 commit()시에 실행한다.
플러시(flush)
- 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것으로 변경을 감지하고 수정된 Entity를 쓰기 지연 SQL 저장소에 등록하며 쿼리를 DB에 전송한다.
- commit()을 하면 플러시가 자동으로 발생한다.
- 직접 호출하여 플러시를 할 수도 있다.
- em.flush()
- 자동으로 호출되는 경우는
- 트랜잭션 커밋
- JPQL 쿼리 실행이 있다.
- 자동으로 호출되는 것을 원치 않아 쿼리를 미리 반영하고 싶다면 커밋 전에 강제로 flush()를 호출하여 사용할 수 있다. 여기서 flush()를 해도 1차 캐시는 유지된다.
- 1차 캐시와는 상관 없이 쓰기 지연 SQL 저장소에 쌓인 쿼리(INSERT, UPDATE, DELETE SQL들)나 변경 감지한 내용이 DB에 반영되는 것이다.
- JPQL 실행 시에는 자동으로 무조건 flush()를 날린다.
- 정리하자면 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업으로(영속성 컨텍스트를 비우는 개념이 아니다) 쉽게 말하면 영속성 컨텍스트의 변경 사항과 DB의 상태를 똑같이 맞추는 작업이다. 트랜잭션이라는 개념이 있기에 동작 가능한 매커니즘이다. 트랜잭션 작업 단위로 동작하기 떄문에 커밋 직전에만 변경 내역을 DB에 보내주면 된다.
- 동작 과정
- 변경을 감지한다
- 수정된 Entity를 쓰기 지연 SQL 저장소에 등록한다
- 쓰기 지연 SQL 저장소의 쿼리를 DB에 보낸다.
- 이때 플러시가 발생했다고 commit이 발생하는 것이 아니라 flush 다음에 실제 commit이 일어난다.
준영속 상태
- 영속은 영속성 컨텍스트에서 관리되는 상태로 insert뿐만 아니라 조회 시 1차 캐시에 없어서 DB에서 가져와 1차 캐시에 올리는 상태도 포함된다.
- 준영속은 영속 상태의 Entity가 영속성 컨텍스트에서 분리되는 상태를 말한다. detach() 메소드를 실행하면 트랜잭션을 커밋해도 영향을 받지 않는다. 즉 영속성 컨텍스트가 제공하는 기능을 사용할 수 없음을 의미한다.
- em.detach(member)
- 특정 Entity를 준영속 상태로 전환한다. 지금은 member Entity를 준영속 상태로 전환하는 것이다.
- em.clear()
- 영속성 컨텍스트를 통으로 지운다. 즉 EntityManager로 작업한 것들을 모두 지우는 것이다.
- 1차 캐시 등도 모두 사라지기 때문에 같은 데이터를 조회해도 캐시가 아닌 DB에서 다시 가져온다.
- em.close()
- 영속성 컨텍스트를 종료한다.
여태 spring-boot-starter-data-jpa 가 JPA의 전부인 줄 알았지만 아니었다. Java에서 대표적인 ORM이 JPA이고(표준이 되었음) 그 구현체 Hibernate가 있는 것과 객체는 객체답게, RDB는 RDB답게 설계하면 ORM 프레임워크가 자동으로 매핑해주는 것, DB에 종속적이지 않은 것 등은 알고 있었다. 그래서 JPA가 뭔지 더 찾아보니 기존에 내가 사용하던 것들의 원리를 알 수 있었다.
JPA의 원리
JPA 동작
JPA는 애플리케이션과 JDBC 사이에서 동작한다. 애플리케이션에서 JPA로 명령을 하면 JPA가 해석해서 JDBC API를 사용하여 SQL을 실행하고 그 결과를 반환받는다. 그래서 findAll()과 같은 자바스러운 메소드를 사용해도 변환해서 SELECT * ~~ 와 같은 쿼리가 발생하는 것이다.
MyBatis를 사용해 보았으면 알겠지만 쿼리문을 하나하나 작성하면 엄청 힘들다. 이렇게 SQL중심적이 아닌 객체 중심적으로 개발할 수 있는 것이 엄청 큰 장점이라 생각한다.
findAll과 같은 메소드는 spring-boot-starter-data-jpa이다. 실제 jpa는 아래와 같은 메소드로 CRUD 작업을 한다.
- 저장 : jpa.persist(member)
- 조회 : Member member = jpa.find(memberId)
- 수정 : member.setName(”변경할 이름”)
- 삭제 : jpa.remove(member)
원래 알던 모습과는 다르지만 CRUD에 대한 SQL작업은 필요 없고 만들어져 있는 메소드를 사용하는 것은 똑같다. 특히 수정은 내가 원하는 값만 넣으면 DB로 수정하는 쿼리를 보낸다. 기존에 사용할 때도 객체를 변경하고 save()만 호출해도 수정 쿼리가 나간 것 처럼 똑같다. 이것도 나중에 이유를 알 수 있었다.
JPA 구동 방식
JPA의 Persistence 클래스가 META-INF/persistence.xml 설정 파일을 읽어서 EntityManagerFactory 라는 클래스를 생성한다. 여기서 필요할 때마다 EntityManager를 만들어서 사용한다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
Member member = entityManager.find(Member.class, 1L);
member.setName("helloJPA");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
entityManager.close();
}
entityManagerFactory.close();
- 업데이트 쿼리를 따로 작성하지 않아도 member객체의 값이 변경되어 자동으로 updqte 쿼리가 나간다.
- 주의할 점
- EntityManagerFactory
- 애플리케이션 실행 시점에 하나만 생성해서 애플리케이션 전체에 걸쳐 공유한다
- EntityManager
- 요청이 왔을 때 썼다가 끝나면 버리는 사이클을 반복한다. → 절대 스레드끼리 공유하면 안된다.
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
- 트랜잭션을 실제로 지정하여 사용해보진 않았지만 DB에서 자체적으로 사용하기 때문에 적용해왔을 것이다.
- EntityManagerFactory
JPQL
- entityManager.find()는 복잡하지 않고 단순한 조회만 할 수 있다. 복잡한 조건이 들어간다면 JPQL을 사용해야 한다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
List<Member> result = entityManager
.createQuery("select m from Member as m", Member.class)
// 1번부터 10개 가져오는 설정
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
for (Member member : result) {
System.out.println("name: " + member.getName());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
entityManager.close();
}
entityManagerFactory.close();
- JPA를 사용하면 Entity 객체를 중심으로 개발하게 되며 검색도 테이블이 아닌 객체를 대상으로 한다.
- 필요한 데이터만 DB에서 조회하려면 결국 조건이 포함된 SQL이 필요한데 쿼리를 쓰면 SQL에 종속되는 문제가 발생한다. 이때 객체를 대상으로 검색할 수 있게 하는 기술이 JPQL이다.
- JPQL은 JPA가 제공하는 SQL을 추상화한 객체 지향 쿼리 언어로 추상화했기 때문에 특정 DB에 의존하지 않으며 SQL과 문법이 유사하다.(select, from, where, group by, having, join 을 지원한다)
영속성 컨텍스트
가장 신기했던 부분이다. 간단하게만 말하자면 저장, 업데이트등의 작업이 일어날 때 DB에 직접적으로 바로 저장하는 것이 아닌 영속성 컨텍스트에 저장하고 commit을 하면 DB에 쿼리가 날아가며 저장된다. 일종의 트랜잭션과 같다고 느껴졌다.
영속성 컨텍스트란?
- Entity를 영구 저장하는 환경으로 논리적인 개념이라 눈에 보이지는 않으며 EntityManager를 통해 접근한다.
- Entity를 DB에 저장하는 코드로 Entity를 영속화 한다는 의미
- Entity를 영속화한다는 것은 영속성 컨텍스트에 저장한다는 의미EntityManager.persist(member)
- EntityManager를 생성하면 그 안에 1:1로 영속성 컨텍스트라는 눈에 보이지 않는 공간이 생기는 개념이다.
비영속
- 최초에 객체를 생성한 상태로 아직 영속성 컨텍스트에 들어가지 않은 상태이다.
- 단순히 Member member = new Member(~~~); 한 것과 같은 상태이다.
영속
- persist()한 상태로 영속성 컨텍스트에서 관리되는 상태이다.
- 여기서 commit()을 하면 DB에 반영된다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
Member member = new Member("test");
em.persist(member);
tx.commit();
- 위의 코드에서 tx.commit()을 해야 반영되는 것이다.
영속성 컨텍스트의 이점
1차캐시
- 1차 캐시라는 캐시와 같은 역할로 처음에 persist()로 영속성 컨텍스트에 저장했다면 이후 commit()전에 조회를하면 1차 캐시에 저장된 객체를 읽는 장점이 있다.
Member member = new Member("test");
entityManager.persist(member);
Member cacheMember = em.find(Member.class, "test");
- 위의 cacheMember는 DB대신 영속성 컨텍스트의 1차 캐시에서 읽어온다. 사실상 영속성 컨텍스트라고 생각해도 된다.
- 성능상 큰 이점은 없다.
- EntityManager는 트랜잭션 단위로 만들고 트랜잭션이 종료되면 삭제되는데 이때 1차 캐시도 함께 날아간다.
동일성 보장
- 자바 컬렉션에서 꺼낸 데이터는 레퍼런스가 같은데 JPA도 이와 같은 동일성을 보장해준다.
Member member1 = em.find(Member.class, "member1");
Member member2 = em.find(Member.class, "member2");
System.out.println(member1 == member2);
- 위의 출력 결과는 true가 나온다는 것이다
트랜잭션을 지원하는 쓰기 지원
- persist()한다고 바로 DB에 저장하는 것이 아니라 JPA가 메모리에 쿼리를 쌓고 있다가 트랜잭션을 커밋하면 DB에 한 번에 보내는 것이다.
- 영속성 컨텍스트에는 쓰기 지연 SQL 저장소가 있다.
- memberA를 persist하면 1차 캐시에 저장되고 JPA가 Entity를 분석해 insert쿼리를 생성만한다. 마찬가지로 memberB를 persist해도 memberA와 같은 일이 발생한다.
- 위와 같이 commit()을 하면 쓰기 지연 SQL 저장소에 만들어 뒀던 쿼리들을 DB에 보내는 것이다.
em.persist(memberA);
em.persist(memberB);
System.out.println("================");
tx.commit();
- 대충 위와 같이 코드를 작성하고 실행하면 쿼리문이 먼저 나가는 것이 아닌 println()이 먼저 실행되어 선이 출력된 후 쿼리문이 날아간다.
변경 감지(더티 체킹)
이게 내가 아까 말했던 변경을 감지해서 자동으로 update쿼리를 날려주는 내용이다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test");
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
Member member = em.find(Member.class, "member");
member.setUsername("test");
tx.commit();
- DB입장에서 생각하면 commit()전에 em.update(member)와 같은 코드가 있어야 업데이트가 될 것 같지만 그렇지 않다.
- JPA는 자바 컬렉션에 넣은 것처럼 값을 다루는게 목적이고 컬렉션에서 꺼낸 값을 변경했다고 다시 컬렉션에 넣지 않는 것과 같다.
- JPA의 경우 1차 캐시에 스냅샷이라는 것이 있는데 여기에 최초로 영속성 컨텍스트에 들어온 상태를 저장해 두는 것이다.
- 이후 commit()할 때 내부적으로 flush()되면서 Entity와 스냅샷을 비교하고 바뀐 내용이 있다면 update쿼리를 생성한다. 이후 쿼리를 쓰기 지연 SQL 저장소에 저장한 뒤 DB에 반영한다.
- 삭제는 remove(member)와 같이 사용하면 되며 다른 변경들과 같이 쓰기 지연 저장소에 쿼리를 모았다가 commit()시에 실행한다.
플러시(flush)
- 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것으로 변경을 감지하고 수정된 Entity를 쓰기 지연 SQL 저장소에 등록하며 쿼리를 DB에 전송한다.
- commit()을 하면 플러시가 자동으로 발생한다.
- 직접 호출하여 플러시를 할 수도 있다.
- em.flush()
- 자동으로 호출되는 경우는
- 트랜잭션 커밋
- JPQL 쿼리 실행이 있다.
- 자동으로 호출되는 것을 원치 않아 쿼리를 미리 반영하고 싶다면 커밋 전에 강제로 flush()를 호출하여 사용할 수 있다. 여기서 flush()를 해도 1차 캐시는 유지된다.
- 1차 캐시와는 상관 없이 쓰기 지연 SQL 저장소에 쌓인 쿼리(INSERT, UPDATE, DELETE SQL들)나 변경 감지한 내용이 DB에 반영되는 것이다.
- JPQL 실행 시에는 자동으로 무조건 flush()를 날린다.
- 정리하자면 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업으로(영속성 컨텍스트를 비우는 개념이 아니다) 쉽게 말하면 영속성 컨텍스트의 변경 사항과 DB의 상태를 똑같이 맞추는 작업이다. 트랜잭션이라는 개념이 있기에 동작 가능한 매커니즘으로 트랜잭션 작업 단위로 동작하기 떄문에 커밋 직전에만 변경 내역을 DB에 보내주면 된다.
- 동작 과정
- 변경을 감지한다
- 수정된 Entity를 쓰기 지연 SQL 저장소에 등록한다
- 쓰기 지연 SQL 저장소의 쿼리를 DB에 보낸다.
- 이때 플러시가 발생했다고 commit이 발생하는 것이 아니라 flush 다음에 실제 commit이 일어난다.
준영속 상태
- 영속은 영속성 컨텍스트에서 관리되는 상태로 insert뿐만 아니라 조회 시 1차 캐시에 없어서 DB에서 가져와 1차 캐시에 올리는 상태도 포함된다.
- 준영속은 영속 상태의 Entity가 영속성 컨텍스트에서 분리되는 상태를 말한다. detach() 메소드를 실행하면 트랜잭션을 커밋해도 영향을 받지 않는다. 즉 영속성 컨텍스트가 제공하는 기능을 사용할 수 없음을 의미한다.
- em.detach(member)
- 특정 Entity를 준영속 상태로 전환한다. 지금은 member Entity를 준영속 상태로 전환하는 것이다.
- em.clear()
- 영속성 컨텍스트를 통으로 지운다. 즉 EntityManager로 작업한 것들을 모두 지우는 것이다.
- 1차 캐시 등도 모두 사라지기 때문에 같은 데이터를 조회해도 캐시가 아닌 DB에서 다시 가져온다.
- em.close()
- 영속성 컨텍스트를 종료한다.