Spring/Spring Data JPA

[자바 ORM 표준 JPA 프로그래밍] 10-5. 객체지향 쿼리 언어: 객체지향 쿼리 심화

noahkim_ 2025. 4. 24. 10:40

김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.

 

1. 벌크 연산

  • 수백개의 엔티티 수정을 하기 위해 각각 상태를 변경할 경우 너무 오래걸림
  • 이를 위해 여러 건을 한번에 처리하는 벌크 연산을 지원함

 

예제)

더보기
@Test
@Transactional
void bulk_operation() {
    String selectJpql = "select u from User as u";
    List<User> result = entityManager.createQuery(selectJpql, User.class).getResultList();

    long start1 = System.currentTimeMillis();

    for (User user : result) user.setName("updated");
    entityManager.flush();
    entityManager.clear();

    long end1 = System.currentTimeMillis();

    System.out.println("일반 방식 소요 시간: " + (end1 - start1) + "ms");


    long start2 = System.currentTimeMillis();

    String updateJpql = "UPDATE User u SET u.name = CONCAT(u.name, '_bulk')";
    int updatedCount = entityManager.createQuery(updateJpql).executeUpdate();

    entityManager.clear();

    long end2 = System.currentTimeMillis();

    System.out.println("벌크 방식 소요 시간: " + (end2 - start2) + "ms");
}
일반 방식 소요 시간: 1471ms
벌크 방식 소요 시간: 103ms
  • 100명의 유저가 있다면, 영속성 컨텍스트가 전부 관리 → 성능 부담
  • 100건 각각 dirty checking 및 update query 요청  → 성능 낮음

 

주의점

  • 벌크 연산은 데이터베이스에 직접 쿼리함 (영속성 컨텍스트와 연관 ❌)
  • 벌크 연산 직후 영속성 컨텍스트에서 가져온 데이터는 1차 캐시에 속한 데이터
  • DB에 수정된 값이 반영되지 않음

 

예제) 해결 방법

더보기

refresh()

@Test
@Transactional
void bulk_operation_solution_refresh() {
    User user = entityManager.find(User.class, 1);

    String updateJpql = "UPDATE User u SET u.age = 100";
    entityManager.createQuery(updateJpql).executeUpdate();

    assertNotEquals(user.getAge(), 100);

    entityManager.refresh(user);
    assertEquals(user.getAge(), 100);
}

 

벌크 연산 수행 후 영속성 컨텍스트 초기화

@Test
@Transactional
void bulk_operation_solution_persistence_context_clear() {
    String updateJpql = "UPDATE User u SET u.age = 100";
    entityManager.createQuery(updateJpql).executeUpdate();

    entityManager.clear();

    User user = entityManager.find(User.class, 1);

    assertEquals(user.getAge(), 100);
}

 

2. 영속성 컨텍스트와 JPQL

  • JPQL로 엔티티 조회 시, 영속성 컨텍스트에서 관리 ✅ (임베디드, 스칼라 타입 관리 ❌)

 

JPQL 조회 흐름

  1. DB 조회
    • JPQL은 즉시 SQL로 변환됨
  2. 영속성 컨텍스트 확인
    • 영속성 컨텍스트는 영속 상태인 엔티티의 동일성을 보장함
    • -> DB에서 조회한 엔티티가 존재 O, 영속성 컨텍스트 값을 반환
    • -> DB에서 조회한 엔티티가 존재 X, 영속성 컨텍스트에 조회 값 저장 및 반환

 

entityManager.find() 조회 흐름

  1. 영속성 컨텍스트 확인
  2. DB 조회

 

예제) JPQL 엔티티 조회

더보기
@Test
@Transactional
void jpql_persistence_context_flow() {
    User user = new User("test");
    user.setAge(20);
    entityManager.persist(user);
    entityManager.flush();

    String updateJpql = "UPDATE User u SET u.age = 100";
    entityManager.createQuery(updateJpql).executeUpdate();

    User result = entityManager.createQuery("SELECT u FROM User u WHERE u.name = 'test'", User.class)
            .getSingleResult();

    assertEquals(result.getAge(), 20);
}

 

예제) entityManager.find()

더보기
@Test
@Transactional
void entityManager_persistence_context_flow() {
    User user = new User("test");
    user.setAge(20);
    entityManager.persist(user);
    entityManager.flush();
    entityManager.clear(); // 1차 캐시 초기화

    // 1차 캐시에 없음 -> db 조회
    User result1 = entityManager.find(User.class, user.getId());
    User result2 = entityManager.find(User.class, user.getId());

    assertSame(result1, result2);
}

 

3. JPQL과 플러시 모드

FlushMode.AUTO

  • JPQL 실행 전에 자동적으로 flush가 호출됨
  • 영속성 컨텍스트와 데이터베이스간의 불일치를 완전히 차단
  • 너무 많은 플러시로 인해 성능상 손해 발생

 

예제

더보기
@Test
@Transactional
void flushmode_auto() {
    entityManager.setFlushMode(FlushModeType.AUTO);

    User user = new User("test");
    entityManager.persist(user);

    // JPQL 실행 전에 flush 발생 -> 동기화
    User result = entityManager.createQuery("SELECT u FROM User u WHERE u.name = 'test'", User.class).getSingleResult();

    assertEquals(result.getName(), user.getName());
}

 

FlushMode.COMMIT

  • 커밋 시에만 플러시하도록 모드를 설정 (자동 flush를 해제)