[JPA] 1.entityManager & flush

재우·2025년 7월 29일

JPA

목록 보기
1/11

JPA는 객체와 DB를 매핑하기 위한 표준 인터페이스이고,
Hibernate는 JPA 표준을 구현한 구현체로서 실제로 SQL 생성, 영속성 컨텍스트 관리, DB 접근을 수행한다.
Spring Boot에서는 Hibernate를 JPA 구현체로 사용한다.

참고로,

  • 라이브러리는 개발자가 필요할 때 직접 호출해서 사용하는 클래스나 메서드와 같은 기능 모음이다.
  • 프레임워크는 애플리케이션의 실행 흐름을 미리 정의해두고, 그 흐름 안에서 개발자가 작성한 코드를 호출하는 틀이다.
  • Spring은 애플리케이션 전체의 실행 흐름을 관리하는 프레임워크이다.
  • JUnit은 테스트 실행 흐름을 관리하는 테스트 프레임워크이다.


스냅샷

영속성 컨텍스트의 1차 캐시에 저장된 엔티티의 초기 상태
(em.find()를 호출하면, db에서 조회된 엔티티의 상태를 말하고, em.persist()를 호출하면 새로 생성된 엔티티의 상태를 말한다.)

update

  1. em.find()나, em.persist()로 인해 이미 1차캐시에 있는 엔티티의 필드값 변경
  2. 트랜잭션 커밋(tx.commit())
  3. JPA는 엔티티트랜잭션에 의해 트랜잭션을 커밋하면, 바로 커밋되는게 아니라, 항상 내부적으로 엔티티매니저의 flush()가 호출된다.
  4. 영속성컨텍스트의 엔티티와 스냅샷을 비교한다. (참고로, 영속성 컨텍스트의 1차캐시에 있는 엔티티를 조회해서 필드값을 변경하면 영속성 컨텍스트의 1차캐시에 저장된 엔티티의 필드값이 자동으로 변경되어있다. 그래서 스냅샷과 비교하는것이다.)
  5. UPDATE SQL을 생성해서 쓰기지연SQL저장소에 SQL쿼리를 저장한다.
  6. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송(=flush)한다.(=쿼리 실행)
  7. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송하고나서, 데이터베이스에 트랜잭션이 커밋(commit)된다.

애플리케이션 내부에서 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차캐시에 있는 엔티티는 무조건 영속상태이다.

insert

  1. em.persist()를 하면, 해당 엔티티가 1차캐시에 추가되고, INSERT SQL을 생성해서 쓰기지연SQL저장소에 SQL쿼리를 저장한다.
  2. 트랜잭션 커밋(commit())
  3. JPA는 엔티티트랜잭션에 의해 트랜잭션을 커밋하면, 바로 커밋되는게 아니라, 항상 내부적으로 엔티티매니저의 flush()가 호출된다.
  4. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송(=flush)한다.(=쿼리 실행)
  5. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송하고나서, 데이터베이스에 트랜잭션이 커밋(commit)된다.

delete

  1. em.remove()를 하면, 해당 엔티티가 1차캐시에서 제거되고, DELETE SQL을 생성해서 쓰기지연SQL저장소에 SQL쿼리를 저장한다.
  2. 트랜잭션 커밋(commit())
  3. JPA는 엔티티트랜잭션에 의해 트랜잭션을 커밋하면, 바로 커밋되는게 아니라, 항상 내부적으로 엔티티매니저의 flush()가 호출된다.
  4. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송(=flush)한다.(=쿼리 실행)
  5. 쓰기지연SQL저장소에있는 쿼리를 데이터베이스에 전송하고나서, 데이터베이스에 트랜잭션이 커밋(commit)된다.


영속성 컨텍스트의 변경 내용을 플러시하는 방법

  • em.flush()
  • 트랜잭션 커밋
  • JPQL 쿼리 실행

JPQL 쿼리 실행 시 플러시가 자동으로 호출되는 이유

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();
  • em.persist()만 하고, 트랜잭션을 커밋하기전에 JPQL을 실행해서 데이터를 조회하려고 하면, 아직 DB에 insert가 안되었기 때문에 DB에 데이터가 아직 없다.
    그래서 문제가 발생할 수 있으므로 JPA는 이를 방지하기 위해, JPQL을 실행하면 flush()가 자동으로 호출되고 나서 JPQL이 실행된다.


GeneratedValue

  • GeneratedValue(strategy = Generationtype.IDENTITY)
  • GeneratedValue(strategy = Generationtype.SEQUENCE)

<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;
}
  • @SequenceGenerator를 사용하면 특정 시퀀스생성기를 통해 특정 시퀀스를 사용하고,
    @SequenceGenerator를 사용하지 않고, @GeneratedValue만 사용하면 JPA가 내부적으로 직접 hibernate_sequence 시퀀스를 사용한다.

< allocationSize >
1. 처음 em.persist()를 할떄 allocation했던거 만큼의 시퀀스 값을 데이터베이스 시퀀스에서 미리 가져와서 메모리(=애플리케이션 내부)에 시퀀스 값을 저장해놓는다. 이떄 1부터 50의 값이 메모리에 저장된다.
2. 데이터베이스 시퀀스의 값(현재값)은 50까지 줬으니 51로 되어있다.
3. 이후 em.persist()를 하면 메모리에 있는 값(=애플리케이션 내부에 있는 시퀀스 값)이 사용되고, 만약 50까지 다 사용하고 나서 em.persist()를하게 되면 51부터 다시 allocation했던거 만큼의 시퀀스 값을 미리 가져와서 메모리에 값을 저장해놓는다. 이때 51부터 100의 값이 메모리에 저장된다. 데이터베이스 시퀀스의 값(현재값)은 100까지 줬으니 101로 되어있다.

0개의 댓글