JPA는 객체와 DB를 매핑하기 위한 표준 인터페이스이고,
Hibernate는 JPA 표준을 구현한 구현체로서 실제로 SQL 생성, 영속성 컨텍스트 관리, DB 접근을 수행한다.
Spring Boot에서는 Hibernate를 JPA 구현체로 사용한다.
참고로,
- 라이브러리는 개발자가 필요할 때 직접 호출해서 사용하는 클래스나 메서드와 같은 기능 모음이다.
- 프레임워크는 애플리케이션의 실행 흐름을 미리 정의해두고, 그 흐름 안에서 개발자가 작성한 코드를 호출하는 틀이다.
- Spring은 애플리케이션 전체의 실행 흐름을 관리하는 프레임워크이다.
- JUnit은 테스트 실행 흐름을 관리하는 테스트 프레임워크이다.
영속성 컨텍스트의 1차 캐시에 저장된 엔티티의 초기 상태
(em.find()를 호출하면, db에서 조회된 엔티티의 상태를 말하고, em.persist()를 호출하면 새로 생성된 엔티티의 상태를 말한다.)
애플리케이션 내부에서 commit(), flush()메서드를 호출하는것와 db에 flush, commit되는것은 약간의 차이가 있다.
트랜잭션을 커밋하면 전체적으로 2~6번의 과정이 진행되고, flush()가 호출되면 위의 3~5번의 과정이 내부적으로 진행된다.
즉, 트랜잭션을 커밋하면 내부적으로 flush()가 호출되고, flush()가 끝난 후 데이터베이스에 트랜잭션이 커밋된다.
그리고 flush()와 flush의 차이점은 이름은 둘다 flush로 동일하지만, flush()는 엔티티매니저의 메서드 이름이고, flush는 flush()를 호출할때 내부적으로 진행되는 영속성 컨텍스트 내의 동작 중 쓰기 지연 저장소에 있는 쿼리를 모아 DB로 전송하는 동작 이름이다.
flush()를 호출하는것은 영속성컨텍스트의 변경내용을 데이터베이스에 반영해서 데이터베이스와 동기화 하는작업이고, 이것은 3~5번의 과정을 말한다.
flush() = 3~5번의 작업
flush = 5번 작업
영속성 컨텍스트의 변경 내용
- 1차캐시에 스냅샷과 비교해 변경된 엔티티의 필드값
- 1차캐시에 새로 추가된 엔티티
- 1차캐시에서 삭제된 엔티티
참고로 1차캐시에 있는 엔티티는 무조건 영속상태이다.
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();
<IDENTITY 전략일 때 insert>
1. em.persist()를 하면, 즉시 INSERT SQL을 생성해서 실행한다.
2. INSERT SQL 실행 후 데이터베이스에 의해 생성된 pk가 반환되고, 반환된 pk값이 엔티티의 @Id 필드의 값으로 설정된다. 그리고 이 pk값이 1차캐시의 key로 사용되고, 해당 엔티티가 1차캐시에 추가된다.
(em.persist()후에는 엔티티의 @Id필드의 값이 설정되므로, 해당 값을 출력해보면 값이 있다.)
3. 트랜잭션 커밋(commit())
4. 엔티티 매니저의 flush()가 호출되어 쓰기지연SQL저장소에 있는 쿼리를 데이터베이스에 전송해야하는데, 이미 INSERT SQL을 실행했으므로 쓰기지연SQL저장소에는 쿼리가 없다.
5. 데이터베이스에 트랜잭션이 커밋된다.
참고로 , 1차캐시의 key(=1차캐시의 @Id컬럼)의 값과 엔티티의 @Id로 설정된 필드의 값은 동일하다.엔티티의 @Id로 설정된 값이 1차 캐시의 Key로 사용된다. 1차 캐시의 @Id 컬럼과 엔티티의 @Id 필드는 같은 값이다.
<SEQUENCE 전략일 때 insert> - 참고로 GeneratedValue의 기본값은 AUTO인데, h2데이터베이스는 AUTO일때 SEQUENCE 전략을 사용한다.
1. em.persist()를 하면, 시퀀스에 있는 값을 가져와서 엔티티의 @Id 필드의 값으로 설정된다. 시퀀스에 있는 값을 가져와서 사용했기 때문에 DB에 있는 시퀀스의 값이 증가된다.그리고 가져온 값이 1차캐시의 key로 사용되고, 해당 엔티티가 1차 캐시에 추가된다. (em.persist()후에는 엔티티의 @Id필드의 값이 설정되므로, 해당 값을 출력해보면 값이 있다.)
2. INSERT SQL을 생성해서 쓰기지연SQL저장소에 SQL쿼리를 저장한다.
3. 트랜잭션 커밋(commit())
4. JPA는 엔티티트랜잭션에 의해 트랜잭션을 커밋하면, 바로 커밋되는게 아니라, 항상 내부적으로 엔티티매니저의 flush()가 호출된다.
5. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송(=flush)한다.(=쿼리 실행)
6. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송하고나서, 데이터베이스에 트랜잭션이 커밋(commit)된다.
👆IDENTITY전략이든, SEQUENCE전략이든, 1차 캐시에 엔티티를 추가하기 위해서는 Key가 있어야 한다. 즉, @Id 필드의 값이 있어야 한다. 그래서 @Id 필드의 값이 설정된 후에 해당 엔티티가 1차 캐시에 추가된다.
@Entity
@SequenceGenerator(name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ", // 매핑할 데이터베이스 시퀀스 이름
initialValue = 1,
allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
< allocationSize >
1. 처음 em.persist()를 할떄 allocation했던거 만큼의 시퀀스 값을 데이터베이스 시퀀스에서 미리 가져와서 메모리(=애플리케이션 내부)에 시퀀스 값을 저장해놓는다. 이떄 1부터 50의 값이 메모리에 저장된다.
2. 데이터베이스 시퀀스의 값(현재값)은 50까지 줬으니 51로 되어있다.
3. 이후 em.persist()를 하면 메모리에 있는 값(=애플리케이션 내부에 있는 시퀀스 값)이 사용되고, 만약 50까지 다 사용하고 나서 em.persist()를하게 되면 51부터 다시 allocation했던거 만큼의 시퀀스 값을 미리 가져와서 메모리에 값을 저장해놓는다. 이때 51부터 100의 값이 메모리에 저장된다. 데이터베이스 시퀀스의 값(현재값)은 100까지 줬으니 101로 되어있다.