JPA의 영속성 관리

전홍영·2024년 12월 31일

JPA

목록 보기
5/6

JPA에는 영속성이라는 개념이 있다. JPA를 사용하면서 어렴풋이 알았던 개념에 대해서 확실히 정리하기 위해서 포스트를 작성한다. 내가 기존에 알고 있던 개념은 약간 스프링의 컨테이너같은 개념으로 어떤 객체를 저장하고 있다가 꺼내서 사용하는 느낌이었다. 하지만 책을 읽고 코드를 짜보면서 영속성 컨텍스트에 대해 조금은 이해하게 된 것 같다.

영속성 컨텍스트??

영속성은 어플리케이션이 닫혀도 그로 인해 생성된 데이터가 비휘발성 저장소에 기록되어 사라지지 않음을 의미한다. JPA에서 영속성은 엔티티를 영구 저장할 수 있는 성질을 의미하는데 이러한 데이터를 관리하는 곳이 영속성 컨텍스트이다. 이 영속성 컨텍스트에 저장되는 엔티티 즉, 데이터들은 컨텍스트가 종료되거나 삭제하지 않는 이상 사라지지 않고 관리될 수 있다.

영속성 켄텍스트를 JPA를 사용하면서 직접 본 적은 없다. 왜냐하면 이 개념은 물리적인 개념보다 논리적인 개념에 가깝다. 따라서 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트라는 곳에 저장한다라는 눈에 보이지 않는 개념을 이용하여 JPA가 활동된다.

어떻게 영속성 컨텍스트에 엔티티를 저장할까?

영속성 컨텍스트에 엔티티를 저장하기 위해서는 엔티티 매니저가 필요하다. 엔티티는 매니저는 엔티티 매니저 팩토리라는 말 그대로 공장에서 만들어주는 매니저가 DB의 커넥션을 사용해서 DB 관련 작업을 수행한다.

엔티티 매니저 팩토리의 생성 비용은 상당히 크다. 따라서 보통 앤티티 매니저 팩토리는 하나만 생성하고 생성 비용이 적은 엔티티 매니저를 많이 생산하여 사용한다. 여기서 중요한 점은 엔티티 매니저 팩토리는 여러 쓰레드가 접근해도 안전하므로 스레드 간에 공유해도 되지만, 엔티티 매니저는 동시에 여러쓰레드가 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유를 하면 안된다.

생성된 엔티티 매니저는 DB의 커넥션을 사용하여 작업을 한다고 했다. 하지만 엔티티 매니저가 생성될 때 바로 커넥션을 사용하는 것이 아니라 트랜잭션이 시작될 때 커넥션을 사용한다. 따라서 엔티티 매니저는 트랜잭션이 시작되기 전에는 커넥션을 유지하고 있는 것이 아니라 트랜잭션이 시작되면 영속성 컨텍스트에 있는 SQL 작업들을 수행한다. (커넥션 풀도 엔티티 매니저 팩토리가 생성될 때 생성된다.)

이렇게 생성된 엔티티 매니저는 로직을 수행하면서 DB에 데이터를 가져와 영속성 컨텍스트에 저장하거나, DB에 저장할 엔티티 데이터를 영속성 컨텍스트에 저장해두었다가 SQL 작업을 수행하도록 한다.

엔티티의 생명주기

지금까지 영속성 컨텍스트와 엔티티를 어떻게 영속성 컨텍스트에 저장하는지 간단하게 알아보았다. 엔티티는 영속된 상태이거나 영속되지 않은 비영속 상태 두가지의 상태만 존재하는 것일까?

엔티티에는 4가지 상태가 존재한다. 비영속, 영속, 준영속, 삭제 이렇게 4가지의 상태로 존재하는데 각각의 상태에 따라 특징이 존재한다.

비영속

비영속은 엔티티가 객체는 생성이 되었지만 영속성 컨텍스트나 DB에 관련 없이 순수한 객체 상태를 의미한다.

영속

영속 상태는 생성된 엔티티가 엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태를 의미한다. 이렇게 저장된 엔티티는 영속성 컨텍스트에 의해 관리되어 진다.

준영속

영속성 컨텍스트에서 관리되어 지던 엔티티를 더 이상 관리하지 않게되면 준영속 상태가 된다. 엔티티 매니저가 detach나 close, clear를 호출하여 컨텍스트가 종료되거나 해당 엔티티를 영속성에서 제외시키면 해당 엔티티들은 준영속 상태가 된다.

삭제

엔티티를 영속성 컨텍스트와 DB에서 삭제하면 해당 엔티티는 삭제 상태가 된다.

영속성 컨텍스트의 장점과 특징

영속성 컨텍스트에는 여러 특징이 존재한다. 영속성 컨텍스트에 저장된 엔티티는 식별자 값(ID)으로 구분하여 저장된다. 따라서 영속성 상태는 식별자 값이 반드시 존재해야 하면 없다면 오류가 발생한다. 이 식별자를 통해서 저장된 엔티티를 조히하거나 더티 체크, 등이 가능하다.

또한 영속성 컨텍스트에 엔티티를 저장하면 이 엔티티는 트랜잭션이 시작되면 저장되는 작업이 수행된다. 트랜잭션이 시작하면 새로 등록된 엔티티를 DB에 저장하고 수정된 데이터가 있으면 수정되는 SQL이 수행된다. 이를 flush라 하는데 flush는 영속성 컨텍스트에 있는 SQL 저장소에 있는 작업들을 모두 수행하도록 하는 작업이다.

영속성 컨텍스트를 이용하면 여러 이점들이 존재한다. 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 등이 있으며 이를 이해해야 JPA를 더 효율적으로 사용할 수 있다.

1차 캐시

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이를 1차 캐시라고 한다. 영속 상태의 엔티티들은 모두 이 곳에 저장되는데 키는 식별자이고 값은 해당 엔티티인 MAP에 저장된다고 생각하면 된다.

만약 엔티티를 조회하고 싶다면 어떻게 JPA에서는 동작할까? 일단 엔티티를 조회하기 위해서는 해당 ID 식별자를 알아야 해당 엔티티를 조회할 수 있다. 따라서 식별자를 통해 엔티티 매니저는 1차 캐시에 해당 엔티티가 존재하는지 조회해보고 없다면 DB에서 엔티티를 조회하여 영속성 컨텍스트에 저장한다. 코드를 보자.

@Test
@DisplayName("1차 캐시")
void first_cache() {
    //given
    Student student = Student.builder()
            .age(10)
            .name("홍길동")
            .build();

    entityManager.persist(student);
    //when
    Student foundStudent = entityManager.find(Student.class, student.getId());
    //then
    assertThat(student).isEqualTo(foundStudent);
}

코드를 보면 student라는 객체를 persist() 호출하여 영속성 컨텍스트에 저장하였다. 그리고 find()를 호출하여 해당 엔티티를 조회하였다. 이러면 일단 영속성 컨텍스트에 student를 저장하고 조회를 했기 때문에 DB에 조회 쿼리를 날릴 필요가 없을 것이다. 실행된 쿼리를 보자.

Hibernate: insert into student (age, name, id) values (?, ?, ?)

이렇게 쿼리가 실행되었다. 이를 통해 알 수 있는 것은 엔티티를 영속성 컨텍스트에 저장하는 순간 1차 캐시에도 저장되고 find()를 호출해도 1차 캐시에서 해당 엔티티를 조회하기 때문에 select 쿼리가 수행되지 않은 것이다. 이렇게 1차 캐시를 이용하면 성능적으로 이점을 누릴 수 있을 것이다.

또한 위의 예시 코드를 보면 student와 find() 통해 찾은 student가 같은 객체임을 Assertions를 통해 증명했다. 이는 두 객체가 같은 객체임을 알 수 있다. 왜냐하면 두 객체는 영속성 컨텍스트에 존재하는 캐시를 통해 같은 식별자를 가지고 같은 엔티티를 반환했기 때문이다. 따라서 1차 캐시를 이용하면 두 객체의 동일성을 보장해준다.

트랜잭션을 지원하는 쓰기 지연

위의 1차 캐시 예시 코드를 보면 insert 쿼리가 수행된 것을 볼 수 있다. 코드를 보면 persist()를 통해 엔티티를 영속성 컨텍스트에 저장만 하였는데 어떻게 insert 쿼리가 자동으로 실행 된 것일까? 이는 영속성 컨텍스트에 새롭게 저장된 엔티티가 있으면 엔티티 매니저는 자동으로 영속성 컨텍스트 안에 존재하는 SQL 저장소에 insert 쿼리를 저장한다. 그리고 트랜잭션이 시작되고 커밋되는 시점에 SQL 저장소에 있는 SQL 수행한다.

Student student = Student.builder()
                .age(10)
                .name("홍길동")
                .build();

entityManager.persist(student);

이렇게 persist를 통해 영속성 컨텍스트에 저장할 때 해당 엔티티가 캐시에 존재하는지 보고 없다면 insert SQL을 생성한다. 그러면 새로운 student를 여러개 생성하여 영속성 컨텍스트에 저장하면 persist를 할 때마다 SQL이 실행되는 것일까? 코드를 통해 보자.

@Test
@DisplayName("지연 쓰기")
void lazy_write() {
	transaction.begin();
    //given
    Student student1 = Student.builder()
            .age(10)
            .name("홍길동")
            .build();

    Student student2 = Student.builder()
            .age(10)
            .name("김춘추")
            .build();

    entityManager.persist(student1);
    entityManager.persist(student2);
    //when & then
    System.out.println("아직 쓰기 작업 안함");
    transaction.commit();
}

이 테스트 코드를 보면 2명의 학생을 생성해서 영속성 컨텍스트에 저장했다. persist를 통해 저장할 때마다 sql이 실행되었을까? 결과를 보면 다음과 같다.

아직 쓰기 작업 안함
Hibernate: insert into student (age, name, id) values (?, ?, ?)
Hibernate: insert into student (age, name, id) values (?, ?, ?)

위의 코드 마지막에 '아직 쓰기 작업 안함'이라는 문장을 출력하게 하였다. 콘솔 창을 보면 persist를 한 후에 insert 쿼리가 수행된 것을 볼 수 있다. 어떻게 된 것일까?

영속성 컨텍스트의 장점으로 트랜잭션을 지원하는 쓰기 지연이라는 것이 있었다. 이는 트랜잭션이 시작되고 커밋되기 전까지 쓰기 작업의 SQL이 실행되지 않고 커밋시점에 실행되도록하는 것이다. 다음 그림을 보자.

persist()를 통해 엔티티가 저장되면 1차 캐시에 저장되고 insert SQL이 생성되어 쓰기 지연 저장소에 저장된다. 그리고 트랜잭션이 커밋되는 시점에에 엔티티 매니저는 flush를 호출하여 쓰기 지연 저장소에 저장되어 있는 SQL들을 수행하도록 한다. 이를 통해서 영속성 컨텍스트의 변경 내용을 데이터베이스와 동기화 후에 실제 데이터베이스 트랜잭션을 커밋한다.

어떻게 이러한 쓰기 지연이 가능한 것일까? 이유는 바로 트랜잭션 때문이다. 트랜잭션은 원자성이라는 특징을 지니고 있다. 따라서 모두 성공하거나 모두 실패하는 특징을 지니고 있다. student1,student2가 영속성 컨텍스트에 저장될 때마다 DB에 저장해도 rollback이 이루어지면 해당 데이터가 다시 삭제될 것이다. 그래서 영속성 컨텍스트에 엔티티들이 저장될 때마다 SQL을 수행하지 않고 저장소에 저장해두었다가 트랜잭션이 커밋되면 해당 SQL 작업들을 모두 수행하고 롤백이 되면 모두 취소하도록하여 성능상의 이점을 가져온다. 따라서 트랜잭션의 특징을 이용하여 쓰기 지연 기능을 수행할 수 있게 되었고 이를 통해 성능 최적화를 이룰 수 있었다.

변경 감지(더티 체크)

JPA에서는 엔티티를 어떻게 수정할까? SQL만을 이용해서 엔티티를 수정할 때에는 수정된 속성에 따라서 Update 쿼리를 다르게 작성할 것이다. 하지만 만약 수정된 속성이 많아지면 쿼리를 작성할 때 실수할 가능성이 높아질 것이다. 그렇다고 모든 속성을 Update 쿼리에 적으려 해도 개발자가 직접 작성하면 실수할 가능성이 높다. 따라서 JPA는 엔티티가 변경되면 변경된 내용을 감지하여 자동으로 Update 쿼리를 작성해준다.


JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사하여 스냅샷을 생성한다. 그 후 트랜잭션이 커밋되면 flush가 실행되는데 이때 엔티티와 스냅샷을 비교하여 변경된 부분이 있으면 엔티티 매니저는 자동으로 수정된 부부에 해당하는 Update 쿼리를 생성하여 지연 쓰기 저장소에 저장한다. 그리고 SQL 저장소에 있는 쿼리를 DB에 보내고 트랜잭션을 커밋하게 된다. 이때 변경 감지는 영속성 컨텍스트에서 관리하는 영속 상태의 엔티티에만 적용되기 때문에 비영속, 준영속 상태의 엔티티는 수정되어도 Update 쿼리가 실행되지 않는다.

@Test
@DisplayName("변경 감지")
void dirty_checking() {
    //given
    Student student = Student.builder()
            .age(10)
            .name("홍길동")
            .build();

    entityManager.persist(student);
    //when
    student.setName("이순신");
    //then
    assertThat(student.getName()).isEqualTo("이순신");
}

--console-- 

Hibernate: insert into student (age, name, id) values (?, ?, ?)
Hibernate: update student set age=?, name=? where id=?

코드를 보면 영속성 컨텍스트에 엔티티를 저장하고 이름을 변경하였다. 그리고 트랜잭션을 커밋한 결과를 보면 insert 문을 실행하고 update를 실행한 것을 볼 수 있다. 여기서 볼 부분은 코드에서는 이름만 변경하였는데 다른 속성도 update 쿼리에 들어간 것을 볼 수 있다. 이렇게 모든 필드를 사용하여 Update 쿼리를 작성하면 DB에 보내는 데이터가 많아지는 단점이 존재한다. 하지만 모든 필드를 사용하면 쿼리가 항상 같기 때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있다. 또한 데이터베이스는 동일한 쿼리를 보내면 이전에 한 번 파싱된 쿼리를 재사용할 수 있어 효과적인 성능을 보여준다.

만약 필드가 너무 많다면 @DynamicInsert 어노테이션을 활용하여 변경된 필드만 수정하는 기능을 이용할 수 있다. 그러나 이를 사용해서 성능 최적화를 이룰 수 있다면 오히려 테이블의 구성이 잘못된 것일 수 있다. 따라서 필드가 30개 이상이라면 해당 어노테이션을 사용할 수 있겠지만 필드가 30개 이상인 테이블이 있다면 해당 테이블을 수정하는 것이 더 좋은 방법일 수 있다.

엔티티 삭제

엔티티를 삭제하기 위해서는 엔티티를 조회해야 한다. 조히된 엔티티는 영속성 컨텍스트에 저장되어 있을 것이고 remove()를 호출하여 해당 엔티티를 삭제한다. remove를 호출하면 해당 엔티티는 영속성 컨텍스트에서 삭제되어 삭제 상태가 된다. 그리고 바로 DB에서도 삭제되는 것이 아니라 SQL 쓰기 지연 저장소에 삭제 쿼리가 자동으로 생성되어 저장되어진다. 트랜잭션이 커밋되고 flush가 실행되면 삭제 쿼리도 실행되어 해당 엔티티는 삭제되어진다. 삭제 상태의 엔티티는 자바의 가비지 컬렉션에 의해서 메모리에 남아있던 삭제 상태의 객체도 삭제된다.

flush는 언제 호출될까?

flush는 앞서 트랜잭션이 커밋되는 시점에 호출된다고 했다. 그러면 항상 이때만 호출되는 것일까? 플리시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다. 플러시는 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾고 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 저장한다. 그리고 저장소에 저장된 쿼리를 DB에 전송한다.

영속성 컨텍스트를 플러시하는 방법은 3가지이다. 직접 호출, 트랜잭션 커밋 시 플러시 자동 호출, JPQL 쿼리 실행 시 자동 호출 이 3가지 방법이다.

직접 호출

직접 호출은 엔티티 매니저의 flush() 메서드를 호출하여 강제로 flush하는 방법이다. 이는 테스트나 다른 프레임워크와 JPA를 함께 사용할 때 사용하지 다른 경우에는 거의 사용하지 않는다.

트랜잭션 커밋 시에 자동 호출

DB에 변경 내용을 SQL로 전달하지 않고 트랜잭션만 커밋하면 어떤 데이터도 DB에 반영되지 않는다. 따라서 트랜잭션을 커밋하기 전에 꼭 플러시를 호출하여야 DB에 변경 내용이 반영된다. 이러한 문제를 예방하기 위해 JPA는 트랜잭션을 커밋할 때 플러시가 자동 호출되도록 한다.

JPQL 쿼리 실행 시 플러시 자동 호출

JPQL, QueryDSL 같은 객체지향 쿼리를 호출할 때도 플러시가 실행된다. 영속성 컨텍스트에 있는 내용이 플러시를 통해 DB에 반영되어야 하는데 JPQL 쿼리가 실행될 때 영속성 컨텍스트에 존재하는 내용이 DB에 아지 반영되지 않은 경우가 있을 수 있다. 따라서 JPQL 쿼리가 실행 될 때 플러시를 통해 저장소에 있는 변경 내용의 SQL을 DB에 전달하고 JPQL을 실행하여 영속성 컨텍스트의 변경된 부분을 DB에 반영하여 동기화를 이룰 수 있다.

플러시는 Auto 모드와 Commit 모드가 존재하는데 기본값은 Auto이고 Commit은 커밋할 때만 플러시를 하는 것이고 Auto는 커밋이나 쿼리 실행 시에 플러시가 실행되도록하는 옵션이다. 플러시는 말 그대로 영속성 컨텍스트에 보관된 엔티티를 제거하는 것이 아니다. 영속성 컨텍스트의 변경된 내용을 DB에 반영하여 DB와 동기화하는 것이다. 그리고 DB와 동기활르 최대한 늦출 수 있는 이유는 트랜잭션 때문이라는 것일 잊지 말자.

준영속과 병합

준영속이란 영속성 컨텍스트에 존재하였다가 clear, close, detach에 의하여 영속성 컨텍스트에서 제외된 엔티티의 상태를 의미한다. 이렇게 준영속이 된 엔티티는 영속성 컨텍스트에서 제외될 뿐만아니라 쓰기 지연 저장소에 연관되어있던 내용들도 전부 제거된다. 따라서 준영속이 되면 더 이상 영속성 컨텍스트에서 제공하는 기능을 이용할 수 없게 된다.

@Test
@DisplayName("준영속")
void detach() {
    //given
    Student student = Student.builder()
            .age(10)
            .name("홍길동")
            .build();

    entityManager.persist(student);
    //when
    entityManager.detach(student);
    //then
    assertThat(entityManager.contains(student)).isFalse();
}

@Test
@DisplayName("영속성 컨텍스트 초기화")
void clear() {
    //given
    Student student1 = Student.builder()
            .age(10)
            .name("홍길동")
            .build();

    Student student2 = Student.builder()
            .age(10)
            .name("김춘추")
            .build();

    entityManager.persist(student1);
    entityManager.persist(student2);
    //when
    entityManager.clear();
    //then
    assertThat(entityManager.contains(student1)).isFalse();
    assertThat(entityManager.contains(student2)).isFalse();
}

코드를 보면 student를 persist를 통해 영속성 컨텍스트에 저장하였지만 detach를 통해 준영속 상태로 변경하였다. 그리고 준영속 상태인 엔티티를 영속성 컨텍스트에 있는지 contains()를 통해 검증하였다. 그 결과 해당 엔티티는 더 이상 영속성 컨테이너에 없다는 것을 알 수 있다. 또한 clear() 통해서 영속성 컨텍스트에 저장되 모든 엔티티를 제외시켰다. 그리고 contains()를 통해 검증하여 더이상 해당 영속성 컨테이너에는 엔티티가 존재하지 않음을 검증하였다.

이렇게 준영속 상태의 엔티티는 어떻게 될까? 영속성 컨텍스트에가 관리하는 엔티티가 아니기 때문에 1차 캐시, 쓰기 지연, 등의 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다. 하지만 준영속 상태의 엔티티는 영속 상태일 때가 있었기 때문에 식별자를 가지고 있다. 따라서 다시 병합을 통해 영속 상태가 될 수 있다. 마지막으로 지연로딩을 사용할 수 없다. 지연 로딩은 다룰 내용이 많기 때문에 다음 포스트로 넘기지만 지연 로딩은 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러 오는데 해당 엔티티는 더 이상 영속 상태가 아니기 때문에 이를 활용할 수 없다.

이러한 준영속 상태의 엔티티를 병합을 통해서 다시 영속 상태로 변경할 수 있다. 위에서 말했듯 준영속 상태의 엔티티는 식별자를 갖는다. 따라서 새로운 영속 상태로 변경시킬 수 있다.

@Test
@DisplayName("병합")
void merge() {
    //given
    EntityManager entityManager2 = emf.createEntityManager();
    EntityTransaction entityManager2transaction = entityManager2.getTransaction();
    entityManager2transaction.begin();

    Student student = Student.builder()
            .age(10)
            .name("홍길동")
            .build();

    entityManager2.persist(student);

    entityManager2transaction.commit();
    entityManager2.close();

    student.setName("이순신");
    //when
    Student mergeStudent = entityManager.merge(student);
    //then
    assertThat(entityManager.contains(student)).isFalse();
    assertThat(entityManager.contains(mergeStudent)).isTrue();
}

--console--
Hibernate: insert into student (age, name, id) values (?, ?, ?)
Hibernate: select s1_0.id,s1_0.age,s1_0.name from student s1_0 where s1_0.id=?
Hibernate: update student set age=?, name=? where id=?

코드를 보면 엔티티 매니저2는 student를 생성하여 영속성 컨테이너에 저장한 후 커밋하고 종료하였다. 영속성 컨테이너를 종료하면서 student는 더 이상 영속상태가 아닌 준영속 상태가 되었다. 이 student의 필드를 변경하고 새로운 엔티티 매니저에 merge()를 통해 새로운 영속성 컨테이네에 병합되어서 새로운 객체로 영속상태가 되었다. 이 merge하는 과정을 보면 해당 엔티티의 식별자 값을 이용하여 영속성 컨텍스트에 해당 엔티티가 있는 지 검증하고 없으면 DB에서 조회한다. 그리고 해당하는 엔티티가 있으면 영속성 컨텍스트에 저장하게 된다.

검증하는 부분을 보면 기존의 student는 새로운 영속성 컨테이너에 존재하지는 않지만 영속상태로 변경된 새로운 student는 영속성 컨테이너 에 존재함을 알 수 있다. 그리고 결과가 출력된 부분을 보면 변경된 필드의 내용을 영속성 컨테이너가 감지하여 update 쿼리까지 실행된 것을 볼 수 있다.

이를 통해 알 수 있는 것은 준영속 상태인 엔티티가 새로운 엔티티 매니저를 통해 영속 상태가 되면 영속 상태로 변경된 엔티티와 준영속 상태의 엔티티는 서로 다르다는 것이다. 따라서 준영속 상태의 엔티티를 다른 곳에서 참조하도록 하면 안된다.

준영속 뿐만아니라 비영속 상태의 엔티티도 영속 상태로 병합할 수 있는데 병합은 파라미터로 넘어온 엔티티의 식별자로 DB에서 조회하여 영속성 컨텍스트에 저장한다. 만약 DB에서로 발견하지 못하면 새로운 엔티티를 생성해서 병합한다. 병합은 비영속, 준영속 상관없이 식별자 값으로 엔티티를 조회할 수 있다면 조회하여 병합하고 없으면 생성해서라도 병합한다. 따라서 병합은 save or update 기능을 수행한다.

정리

지금까지 엔티티 매니저 팩토리부터 엔티티 매니저, 영속성 컨텍스트, 엔티티 상태, 영속성 컨텍스트의 특징 등을 알아보았다. 영속성 컨텍스트의 특징 중 중요한 부분인 지연 로딩은 따로 알아봐야 할 정도로 중요한 부분이기 때문에 다음 포스트로 넘긴다. 이번에 영속성에 대해서 공부하면서 JPA를 이용할 때 왜 udpate 쿼리가 자동으로 실행되고 지연 쓰기와 같은 기능이 수행되는지에 대해 이해할 수 있었던 것 같다. 이러한 영속성을 기반으로 앞으로 JPA에서 등장하는 개념을 알아본다면 좀 더 수월하게 이해할 수 있을 것 같다.

참조

profile
Don't watch the clock; do what it does. Keep going.

0개의 댓글