[JPA 기본] 객체지향 쿼리 언어 (JPQL)
[JPA 기본] 객체지향 쿼리 언어 (JPQL)
객체지향 쿼리 언어 (JPQL)
JPA는 다양한 쿼리 방법을 지원한다.
- JPQL (표준)
- JPA Criteria
- QueryDSL
- 네이티브 SQL
- JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께사용
JPQL
- JPA가 제공하는 SQL을 추상화한 객체 지향 쿼리 언어
- JPA를 사용하면 엔티티 객체를 중심으로 개발한다.
- JPQL은 엔티티 객체를 대상으로 쿼리를 생성한다.
- DB의 모든 데이터를 객체로 변환해서 검색하는 것은 불가능하다.
- 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요하다.
- SQL과 문법 유사하며 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 등을 지원한다.
- 예시 (기본)
Member newMember = new Member();
newMember.setName("newMember");
newMember.setAge(21);
em.persist(newMember);
String query = "SELECT m FROM Member m";
List<Member> members = em.createQuery(query, Member.class).getResultList();SELECT
m
FROM
Member m */ select
m1_0.id,
m1_0.age,
m1_0.username,
m1_0.team_id,
m1_0.type
from
Member m1_0- 예시 (조건 검색)
Member newMember = new Member();
newMember.setName("newMember");
newMember.setAge(21);
em.persist(newMember);
String query = "select m from Member m where m.age > 18";
List<Member> members = em.createQuery(query, Member.class).getResultList();select
m
from
Member m
where
m.age > 18 */ select
m1_0.id,
m1_0.age,
m1_0.username,
m1_0.team_id,
m1_0.type
from
Member m1_0
where
m1_0.age>18Criteria
- 동적 쿼리 생성을 도와준다.
- 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할을 한다.
- JPA 공식 기능이다.
- 너무 복잡하고 실용성이 없다.
- Criteria 대신에 QueryDSL 사용 권장
- 예시
//Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
//루트 클래스 지정 (조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
//쿼리 생성
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("name"), "kim"));
List<Member> Members = em.createQuery(cq).getResultList();select
m1_0.id,
m1_0.age,
m1_0.username,
m1_0.team_id,
m1_0.type
from
Member m1_0
where
m1_0.username=?QueryDSL
- 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할을 한다.
- 컴파일 시점에 문법 오류를 찾을 수 있다.
- 동적 쿼리 작성하기에 편리하다.
- 단순하고 쉽다.
- 실무 사용 권장 (★)
- 예시
JPAQueryFactory query = new JPAQueryFactory(em);
QMember m = QMember.member;
List<Member> list =
query.selectFrom(m)
.where(m.age.gt(18))
.orderBy(m.name.desc())
.fetch();네이티브 SQL
- JPA가 제공하는 SQL을 직접 사용하는 기능
- JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 위해서 사용한다.
- 예시
- 오라클 CONNECT BY
- 특정 DB만 사용하는 SQL 힌트
- 예시
String sql = "SELECT ID, AGE, TEAM_ID, USERNAME FROM MEMBER WHERE USERNAME = 'kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();JDBC 직접 사용, SpringJdbcTemplate 등
- JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, 마이바티스등을 함께 사용 가능
- 단, 영속성 컨텍스트를 적절한 시점에 강제로 플러시 처리가 필요가 필요하다.
- JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트 수동 플러시
기본 문법과 쿼리 API
- JPQL은 객체지향 쿼리 언어다.
- 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
- JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.
기본 문법
- 엔티티명과 속성명은 대소문자를 구분한다.
- 만약 엔티티명이 Member일 때 member라고 명시하면 오류가 발생한다.
- JPQL 키워드는 대소문자를 구분하지 않는다.
- SELECT, FROM, where 모두 사용가능한 키워드들이다.
- 테이블명이 아닌 엔티티명을 사용한다.
- 별칭은 필수로 설정해야 한다.
- AS는 생략해도 된다.
- 예시
select m from Member as m where m.age > 18select m.id, m.age, m.username from Member as m where m.age > 18
TypeQuery과 Query
- TypeQuery
- 반환 타입이 명확할 때 사용한다.
- 예시
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
- Query
- 반환 타입이 명확하지 않을 때 사용
- 예시
Query query = em.createQuery("SELECT m FROM Member m");
결과 조회 API
- query.getResultList()
- 결과가 하나 이상일 때는 리스트를 반환한다.
- 결과가 없으면 빈 리스트를 반환한다.
- query.getSingleResult()
- 결과가 정확히 하나일 때 단일 객체를 반환한다.
- 결과가 없는 경우
- javax.persistence.NoResultException 발생
- 둘 이상인 경우
- javax.persistence.NonUniqueResultException 발생
파라미터 바인딩
이름 기준
- 변수명을 기준으로 파라미터를 바인딩한다.
- 예시
TypedQuery<Member> query2 = em.createQuery("select m from Member m where m.age > :age", Member.class);
query2.setParameter("age", 18);
List<Member> members2 = query2.getResultList();위치 기준
- 위치를 기준으로 파라미터를 바인딩한다.
- 위치는 절대적인 것이 아니기 때문에 절대 사용하면 안된다.
- 예시
TypedQuery<Member> query2 = em.createQuery("select m from Member m where m.age > ?1", Member.class);
query2.setParameter(1, 18);
List<Member> members2 = query2.getResultList();프로젝션 (SELECT)
- 프로젝션이란?
- SELECT 절에 조회할 대상을 지정하는 것
- 프로젝션 대상
- 엔티티
- 예시
- Member는 회원 엔티티다.
- Team은 팀 엔티티다.
SELECT m FROM Member mSELECT m.team FROM Member m
- 예시
- 임베디드 타입
- 예시
- address는 주소에 대한 임베디드 타입이다.
SELECT m.address FROM Member m
- 예시
- 스칼라 타입(숫자, 문자등 기본 데이터 타입)
- 예시
- username은 회원명을 가리키는 String이다.
- age는 회원의 나이를 가리키는 int다.
SELECT m.username, m.age FROM Member m
- 예시
- 엔티티
- DISTINCT 키워드로 중복을 제거할 수 있다.
- 여러 가지 값을 조회할 때의 처리 방법은 3가지가 있다.
- Query 타입으로 조회
- Object[] 타입으로 조회
- new 명령어로 조회
- 단순 값을 VO(DTO)로 바로 조회
- 예시
SELECT new com.example.demo.vo.step9.UserVO(m.username, m.age) FROM Member m
- 예시
- 패키지 명을 포함한 전체 클래스명을 입력해야 한다.
- 순서와 타입이 일치하는 생성자가 필요하다.
- 단순 값을 VO(DTO)로 바로 조회
페이징
- JPA는 페이징을 다음 두 API로 추상화한다.
- setFirstResult(int startPosition)
- 조회 시작 위치
- 0부터 시작한다.
- setMaxResults(int maxResult)
- 조회할 데이터 수
- 예시
//회원 15명 추가
for (int i = 1; i <= 15; i++) {
Member member = new Member();
member.setName("member" + i);
member.setAge(20 + i);
em.persist(member);
}
em.flush();
em.clear();
List<Member> members =
em.createQuery("SELECT m FROM Member m order by m.age desc", Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
for (Member data : members) {
System.out.println("member : " + data);
}
tx.commit();조인
조인 종류
내부 조인
- 예시
- 예시
- 예시
select count(m) from Member m, Team t where m.username = t.name
ON절을 활용한 조인
- JPA 2.1부터 지원한다.
- 종류
- 조인 대상 필터링
- 연관관계 없는 엔티티 외부 조인
- 하이버네이트 5.1부터 지원한다.
조인 대상 필터링
- 예시
- 전제 조건
- 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
- JPQL
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
- SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'
- 전제 조건
연관관계 없는 엔티티 외부 조인
- 예시
- 전제 조건
- 회원의 이름과 팀의 이름이 같은 대상 외부 조인
- JPQL
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
- SQL
SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
- 전제 조건
서브 쿼리
- JPA에서는 서브 쿼리도 지원한다.
- 서브 쿼리도 메인 쿼리랑 마찬가지로 엔티티를 기준으로 작성해야 한다.
- 관련 SQL 함수
- EXISTS (subquery)
- 서브쿼리에 결과가 존재하면 참
- NOT EXISTS (subquery)
- 서브쿼리에 결과가 존재하지 않으면 참
- ALL (subquery)
- 모두 만족하면 참
- 예시
- 나이 > ALL (10, 20, 30)이라면 나이 > 10, 나이 > 20, 나이 > 30을 모두 만족해야 한다.
- ANY (subquery) or SOME (subquery)
- 조건을 하나라도 만족하면 참
- 예시
- 나이 > ANY (10, 20, 30)이라면 나이 > 10, 나이 > 20, 나이 > 30 중 하나라도 만족하면 된다.
- IN (subquery)
- 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
- NOT IN (subquery)
- 서브쿼리의 결과 중 같은 것이 하나도 없으면 참
- EXISTS (subquery)
- JPA 서브 쿼리 한계
- JPA의 서브 쿼리는 WHERE, HAVING 절에서만 사용할 수 있다.
- SELECT절에서도 서브 쿼리를 사용할 수 있다.
- 하이버네이트에서 지원
- 현재 JPQL에서는 FROM절에 서브 쿼리를 사용할 수 없다.
- 조인으로 풀 수 있으면 풀어서 해결해야 한다.
- 그러나 하이버네이트6부터는 FROM절에서도 서브 쿼리를 사용할 수 있다.
JPQL 타입 표현과 기타식
JPQL 타입 표현
- 문자
'HELLO''She''s'
- 숫자
- Double
10D
- Float
10F
- Long
10L
- Double
- Boolean
TRUEFALSE
- ENUM
com.example.demo.MemberType.Admin- 패키지명을 포함해야 한다.
- 파라미터 바인딩 방식을 쓰는 게 편하다.
- 엔티티 타입
TYPE(m) = Member- 상속 관계에서 사용한다.
- WHERE절에서 사용한다.
- 자주 사용되지는 않지만 다형성 쿼리에서는 쓰는 경우가 있다.
기타식
- SQL과 문법이 같은 식
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <>
- BETWEEN, LIKE, IS NULL
조건식 (CASE 등등)
기본 CASE식
- 조건식의 결과가 참인 경우의 값을 반환한다.
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from
Member m조건 CASE식
- 값이 일치하는 경우의 값을 반환한다.
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from
Team tCOALESCE
- 인자로 지정한 요소들을 하나씩 비교해서 NULL이 아니면 NULL이 아닌 첫번째 값을 반환한다..
- 만약 COALESCE(A, B, C)인 경우에 A와 B가 NULL이라면 C를 반환한다.
select
coalesce(m.username, '이름 없는 회원')
from
Member mNULLIF
- 두 값이 같으면 null을 반환한다.
- 두 값이 다르면 첫번째 값을 반환한다.
select
NULLIF(m.username, '관리자')
from
Member mJPQL 함수
JPGQ 기본 함수
- CONCAT
- SUBSTRING
- TRIM
- LOWER, UPPER
- LENGTH
- LOCATE
- ABS, SQRT, MOD
- SIZE, INDEX(JPA 용도)
사용자 정의 함수 호출
- 하이버네이트는 사용전 방언에 추가해야 한다.
- 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.
- 예시
select function('group_concat', i.name) from Item i
- 예시
경로 표현식
경로 표현식이란?
- .(점) 연산자를 통해 객체 그래프를 탐색하는 것
select
m.username /* 상태 필드 */
from
Member m
join
m.team t /* 단일 값 연관 필드 */
join
m.orders o /* 컬렉션 값 연관 필드 */
where t.name = '팀A'관련 용어
- 상태 필드 (state field)
- 단순히 값을 저장하기 위한 필드
- 예시 : m.username
- 연관 필드 (association field)
- 연관관계를 위한 필드
- 단일 값 연관 필드
- 대상이 엔티티인 경우에 해당한다.
- @ManyToOne, @OneToOne
- 예시 : m.team
- 대상이 엔티티인 경우에 해당한다.
- 컬렉션 값 연관 필드
- 대상이 컬렉션인 경우에 해당한다.
- @OneToMany, @ManyToMany
- 예시 : m.orders
- 대상이 컬렉션인 경우에 해당한다.
- 단일 값 연관 필드
- 연관관계를 위한 필드
경로 표현식의 특징
- 상태 필드 (state field)
- 이름이나 나이처럼 특정 값을 나타내는 상태 필드는 더 이상 경로 탐색을 할 수 없다.
- 단일 값 연관 경로
- 단일 값 연관 필드가 조회 대상에 포함될 경우에 묵시적 내부 조인이 발생한다.
- inner join을 의미한다.
- 추가 탐색이 가능하다.
- 단일 값 연관 필드가 조회 대상에 포함될 경우에 묵시적 내부 조인이 발생한다.
- 컬렉션 값 연관 경로
- 컬렉션 값 연관 필드가 조회 대상에 포함될 경우에 묵시적 내부 조인이 발생한다.
- 컬렉션 자체를 가리키는 것이기 때문에 추가 탐색이 불가능하다.
- FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색할 수 있다.
- 예시 :
select m.username from team t join t.members m
- 예시 :
명시직 조인
- join 키워드 직접 사용하여 조인한 경우를 의미한다.
- 예시 :
select m from Member m join m.team t
- 예시 :
묵시적 조인
- 경로 표현식에 의해 묵시적으로 조인이 발생하는 경우를 의미한다.
- 내부 조인만 가능
- 예시 :
select m.team from Member m
경로 표현식 - 예제
select o.member.team from Order o=> 성공select t.members from Team=> 성공select t.members.username from Team t=> 실패select m.username from Team t join t.members m=> 성공
경로 탐색을 사용한 묵시적 조인 시 주의사항
- 경로 탐색에 의한 묵시적 조인 시은 항상 내부 조인이다.
- 컬렉션 값 연관 필드 경로 탐색의 끝이다.
- 만약 추가 탐색을 진해앟려면 명시적 조인을 통해 별칭을 얻어야 한다.
- 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 FROM (JOIN) 절에 영향을 준다.
TIP
- 가급적 묵시적 조인 대신에 명시적 조인을 사용해야 한다.
- 조인은 SQL 튜닝에서 매우 중요한 요소다.
- 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다.
- 추후 유지보수에 매우 곤란함을 제공하기도 한다.
패치 조인
- 실무에서 매우 중요한 기능이다.
- SQL 조인 종류에 포함되지 않는다.
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않는다.
join fetch명령어를 통해 사용한다.[ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
- JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다.
- SELECT 절에 지정한 엔티티만 조회한다.
- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다.
- 즉시 로딩으로 동작한다.
- 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다.
- N + 1 문제를 해결할 때 많이 사용된다.
- N + 1이란 최초 1번 엔티티 A의 목록을 조회하고 A의 목록에 대해 반복문을 돌리면서
해당 A에 관련된 엔티티 B를 조회할 때 해당하는 B가 영속성 컨텍스트에 존재하지 않아서
SQL을 새로 실행하는 경우가 최대 N번 (A의 개수를 의미) 발생 할 수 있다는 것을 의미한다.
- N + 1이란 최초 1번 엔티티 A의 목록을 조회하고 A의 목록에 대해 반복문을 돌리면서
- 모든 것을 페치 조인으로 해결할 수 는 없다.
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
- 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면,
페치 조인보다는 일반 조인으로 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적이다.
엔티티 페치 조인
- 특정 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
- 페치 조인으로 함께 조회하기 때문에 즉시 로딩이 된다.
- 예시
- JPQL
select m from Member m join fetch m.team
- SQL
SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID- 회원과 팀에 대해서 모두 조회한다.
- JPQL
컬렉션 페치 조인
- 일대다 관계, 컬렉션 페치 조인
- 예시
- JPQL
select t from Team t join fetch t.members where t.name = 'Team A'
- SQL
SELECT T.*, M.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = 'Team A'
- JPQL
페치 조인과 DISTINCT
- SQL의 DISTINCT
- 중복된 결과를 제거하는 명령
- SQL에서는 DISTINCT를 추가해도 데이터가 조금이라도 다르면 중복이 제거되지 않는다.
- JPQL의 DISTINCT
- SQL에 DISTINCT를 추가한다.
- 애플리케이션에서 엔티티 중복을 제거한다.
- 같은 식별자를 가진 엔티티를 제거한다.
- 하이버네이트6 변경 사항
- DISTINCT 명령어를 사용하지 않아도 애플리케이션에서 자동으로 중복 제거를 시도한다.
- 예시
select distinct t from Team t join fetch t.members where t.name = 'Team A'
페치 조인의 특징과 한계
- 페치 조인 대상에는 별칭을 줄 수 없다.
- 하이버네이트는 가능하다.
- 가급적 사용하지 않아야 한다.
- 관례적으로 별칭을 주지 않는다.
- 둘 이상의 컬렉션은 페치 조인 할 수 없다.
- 한 번의 일대다도 조회되는 데이터의 수가 급증할 수 있는데 그 이상은 더욱 급증할 수 있다.
- 컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하다.
- 방향을 뒤집어서 해결하는 방법도 있다.
- 기존
select t from Team t join fetch t.members m
- 변경
select m from Member m join fetch m.team t
- 기존
- @BatchSize를 활용하기도 한다.
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징을 한다.
- 매우 위험한 방법이다.
- 연관된 엔티티들을 SQL 한 번으로 조회하여 성능 최적화에 도움을 준다.
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선권을 갖는다.
- 글로벌 로딩 전략은
@OneToMany(fetch = FetchType.LAZY)같은 경우를 의미한다.
- 글로벌 로딩 전략은
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩이다.
- 최적화가 필요한 곳은 페치 조인 적용한다.
- 예시 : N + 1
다형성 쿼리
- 슈퍼 클래스와 서브 클래스 간의 관계에 대하여 조건을 설정한다.
- 예시를 위해 Item이라고 하는 물품 클래스가 있고,
Item 클래스를 상속받는 Album, Book, Movie 클래스가 있다고 가정한다.
type
- 조회 대상을 특정 자식으로 한정한다.
- 예시
- 전제 조건
- Item 중에 Book, Movie를 조회
- JPQL
select i from Item i where type(i) IN (Book, Movie)
- SQL
select i from i where i.DTYPE in ('B', 'M')
- 전제 조건
treat (JPA 2.1)
- 자바의 타입 캐스팅과 유사한 방식
- 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
- FROM절과 WHERE절에서 사용한다.
- 하이버네이트에서는 SELECT도 지원한다.
- 예시
- 전제 조건
- 부모인 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
- JPQL 둘다 같은 다음 SQL 실행
select count(m.id) as cnt from Member m
- JPQL
- 엔티티를 파라미터로 전달
String jpql = "select m from Member m where m = :member";
/*
[실행된 SQL]
select m.* from Member m where m.id=?
*/
List resultList =
em.createQuery(jpql)
.setParameter("member", member)
.getResultList();- 식별자를 직접 전달
String jpql = "select m from Member m where m.id = :memberId";
/*
[실행된 SQL]
select m.* from Member m where m.id=?
*/
List resultList =
em.createQuery(jpql)
.setParameter("memberId", memberId)
.getResultList();외래 키 값
Team team = em.find(Team.class, 1L);
String qlString = “select m from Member m where m.team = :team”;
/*
[실행된 SQL]
select m.* from Member m where m.team_id=?
*/
List resultList =
em.createQuery(qlString)
.setParameter("team", team)
.getResultList(); String qlString = “select m from Member m where m.team.id = :teamId”;
/*
[실행된 SQL]
select m.* from Member m where m.team_id=?
*/
List resultList =
em.createQuery(qlString)
.setParameter("teamId", teamId)
.getResultList();Named 쿼리
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL
- 정적 쿼리로만 사용할 수 있다.
- 파라미터를 설정하는 것은 가능하다.
- 미리 정의한 쿼리에 추가 내용을 덧붙일 수는 없다.
- 어노테이션이나 XML에 정의하여 사용한다.
- 애플리케이션 로딩 시점에 초기화 후 재사용
- 애플리케이션 로딩 시점에 JPA나 하이버네이트가 SQL로 파싱해서 캐시에 보유하고 있게 된다.
- 코스트가 엄청나게 줄어든다.
- 애플리케이션 로딩 시점에 쿼리를 검증한다.
- 테스트를 한 번이라도 하고 배포를 한다면 왠만하면 오류를 잡을 수 있다.
- XML을 통해 작성한 네임드 쿼리가 항상 우선권을 가진다.
- 애플리케이션 운영 환경에 따라 다른 XML을 배포할 수 있다.
어노테이션
//s:엔티티 정의
@Entity
@NamedQuery(
name = "Member.findByUsername",
query="select m from Member m where m.username = :username"
)
public class Member {
/* 내용 작성 */
}
//e:엔티티 정의
//s:테스트
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
//e:테스트XML
<!-- s:persistence.xml -->
<persistence-unit name="hello">
<mapping-file>META-INF/ormMember.xml</mapping-file>
</persistence-unit name="hello">
<!-- e:persistence.xml -->
<!-- s: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.name = :name
]]>
</query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>
<!-- e:ormMember.xml -->벌크 연산
- 여러 건의 데이터를 한꺼번에 UPDATE/DELETE하는 연산
- 단 건 데이터에 대한 경우는 포함되지 않는다.
- JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL이 실행된다.
- 만약에 변경된 데이터가 100건이라면 100번의 UPDATE SQL이 실행된다.
- 예시
- 전제 조건
- 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
- 동작 순서
- 재고가 10개 미만인 상품을 리스트로 조회한다.
- 상품 엔티티의 가격을 10% 증가한다.
- 트랜잭션 커밋 시점에 변경감지가 동작한다.
- 전제 조건
- executeUpdate()의 결과는 영향받은 엔티티 수를 반환한다.
UPDATE와DELETE를 지원한다.- 하이버네이트는
INSERT INTO SELECT도 지원한다.
- 하이버네이트는
- 주의점
- 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날린다.
- 해결법
- 벌크 연산을 먼저 실행
- 영속성 컨텍스트에 아무 작업이 되어 있지 않은 상태에서 실행한다.
- 벌크 연산 수행 후 영속성 컨텍스트 초기화
- 벌크 연산도 JPQL이 나가는 것이기 때문에 자동으로 flush가 된다.
- 데이터 불일치성이 일어날 수 있으니 벌크 연산 후에는 반드시 clear를 해준다.
- 벌크 연산을 먼저 실행
- UPDATE 예시
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();공통 코드
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); //애플리케이션 전체 공유 (persistence.xml 참조)
EntityManager em = emf.createEntityManager(); //한번 쓰고 버려야함, 쓰레드간 공유하지 않음
EntityTransaction tx = em.getTransaction(); //조회를 제외한 DML 작업시 필수로 사용
tx.begin();
try {
//실행 내용
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
em.close();
}
emf.close();출처
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.