- EntityManager 기본설정
- 비영속상태
- 준영속상태
- 객체 영속화
- 트랜잭션 커밋
- 영속객체조회
- 쓰기지연
- 변경감지
- 플러쉬
EntityManager기본설정
[persistence.xml]
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="jpabasic">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=" "/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/jpabasic"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
EntityManagerFactory를 통해 등록한 Persistence Unit 이름을 지정해준다.
public class JpaExampleMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabasic");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
//로직 시작 - 이곳에 jpa 코드 작성
//로직 종료
tx.commit(); //트랜잭션 커밋
} catch (Exception e) {
tx.rollback(); // 데이터 저장시 문제 발생하면 트랜잭션을 롤백한다.
} finally {
em.close(); // 엔티티매니저 닫기.(내부적으로 DB 커넥션을 물고 동작하므로 사용후에는 꼭 닫아줘야한다)
}
emf.close(); //전체 애플리케이션이 종료되면 EntityManagerFactory도 닫아줘야한다.
}
}
만약 원하는 엔터티만 인식하게 하고 싶다면 <persistence-unit>태그 하위에 아래와 같이 선언해준다.
<persistence-unit name="jpqlbasic">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<mapping-file>META-INF/ormMember.xml</mapping-file>
<class>패키지명.엔터티1</class>
<class>패키지명.엔터티2</class>
<class>패키지명.엔터티3</class>
<class>패키지명.엔터티4</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<!-- 중략... -->
</properties>
</persistence-unit>
또한 persistence-unit은 아래와 같이 여러개 지정이 가능하다.
<persistence-unit name="jpajpql">
<properties>
<!-- 필수 속성 생략 -->
</properties>
</persistence-unit>
<persistence-unit name="jpapractice">
<properties>
<!-- 필수 속성 생략 -->
</properties>
</persistence-unit>
위와 같이 여러개를 지정하면 하나의 프로젝트에서 각각 다른 엔티티 매니저를 생성해 낸 뒤 엔티티를 상위 패키지 별로 분리하여 관리할 수 있고 DB 스키마를 따로 관리할 수 있다.
비영속상태
- 엔티티 객체를 단순히 생성만 한 상태
Member member = new Member();
member.setId(1L);
member.setName("firstMember");
준영속 상태
- member 엔티티 객체를 영속성 컨텍스트에서 분리한다.
영속 상태의 엔터티가 영속성 컨텍스트에 분리됨으로써 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.(영속성 컨텍스트에 의해 엔터티 객체를 더이상 관리하지 않게된다.)
- em.detach(Entity객체) : 특정 엔터티만 준영속 상태로 만든다.
(영속성 컨텍스트 1차 캐시로부터 해당 영속객체를 삭제한다.) - em.clear() : 엔티티 매니저 안에 있는 영속성 컨텍스트를 통채로 초기화 해버린다.
- em.close() : 영속성 컨텍스트를 종료한다.
객체 영속화
- 생성한 member 엔티티 객체를 영속성 컨텍스트에 저장한 상태 (아직 DB에 저장되지는 않는다.)
em.persist(member)
트랜잭션 커밋
- 트랜잭션이 커밋되는 시점에 영속성 컨텍스트에 등록된 객체에 대해 DB에 insert 쿼리를 날린다.
tx.commit
영속객체 조회
- 영속성 컨텍스트로부터 member를 조회
Member findMember = em.find(Member.class, 1L);
영속성 컨텍스트는 내부에 1차캐시라는것을 갖고있다.
위 비영속 상태의 엔티티 객체를 영속시키는 순간
쓰기지연
- 영속성 컨텍스트 1차캐시에 영속화만 진행한 상태
em.persist(memberA);
em.persist(memberB);
쓰기지연의 이유 : 버퍼링 - 쿼리가 작동하기 전까지 최적화 여지
[persist.xml] 쓰기지연의 쿼리 범위를 설정한다.
<property name="hibernate.jdbc.batch_size" value="10"/>
변경 감지
<자바 컬렉션>
도메인 객체A의 값을 세팅하고 Map컬렉션에 담은 뒤 Map컬렉션에서 도메인 객체B의 값을 불러와 수정한다.
저장하는 변수의 위치는 다르지만, 같은 주소를 참조하고 있으므로, A와 B는 값을 공유한다.
Diet diet = new Diet(); //1차 다이어트 생성
diet.setUserId("1234"); // 1차 다이어트 값 저장
Map<String, Diet> dMap = new HashMap<>();
dMap.put("hi", diet); //1차 다이어트 맵에 저장
System.out.println("dMap = " + dMap);
Diet hiDiet = dMap.get("hi"); //Map에서 1차다이어트 반환
System.out.println(hiDiet);
hiDiet.setUserId("bye"); //반환받은 hiDiet
System.out.println("diet = " + diet);
System.out.println(diet == hiDiet);
- 트랜잭션이 commit되는 시점에 내부적으로 flush()를 호출한다.
(flush가 호출되는 순간 Entity와 스냅샷을 비교함.) - flush 동작 : Entity와 스냅샷을 비교
(1차캐시에는 pk, Entity, 스냅샷이 존재한다.
값을 영속성컨텍스트에 등록하는 최초의 시점 상태를 스냅샷으로 찍고 1차캐시에 저장한다.) - 변경된 값을 감지한 뒤 Update쿼리를 쓰기지연 SQL저장소에 저장한다.
위 하나의 과정이 flush가 종료되기 전에 발생하고, flush의 과정중 결과적으로 update쿼리를 날린 후 비로서 커밋까지 완료
플러쉬 (flush)
영속성 컨텍스트의 변경내용을 데이터에베이스에 반영
트랜잭션 커밋 시점에 플러쉬가 발생.
플러쉬는 쓰기지연 SQL에 쌓여있는 insert, update, delete등의 SQL이 실제로 호출되는것.
영속성 컨텍스트의 현재 변경사항과 데이터베이스를 맞추는 작업.
플러쉬 방법
- em.flush();
- 트랜잭션 커밋 (flush 자동 호출)
- JPQL 쿼리 실행 (flush 자동 호출)
예시 1) 직접 호출 : 영속 객체를 생성하고 값을 세팅한 뒤 예를들어 트랜잭션이 커밋되는 시점에 자동으로 flush호출되기 전에 직접 쿼리를 조회해보고 싶을때 미리 em.flush()를 강제로 호출할 수 있다.
트랜잭션 시점까지 기다리지 않고 쿼리를 바로 호출 -> 여러 데이터 갱신 과정 중 특정 시점에 쓰기지연저장소의 쿼리를 호출하고자 하는경우
flush를 한 뒤에는 쓰기지연 SQL에 존재하는 SQL들을 호출할 뿐, 1차 캐시에는 영향을 주지 않는다.
예시 3) JPQL실행에 의한 호출 : em.persist() 즉, 서로다른 영속객체를 컨텍스트에 등록하는 작업을 3번 진행했다고 가정한다.
따로 flush를 호출하지 않고 JQPL을 통해 SELECT문을 호출한다면 기본적인 메커니즘상의 이해로는 조회되지 않아야 정상이다.
이유는 find메소드는 1차캐시에서 조회하려는 값을 조회하고 있으면 1차캐시에서 반환하고 1차캐시에 존재하지 않는다면 SELECT쿼리를 날리게 되는데,
애초에 JPQL은 1차캐시에 접근하지 않고 바로 SELECT SQL 쿼리를 날려버리는 작업이기 때문이다.
따라서 JPA에서는 그러한 잘못된 현상을 발생하지 않게하기 위해 JPQL이 실행되면 무조건 Flush를 한번 호출한 뒤 쿼리를 날린다.
정리
- flush는 영속성 컨텍스트를 비우는것이 아니다.
- flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는것이다.
- 트랜잭션이라는 작업 단위가 중요하다 (커밋 직전에만 동기화 하면 된다.)
'Hibernate > JPA(EntityManager)' 카테고리의 다른 글
[JPA] UPDATE 변경감지와 병합 (DirtyChecking / Merge) (0) | 2023.07.14 |
---|---|
[JPA] 영속성 전이와 고아객체 (CASCADE, orphaRemoval) (0) | 2023.07.13 |
[JPA] 즉시로딩/지연로딩(Eager/Lazy) 과 JPQL N+1 이슈 (0) | 2023.07.13 |
[JPA] JPA에서 Proxy 객체란? (0) | 2023.07.13 |
[JPA] 상속 관계 매핑 @Inheritance , @Discriminator____ (0) | 2023.06.30 |