다형성 쿼리
조회 대상을 특정 자식으로 한정하는 기능입니다.
예) Item 중 Book, Movie 조회
// JPQL
slect i from Item i
where **type(i)** IN (Book, Movie)
// SQL
select i from i
where i.DTYPE in ('B', 'M')TREAT
자바의 타입 캐스팅과 유사하며 상속 구조에서 부모 타입을 특저 ㅇ자식 타입으로 다룰 때 사용합니다.
예) 부모인 Item과 자식 Book이 있음.
// JPQL
select i from Item i
where **treat(i as Book)**.author = 'kim'
// SQL
select i.* from Item i
where i.DTYPE = 'B' and i.author = 'kim'엔티티 직접 사용
JPQL에서 엔티티 필드가 아닌 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용하게 됩니다.
// JPQL
select count(m.id) from Member m // 엔티티의 아이디를 사용
select count(m) from Member m // 엔티티를 직접 사용
// sql (jqpl 둘 다 같은 다음 SQL 실행)
select count(m.id) as cnt from Member mNamed 쿼리(정적 쿼리)
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();XML 방식
// persistence.xml
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>
// ormMember.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
<named-query name="Member.findByUsername">
<query><![CDATA[
select m
from Member m
where m.username = :username
]]></query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>어노테이션과 XML 동시 사용시 항상 XML이 우선권을 가지며 운영 환경에 따라 다른 XML을 배포할 수 있습니다. 따로 선언해서 사용하는게 불편할 수는 있지만 JpaRepository내 @Query를 통해 사용하는 모든 쿼리는 이름 없는 named 쿼리로 등록되어 사용됩니다.
벌크 연산
벌크 연산이란 일반적으로 생각하는 SQL 내 update나 delete 쿼리로 생각하면 되는데 이해를 위한 시나리오는 다음과 같습니다.
- 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
-
JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL이 실행된다.
- 재고가 10개 미만인 상품을 리스트로 조회
- 상품 엔티티의 가격을 10% 증가
- 트랜잭션 커밋 시점에 변경감지가 동작 (더티체킹)
- 변경된 데이터가 100건이라면? ⇒ 100번의 UPDATE SQL 실행
벌크 연산 예제
쿼리 한 번으로 여러 테이블 로우를 변경하는 기능을 JPA는 제공합니다. (엔티티 기준)
String qlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
// excuteUpdate의 return 값은 "영향받은 엔티티의 수"
int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", 10)
.executeUpdate(); 벌크 연산 주의사항
벌크 연산은 영속성 컨텍스트를 무시하고 직접 쿼리하기 때문에 꼭 벌크 연산을 먼저 수행하거나, 연산 수행 후 영속성 컨텍스트를 초기화해줘야 합니다.
// Member 1~3은 영속화만 되어 있고 DB에는 없음
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);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(0);
member3.setTeam(teamB);
em.persist(member3);
// 벌크 연산은 "DB에 직접" 쿼리하기 때문에 update 쿼리가 나가도 반영되지 않음
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());
// 실행 결과
Hibernate:
/* update
Member m
set
m.age = 20 */ update Member
set
age=20
findMember = 0
==========================================================================
// 벌크 연산 수행 후 영속성 초기화
...
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
em.clear();
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getAge());
// 실행 결과
Hibernate:
/* update
Member m
set
m.age = 20 */ update Member
set
age=20
Hibernate:
select
m1_0.id,
m1_0.age,
m1_0.TEAM_ID,
m1_0.username
from
Member m1_0
where
m1_0.id=?
findMember = 20이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗