[JPA] 객체 지향 쿼리 언어(JPQL) 4 - Named 쿼리, 벌크 연산

조성우·2024년 6월 23일

JPA - 공부

목록 보기
16/16
post-thumbnail

Named 쿼리와 벌크 연산에 대해 알아보자


Named 쿼리

  • 미리 정의해서 이름을 부여해두고 사용하는 JPQL
  • 정적 쿼리
  • 어노테이션, XML에 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용
  • 애플리케이션 로딩 시점에 쿼리를 검증 (오류를 잡아낼 수 있음)
    • 가장 좋은 오류인 컴파일 시점에 나는 오류와 가장 나쁜 오류인 사용 시점에 나는 오류의 사이의 시점! (런타인 오류긴 하지만, 실행시켜봄으로서 알 수 있는 오류)

어노테이션을 통해 사용

@Entity
@NamedQuery(
    name = "Member.findByUsername",
    query="select m from Member m where m.username = :username")
public class Member {
    ...
}
List<Member> resultList =
    em.createNamedQuery("Member.findByUsername", Member.class)
    .setParameter("username", "회원1")
    .getResultList();

Spring Data JPA에서의 Named 쿼리 - @Query : 이름없는 Named 쿼리

알고보니 내가 프로젝트에서 JPQL을 사용하던 방식이 Named 쿼리였다!


벌크 연산

  • 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
  • JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
    1. 재고가 10개 미만인 상품을 리스트로 조회한다.
    2. 상품 엔티티의 가격을 10% 증가한다.
    3. 트랜잭션 커밋 시점에 변경감지가 동작한다.
  • 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행
  • 쿼리 한 번으로 여러 테이블 로우 변경 (엔티티)
  • executeUpdate()의 결과는 영향받은 엔티티 수 반환
  • UPDATE, DELETE 지원
  • INSERT(insert into .. select, 하이버네이트 지원)
String qlString = "update Product p " +
    "set p.price = p.price * 1.1 " +
    "where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
    .setParameter("stockAmount", 10)
    .executeUpdate();

벌크 연산 - 주의

  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
    • 영속성 컨텍스트의 1차 캐시에 저장되어 있는 데이터와 데이터베이스에 저장되어 있는 데이터가 서로 다른 경우가 발생할 수 있음!
    • 해결 방안
      1. (영속성 컨텍스트 아무것도 없는 상태로) 벌크 연산을 먼저 실행
      2. 벌크 연산 수행 후 영속성 컨텍스트 초기화
// 벌크 연산을 먼저 실행하지 않고 영속성 컨텍스트에 기존 데이터가 그대로 남아있다면 아래와 같은 문제를 초래함
// 따라서 가장 먼저 실행하거나, em.clear()를 통해 영속성 컨텍스트를 초기화해주어야 함
Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(0);
member1.setTeam(teamA);
em.persist(member1);
  
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(0);
member2.setTeam(teamA);
em.persist(member2);
  
int resultCount = em.createQuery("update Member m set m.age = 20")
	.executeUpdate();
  	
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getAge());  // findMember = 0이 출력됨

0개의 댓글