Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간이다.
EntityManager는 이름 그대로 Entity를 관리하는 관리자입니다.
- **'1차 캐시'** 사용의 장점
1. DB 조회 횟수를 줄임
2. **'1차 캐시'**를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)
- 객체 동일성 보장
```sql
@Test
@DisplayName("객체 동일성 보장")
void test4() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
Memo memo3 = new Memo();
memo3.setId(2L);
memo3.setUsername("Robbert");
memo3.setContents("객체 동일성 보장");
em.persist(memo3);
Memo memo1 = em.find(Memo.class, 1);
Memo memo2 = em.find(Memo.class, 1);
Memo memo = em.find(Memo.class, 2);
System.out.println(memo1 == memo2);
System.out.println(memo1 == memo);
et.commit();
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
```
- 같은 값을 조회하는 memo1과 memo2는 == 결과 true를 반환합니다.
- memo1과 다른 값을 조회하는 memo는 == 결과 false를 반환합니다.
<aside>
💡
보통 Java에서 == 표시로 객체를 비교하면 주소가 다르다고 판단해 false가 나오는데?
find로 1번을 가져오면 1차 캐시에 있는 값을 동일하게 받아와 동일하게 판단한다.
객체가 한개당 하나라고 인정을 해준다.
</aside>
Ex. EntityManager로 transaction 환경을 만들었다는 가정.
JPA는 트랜잭션처럼 SQL을 모아서 한번에 DB에 반영하는데 JPA는 이를 구현하기 위해 쓰기 지연 저장소를 만들어 SQL을 모아두고 있다가 트랜잭션 commit 후 한번에 DB에 반영합니다.
쓰기 지연이 발생하는 시점
쓰기 지연 효과
여러개의 객체를 생성할 경우 모아서 한번에 쿼리를 전송한다.
영속성 상태의 객체가 생성 및 수정이 여러번 일어나더라도 해당 트랜잭션 종료시 쿼리는 1번만 전송될 수 있다.
영속성 상태에서 객체가 생성되었다 삭제되었다면 실제 DB에는 아무 동작이 전송되지 않을 수 있다.
즉, 여러가지 동작이 많이 발생하더라도 쿼리는 트랜잭션당 최적화 되어 최소쿼리만 날라가게된다.
💁♂️ 키 생성전략이 `generationType.IDENTITY` 로 설정 되어있는 경우 생성쿼리는 쓰기지연이 발생하지 못한다.why? 단일 쿼리로 수행함으로써 외부 트랜잭션에 의한 중복키 생성을 방지하여 단일키를 보장한다.
트랜잭션 commit 후 쓰기 지연 저장소의 SQL들이 한번에 요청됨을 확인했습니다.
사실 트랜잭션 commit 후 추가적인 동작이 있는데 바로 em.flush(); 메서드의 호출입니다.
flush 메서드는 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행합니다.
flush() 동작 확인을 위해 직접 호출해보겠습니다.
@Test
@DisplayName("flush() 메서드 확인")
void test7() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
Memo memo = new Memo();
memo.setId(4L);
memo.setUsername("Flush");
memo.setContents("Flush() 메서드 호출");
em.persist(memo);
System.out.println("flush() 전");
em.flush(); // flush() 직접 호출
System.out.println("flush() 후\n");
System.out.println("트랜잭션 commit 전");
et.commit();
System.out.println("트랜잭션 commit 후");
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
💡
flush 메서드가 호출되었을때 "flush() 후" 바로 전에 쿼리가 날라간다.
commit 메서드 호출해도 쿼리가 날라가지 않는다.
em.flush(); 메서드가 호출되자 바로 DB에 쓰기 지연 저장소의 SQL이 요청되었습니다.flush()는 데이터베이스에 변경된 객체의 상태를 동기화하는 작업으로, 아직 트랜잭션이 완료되기 전에 메모리 내의 객체를 데이터베이스와 일치시키는 역할을 합니다.
즉, 변경된 객체가 데이터베이스에 반영되고 나서, commit()이 호출되어 트랜잭션이 완전히 저장(persist)됩니다.
따라서 `commit()`을 호출하면:
1. *flush()**가 먼저 실행되어 변경 사항이 데이터베이스에 기록됩니다.(SQL 쿼리 날라간다.)
2. 그런 다음 **commit()**이 호출되어 트랜잭션을 확정하고, 이를 통해 변경 사항이 영구적으로 저장됩니다.
추가) 트랜잭션을 설정하지 않고 flush 메서드를 호출하면 no transaction is in progress 메시지와 함께 TransactionRequiredException 오류가 발생합니다.
(Select는 상관없다. ⇒ 필수는 아니지만 필요할 때가 있다.)
필요할때는 FetchType이 Lazy이고 Select 할때 Transasctional 환경이 필요하다.
em.update(entity); 같은 메서드를 지원할 것 같지만 찾아볼 수 없습니다.em.flush(); 가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교합니다.@Test
@DisplayName("변경 감지 확인")
void test8() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
System.out.println("변경할 데이터를 조회합니다.");
Memo memo = em.find(Memo.class, 4);
System.out.println("memo.getId() = " + memo.getId());
System.out.println("memo.getUsername() = " + memo.getUsername());
System.out.println("memo.getContents() = " + memo.getContents());
System.out.println("\n수정을 진행합니다.");
memo.setUsername("Update");
memo.setContents("변경 감지 확인");
System.out.println("flush 전");
em.flush();
System.out.println("flush 후");
System.out.println("트랜잭션 commit 전");
et.commit();
System.out.println("트랜잭션 commit 후");
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
변경할 데이터를 조회합니다.
Hibernate:
select
m1_0.id,
m1_0.contents,
m1_0.username
from
memo m1_0
where
m1_0.id=?
memo.getId() = 6
memo.getUsername() = Tw4YomulpY
memo.getContents() = 메모1
수정을 진행합니다.
flush 전
Hibernate:
/* update
com.sparta.entity.Memo */ update memo
set
contents=?,
username=?
where
id=?
flush 후
트랜잭션 commit 전
트랜잭션 commit 후
JPA는 연관관계가 설정된 Entity의 정보를 바로 가져올지, 필요할 때 가져올지 정할 수 있습니다.
*LAZY*, 다른 하나는 *EAGER 입니다.*LAZY는 지연 로딩으로 필요한 시점에 정보를 가져옵니다.*EAGER는 즉시 로딩으로 이름의 뜻처럼 조회할 때 연관된 모든 Entity의 정보를 즉시 가져옵니다.*기본적으로 @OneToMany 애너테이션은 Fetch Type의 default 값이 LAZY로 지정되어있고 반대로 @ManyToOne 애너테이션은 *EAGER로 되어있습니다.*
다른 연관관계 어노테이션들도 default 값이 있는데 이를 구분하는 방법이 있습니다.
지연 로딩이 default로 설정되어있습니다.즉시 로딩이 default로 설정되어있습니다.지연 로딩된 Entity의 정보를 조회하려고 할 때는 반드시 영속성 컨텍스트가 존재해야합니다.
‘영속성 컨텍스트가 존재해야한다’라는 의미는 결국 SpringBoot에서 ‘트랜잭션이 적용되어있어야 한다’라는 의미와 동일합니다.