[JPA] Chapter 10. 객체지향 쿼리 언어 1 - JPQL

joyful·2021년 8월 23일
2

JPA

목록 보기
14/18

들어가기 앞서

이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.


  • JPA는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리 기술을 지원
  • JPQL은 가장 중요한 객체지향 쿼리 언어이며, 나머지 쿼리 언어들은 JPQL을 편리하게 사용하도록 도와주는 기술

10.1 객체지향 쿼리 소개

  • JPA가 공식 지원하는 기능

    • JPQL(Java Persistence Query Language)
    • Criteria 쿼리(Criteria Query) : JPQL을 편하게 작성하도록 도와주는 API, 빌더 클래스 모음
    • 네이티브 SQL(Native SQL) : JPA에서 JPQL 대신 SQL 직접 사용
  • 그 외

    • QueryDSL
      • JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음
      • 비표준 오픈소스 프레임워크
    • 필요하면 JDBC 직접 사용 가능
      • JDBC 직접 사용
      • SQL 매퍼 프레임워크 사용 ex) MyBatis

10.1.1 JPQL 소개

JPQL(Java Persistence Query Language)

엔티티 객체를 조회하는 객체지향 쿼리


✅ 등장 배경

  • 기존의 검색 방법

    1. 식별자로 엔티티 조회 EntityManager.find()
    2. 조회한 엔티티에 객체 그래프 탐색 사용 ex) a.getB().getC()
    • 위의 기능만으로 애플리케이션을 개발하기는 어려움
  • JPQL 등장 배경

    • 검색하고 싶은 모든 엔티티를 메모리에 올려두고 애플리케이션에서 조건에 맞는 객체를 검색하는 것은 현실성 x
    • 데이터는 DB에 있으므로 SQL로 필요한 내용을 최대한 걸러서 조회해야 함
    • ORM을 사용하면 엔티티 객체를 대상으로 개발하므로, 검색도 테이블이 아닌 엔티티 객체를 대상으로 하는 방법 필요

✅ 특징

  • 문법이 SQL과 비슷함
  • ANSI 표준 SQL이 제공하는 기능을 유사하게 지원
  • 엔티티 객체를 대상으로 쿼리
  • SQL 추상화 → 특정 데이터베이스에 의존 x
    • 데이터베이스 방언(Dialect)만 변경하면 JPQL을 수정하지 않아도 데이터베이스 변경 가능
  • SQL보다 간결
    • 엔티티 직접 조회
    • 묵시적 조인
    • 다형성 지원
  • 결국은 SQL로 변환 됨

✅ 예제

💻 회원 엔티티

@Entity(name="Member")  //name 속성의 기본값은 클래스 명
public class Member {

    @Column(name = "name")
    private String username;
    //...
}

💻 JPQL 사용

//쿼리 생성
String jpql = "select m from Member as m where m.username = 'kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();

💻 실행한 JPQL

select m from Member as m where m.username = 'kim'

💻 실제 실행된 SQL

select
    member.id as id,
    member.age as age,
    member.team_id as team,
    member.name as name
from
    Member member
where
    member.name='kim'


10.2 JPQL

10.2.1 기본 문법과 쿼리 API

  • SELECT, UPDATE, DELETE문 사용 가능
  • INSERT문 x
    EntityManager.persist() 사용하여 엔티티 저장

💻 JPQL 문법

select_문 :: =
    select_절
    from_절
    [where_절]
    [groupby_절]
    [having_절]
    [orderby_절]
    
//벌크 연산
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]

SELECT

SELECT m FROM Member AS m where m.username = 'Hello'

📝 대소문자 구분

  • 엔티티와 속성 → 구분 o
    ex1) Membermember
    ex2) usernameUsername

  • JPQL 키워드 → 구분 x
    ex1) SELECTselect
    ex2) FROMfrom
    ex3) ASas

📝 엔티티 이름

  • Member → 클래스 명 x, 엔티티 명 o
  • 엔티티 명 지정 : @Entity(name="XXX")
  • 기본값 : 클래스 명 → 권장

📝 별칭 필수

  • JPQL은 별칭 사용 필수
  • 예시
SELECT username FROM Member m //잘못된 문법
                              //username을 m.username으로 고쳐야 함
  • AS : 생략 가능
    Member As m = Member m

💡 HQL(Hibernate Query Language)

  • 하이버네이트는 HQL를 제공
  • JPA 구현체로 하이버네이트 사용 시 HQL 사용 가능
  • 별칭 없이 사용 가능

💡 참고

JPA 표준 명세는 별칭을 식별 변수(Indeitification variale)로 정의


✅ TypeQuery, Query

  • 작성한 JPQL을 실행하기 위한 쿼리 객체
  • 차이점
    • TypeQuery : 반환 타입 명확하게 지정 가능
    • Query : 반환 타입 명확하게 지정 불가능

💻 TypeQuery 사용

TypedQuery<Member> query
    = em.createQuery("SELECT m FROM Member m", Member.class);

List<Member> resultList = query.getResultList();
for (Member member : resultList) {
    System.out.println("member = " + member);
}

💻 Query 사용

Query query
    = em.createQuery("SELECT m.username, m.age FROM Member m");

List<Member> resultList = query.getResultList();
for (Object o : resultList) {
    Object[] result = (Object[]) o;  //결과가 둘 이상이면 Object[] 반환
    System.out.println("username = " + result[0]);
    System.out.println("age = " + result[1]);
}
  • em.createQuery()
    • 두 번째 파라미터에 반환 타입 지정 → TypeQuery 반환
    • 두 번째 파라미터에 반환 타입 지정 x → Query 반환
  • Query 객체
    • SELECT 절에서 여러 엔티티나 컬럼을 선택 시 반환 타입이 명확하지 않으므로 사용
    • 조회 대상의 갯수에 따라 반환 타입이 달라짐
      • 둘 이상 : Object[]
      • 하나 : Object

✅ 결과 조회

  • query.getResultList() : 결과를 리스트로 반환
    • 결과 x → 빈 컬렉션 반환
  • query.getSingleResult() : 결과가 정확히 하나일 때 사용
    • 결과가 정확히 1개가 아니면 예외 발생
      • 결과 x → javax.persistence.NoResultException
      • 결과 1개 초과 → javax.persistence.NonUniqueResultException


10.2.2 파라미터 바인딩

📊 파라미터 바인딩 지원 범위

기준JDBCJPQL
위치OO
이름XO

✅ 이름 기준 파라미터(Named Parameters)

  • 파라미터를 이름으로 구분
  • 파라미터 앞에 : 사용 ex) :username
String usernameParam = "User1";

TypedQuery<Member> query =
    em.createQuery("SELECT m FROM Member m where m.username = :username",
Member.class);
    
query.setParameter("username", usernameParam);
List<Member> resultList = query.getResultList();

  • 연속 작성 가능
    → JPQL API가 메소드 체인 방식으로 설계되어 있기 때문
List<Member> members =
    em.createQuery("SELECT m FROM Member m WHERE m.username = :username",
Member.class)
    .setParameter("username", usernameParam)
    .getResultList();

✅ 위치 기준 파라미터(Positional Parameters)

  • ? 다음에 위치 값 지정
List<Member> members =
    em.createQuery("SELECT m FROM Member m WHERE m.username = ?1", Member.class)
        .setParameter(1, usernameParam)
        .getResultList();

💡 참고

  • 위치 기준 파라미터 방식보다 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확함

💡 참고

  • 파라미터 바인딩 방식은 필수
  • 장점
    • SQL 인젝션 공격 방지
    • 애플리케이션과 데이터베이스 모두 해당 쿼리의 파싱 결과 재사용 가능
      → 전체 성능 향상


10.2.3 프로젝션(Projection)

SELECT 절에 조회할 대상 지정

  • 방법 : [SELECT {프로젝션 대상} FROM]
  • 프로젝션 대상
    • 엔티티
    • 임베디드(Embedded) 타입
    • 스칼라(Scala) 타입 → 기본 데이터 타입 ex) 숫자, 문자, 날짜 등

✅ 엔티티 프로젝션

SELECT m FROM Member m  //회원
SELECT m.team FROM Member m //팀
  • 원하는 객체를 바로 조회
  • 조회한 엔티티는 영속성 컨텍스트에서 관리

✅ 임베디드 타입 프로젝션

  • 엔티티와 거의 비슷
  • 조회의 시작점이 될 수 없음
  • 엔티티 타입(x), 값 타입(o)
    영속성 컨텍스트에서 관리되지 않음

💻 예제

String query = "SELECT o.adress FROM Order o";
List<Address> addresses = em.createQuery(query, Address.class)
                            .getResultList();
//실행된 SQL
select
    order.city,
    order.street,
    order.zipcode
from
    Orders order

✅ 스칼라 타입 프로젝션

💻 전체 회원 이름 조회

List<String> usernames =
    em.createQuery("SELECT username FROM Member m", String.class)
      .getResultList();

💻 중복 데이터 제거 DISTINCT

SELECT DISTINCT username FROM Member m

💻 통계 쿼리

Double orderAmountAvg =
    em.createQuery("SELECT AVG(o.orderAmount) FROM Order o", Double.class)
      .getSingleResult();

✅ 여러 값 조회

  • 꼭 필요한 데이터들만 선택해서 조회해야 할 경우 존재
  • 프로젝션에 여러 값 선택 시 Query 사용
  • 조회한 엔티티는 영속성 컨텍스트에서 관리

💻 여러 프로젝션

Query query =
    em.createQuery("SELECT m.username, m.age FROM Member m");
List resultList = query.getResultList();

Iterator iterator = resultList.iterator();
while(iterator.hasNext()) {
    Object[] row = (Object[]) iterator.next();
    String usernmae = (String) row[0];
    Integer age = (Integer) row[1];
}

💻 여러 프로젝션 Object[]로 조회

List<Object[]> resultList =
    em.createQuery("SELECT m.username, m.age FROM Member m")
      .getResultList();
      
for (Object[] row : resultList) {
    String username = (String) row[0];
    Integer age = (Integer) row[1];
}

💻 여러 프로젝션 엔티티 타입 조회

List<Object[]> resultList =
    em.createQuery("SELECT o.member, o.product, o.orderAmount FROM Order o")
      .getResultList();
      
for (Object[] row : resultList) {
    Member member = (Member) row[0];  //엔티티
    Product product = (Product) row[1];  //엔티티
    int orderAmount = (Integer) row[2];   //스칼라
}

NEW 명령어

💻 NEW 명령어 사용 전

List<Object[]> resultList =
    em.createQuery("SELECT m.username, m.age FROM Member m")
      .getResultList();

//객체 변환 작업
List<UserDTO> userDTOs = new ArrayList<>();
for (Object[] row : resultList) {
    UserDTO userDTO = new UserDTO((String)row[0], (Integer)row[1]);
    userDTOs.add(userDTO);
}
return userDTOs;
/**
 * UserDTO
 */
public class UserDTO {

    private String username;
    private int age;
    
    public UserDTO(String username, int age) {
        this.username = username;
        this.age = age;
    }
    //...
}

💻 NEW 명령어 사용 후

TypedQuery<UserDTO> query =
    em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age)
    FROM Member m", UserDTO.class);
    
List<UserDTO> resultList = query.getResultList();
  • SELECT 다음 NEW 명령어 사용하여 반환받을 클래스 지정
    → 클래스의 생성자에 JPQL 조회 결과 넘겨줄 수 있음
  • TypeQuery 사용 가능 → 지루한 객체 변환 작업 감소
  • 주의 사항
    1. 패키지 명을 포함한 전체 클래스 명 입력
    2. 순서와 타입이 일치하는 생성자 필요


10.2.4 페이징 API

  • 종류
    • setFirstResult(int startPostion) : 조회 시작 위치(0부터 시작)
    • setMaxResults(int maxResult) : 조회할 데이터 수
TypedQuery<Member> query =
    em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);
    
query.setFirstResult(10);  //11번째부터 시작
query.setMaxResults(20);  //총 20건
query.getResultList();  //11~30번 데이터 조회
  • 데이터베이스마다 페이징을 처리하는 SQL 문법이 다름
    → 데이터베이스 방언(Dialect)에 의해 같은 API로 페이징 처리 가능
  • 페이징 쿼리는 정렬조건이 중요


10.2.5 집합과 정렬

  • 집합 → 집합함수와 함께 통계 정보를 구할 때 사용

✅ 집합 함수

함수설명반환 타입
COUNT결과 갯수 반환Long
MAX, MIN◾ 최대, 최소 값 반환
◾ 문자, 숫자, 날짜 등에 사용
AVG◾ 평균값 반환
◾ 숫자타입만 사용 가능
Double
SUM◾ 합계 반환
◾ 숫자타입만 사용 가능
◾ 정수합 : Long
◾ 소수합 : Double
◾ BigInteger합 : BigInteger
◾ BigDecimal합 : BigDecimal

✅ 집합 함수 사용 시 참고사항

  • NULL 값은 무시하므로 통계에 잡히지 않음
    (DISTINCT가 정의되어 있어도 무시)
  • 값이 없는 경우
    • SUM, AVG, MAX, MINNULL
    • COUNT → 0
  • DISTINCT를 집합 함수 안에 사용하여 중복 값 제거 후 집합 구할 수 있음
    ex) select COUNT(DISTINCT m.age) from Member m
  • DISTINCTCOUNT에서 사용할 때 임베디드 타입 지원 x

✅ GROUP BY, HAVING

📝 GROUP BY

  • 통계 데이터를 구할 때 특정 그룹끼리 묶어줌
  • 문법 : groupby_절 ::= GROUP BY {단일값 경로 | 별칭}+
//팀 이름 기준
select t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
from Member m LEFT JOIN m.team t
GROUP BY t.name

📝 HAVING

  • GROUP BY와 함께 사용
  • GROUP BY로 그룹화 한 통계 데이터를 기준으로 필터링
  • 문법 : having_절 ::= HAVING 조건식
select t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
from Member m LEFT JOIN m.team t
GROUP BY t.name
HAVING AVG(m.age) >= 10

📝 통계 쿼리(=리포팅 쿼리)

  • 통계를 내기 위한 쿼리
  • 장점 : 코드 수 감소
  • 단점 : 전체 데이터를 기준으로 처리 → 실시간 사용에는 적합하지 않음

    결과가 아주 많은 경우, 통계 결과만 저장하는 테이블을 별도로 만들어 두고, 사용자가 적은 새벽에 통계 쿼리를 실행해서 결과 보관(권장)


✅ 정렬(ORDER BY)

  • 결과 정렬시 사용
  • 문법 : orderby_절 ::= ORDER BY {상태필드 경로 | 결과 변수 [ASC | DESC]}+
    • 상태필드 : 객체의 상태를 나타내는 필드
    • 결과 변수 : SELECT 절에 나타나는 값
    • ASC : 오름차순(기본값)
    • DESC : 내림차순


💻 예제 1

select m from Member m order by m.age DESC, m.username ASC

💻 예제 2

select t.name, COUNT(m.age) as cnt  //cnt : 결과 변수
from Member m LEFT JOIN m.team t
GROUP BY t.name //t.name : 상태필드
ORDER BY cnt


10.2.6 JPQL 조인

✅ 내부 조인

  • INNER JOIN 사용
    INNER 생략 가능

💻 내부 조인 사용 예

String teamName = "팀A";
String query = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName";

List<Member> members = em.createQuery(query, Member.class)
    .setParameter("teamName", teamName)
    .getResultList();

💻 생성된 내부 조인 SQL

SELECT
    M.ID AS ID,
    M.AGE AS AGE,
    M.TEAM_ID AS TEAM_ID,
    M.NAME AS NAME
FROM
    MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID
WHERE
    T.NAME=?
  • 연관 필드(m.team) 사용
    • FROM Member m : 회원을 선택하고 별칭 m 설정
    • Member m JOIN m.team t
      • 회원이 가지고 있는 연관 필드로 팀과 조인
      • 조인한 팀에 별칭 t 설정
  • JPQL은 JOIN 명령어 다음에 조인할 객체의 연관 필드 사용
    SQL 조인처럼 사용하면 문법 오류 발생
    ex) FROM Member m JOIN Team t //잘못된 JPQL 조인, 오류 발생

💻 조인한 두 개의 엔티티 조회

//JPQL 작성
SELECT m, t
FROM Member m JOIN m.team t

//조회
List<Object[]> result = em.createQuery(query).getResultList();

for ( Object[] row : result) {
    Member member = (Member) row[0];
    Team team = (Team) row[1];
}

✅ 외부 조인

💻 외부 조인 JPQL

SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
  • OUTER 생략 가능 → 보통 LEFT JOIN으로 사용

💻 JPQL 실행

SELECT
    M.ID AS ID,
    M.AGE AS AGE,
    M.TEAM_ID AS TEAM_ID,
    M.NAME AS NAME
FROM
    MEMBER M LEFT OUTER JOIN TEAM T ON M.TEAM_ID=T.ID
WHERE
    T.NAME=?

✅ 컬렉션 조인

컬렉션을 사용하는 곳에 조인하는 것 ex) 일대다 관계, 다대다 관계

  • [회원 → 팀]
    • 다대일 조인
    • 단일 값 연관 필드(m.team) 사용
  • [팀 → 회원]
    • 일대다 조인
    • 컬렉션 값 연관 필드(m.members) 사용

💻 예제

//팀과 팀이 보유한 회원목록을 컬렉션 값 연관 필드로 외부 조인
SELECT t, m FROM Team t LEFT JOIN t.members m

💡 IN 명령어

  • 컬렉션 조인 시 JOIN 대신 사용 가능
    ex) SELECT t, m FROM TEAM t, IN(t.members) m
  • 기능상 JOIN과 같지만 컬렉션일 때만 사용 가능
  • 과거 EJB 시절의 유물, 특별한 장점 x
    JOIN 명령어 사용 권장

✅ 세타 조인

조인에 참여하는 두 릴레이션의 속성 값을 비교하여 조건을 만족하는 튜플만 반환하는 조인

  • WHERE절을 이용하여 사용
  • 내부 조인만 지원
  • 전혀 관계없는 엔티티도 조인 가능

💻 회원 이름이 팀 이름과 똑같은 사람 수를 구하는 예

//JPQL
select count(m) from Member m, Team t
where m.username = t.name //전혀 관련 없는 엔티티

//SQL
SELECT COUNT(M.ID)
FROM
    MEMBER M CROSS JOIN TEAM T
WHERE
    M.USERNAME=T.NAME

✅ JOIN ON 절(JPA 2.1)

  • JPA 2.1부터 조인 시 지원
  • 조인 대상 필터링 및 조인 가능
  • 내부 조인의 ON 절은 WHERE절을 사용할 때와 결과가 같음
    → 보통 외부 조인에서만 사용

💻 예제

//JPQL
select m, t from Member m  //모든 회원 조회하면서 회원과 연관된 팀 같이 조회
left join m.team t on t.name = 'A'  //팀 이름이 A인 팀만 조회

//SQL
SELECT m.*, t.* FROM Member m
LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'  //조인 시점에 조인 대상 필터링


10.2.7 페치 조인(Fetch Join)

  • JPQL에서 성능 최적화를 위해 제공하는 기능
    (SQL에서의 페치 조인과 다른 개념)
  • 연관된 엔티티나 컬렉션을 한 번에 같이 조회
  • 명령어 : join fetch
  • 문법 : 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로

✅ 엔티티 페치 조인

💻 예제

//JPQL 작성
//회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회
//별칭 사용 할 수 없음(하이버네이트는 허용)
String jpql = "select m from Member m join fetch m.team";

List<Member> members = em.createQuery(jpql, Member.class)
                         .getResult();

for (Member member : members) {
    //페치 조인으로 회원과 팀을 함께 조회하므로 지연 로딩 발생 x
    System.out.println("username = " + member.getUsername() + ", " +
        "teamname = " + member.getTeam().name());
}
  • 회원 조회 시 페치 조인을 사용하여 팀도 함께 조회
    → 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티
    • 연관된 팀을 사용해도 지연 로딩 발생 x
    • 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀 조회 가능

💻 실행된 SQL

SELECT
    M.*, T.*
FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID

💻 출력 결과

username = 회원1, teamname = 팀A
username = 회원2, teamname = 팀A
username = 회원3, teamname = 팀B

✅ 컬렉션 페치 조인

💻 컬렉션 페치 조인 사용

//JPQL
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'";
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();

for (Team team : teams) {
    System.out.println("teamname = " + team.getName() + ", team = " + team);
    
    for (Member member : team.getMembers()) {
        //페치 조인으로 팀과 회원을 함께 조회 → 지연 로딩 발생 x
        System.out.println(
            "->username = " + member.getUsername() + ", member = " + member
        );
    }
}
  • 팀(t)을 조회하면서 연관된 회원 컬렉션(t.members)도 함께 조회

💻 실행된 SQL

SELECT
    T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'

💻 출력 결과

teamname = 팀A, team = Team@0x100
->username = 회원1, member = Member@0x200
->username = 회원2, member = Member@0x300
teamname = 팀A, team = Team@0x100
->username = 회원1, member = Member@0x200
->username = 회원2, member = Member@0x300
  • TEAM 테이블에서 팀A는 하나지만 MEMBER 테이블과 조인하면서 결과 증가
    → '팀A'가 2건 조회 됨

    💡 참고

    일대일, 다대일 조인은 결과가 증가하지 않음


✅ 페치 조인과 DISTINCT

SQL에서 중복 결과 제거 + 애플리케이션에서 중복 결과 제거

💻 예제

select distinct t from Team t join fetch t.members where t.name = '팀A'
  1. SQL에 SELECT DISTINCT 추가됨
    → 각 로우의 데이터가 달라 효과 x
  2. 애플리케이션에서 distinct 명령어를 보고 중복 데이터 거름
    → 중복된 엔티티 제거로 결과는 하나만 조회됨

💻 출력 결과

teamname = 팀A, team = Team@0x100
->username = 회원1, member = Member@0x200
->username = 회원2, member = Member@0x300

✅ 페치 조인과 일반 조인의 차이

  • 일반 조인 : 연관된 엔티티 조회 x

    • JPQL은 결과를 반환할 때 연관관계까지 고려하지 않으며 SELECT 절에 지정한 엔티티만 조회하기 때문
    • 지연 로딩 → 프록시아직 초기화하지 않은 컬렉션 래퍼 반환
    • 즉시 로딩 → 즉시 로딩을 위해 쿼리를 한 번 더 실행
  • 페치 조인 : 연관된 엔티티 함께 조회


✅ 페치 조인의 특징과 한계

📝 특징

  • 성능 최적화

    • SQL 한 번으로 연관된 엔티티 함께 조회 → SQL 호출 횟수 감소
  • 글로벌 로딩 전략보다 우선

    📕 글로벌 로딩 전략

    • 엔티티에 직접 적용하는 로딩 전략 → 애플리케이션 전체에 영향 미침
      ex) @OneToMany(fetch = FetchType.LAZY)
    • 즉시 로딩으로 설정 시 성능에 악영향 미칠 수 있음
      • 애플리케이션 전체에서 항상 즉시 로딩 발생
      • 사용하지 않는 엔티티를 자주 로딩하게 됨
    • 될 수 있으면 지연 로딩을 사용하고 최적화가 필요한 경우 페치 조인 적용 권장
  • 연관된 엔티티를 쿼리 시점에 조회 → 지연 로딩 발생 x
    ∴ 준영속 상태에서도 객체 그래프 탐색 가능


📝 한계

  • 페치 조인 대상에는 별칭을 줄 수 없음

    • SELECT절, WHERE절, 서브 쿼리에 페치 조인 대상 사용 x
    • JPA 표준에서는 지원하지 않음
    • 예외) 하이버네이트를 포함한 몇몇 구현체들은 지원
      • 별칭을 잘못 사용할 경우, 데이터 무결성이 깨질 수 있음
      • 특히 2차 캐시와 함께 사용 시 조심해야 함
        ex) 연관된 데이터 수가 달라진 상태에서 2차 캐시에 저장
        → 다른 곳에서 조회할 때도 연관된 데이터 수가 달라짐
  • 둘 이상의 컬렉션을 페치할 수 없음

    • 구현체에 따라 가능한 경우도 존재
      → 컬렉션 * 컬렉션의 카테시안 곱이 만들어지므로 주의
    • 하이버네이트 사용 시 예외 발생

      "javax.persistence.PersistenceException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags"

  • 컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults) 사용 불가

    • 단일 값 연관 필드(일대일, 다대일)들은 페치 조인을 사용해도 페이징 API 사용 가능
    • 하이버네이트
      • 경고 로그 남기면서 메모리에서 페이징 처리
      • 데이터가 많으면 성능 이슈와 메모리 초과 예외가 발생할 수 있어 위험

💡 참고

  • 객체 그래프를 유지하는 경우 → 페치 조인
  • 여러 테이블을 조인하여 엔티티가 가진 모양과 전혀 다른 결과를 내야하는 경우
    → 여러 테이블에서 필요한 필드들만 조회하여 DTO로 반환


10.2.8 경로 표현식(Path Expression)

.(점)을 찍어 객체 그래프를 탐색하는 방법
ex) m.username, m.team


✅ 경로 표현식의 용어

  • 상태 필드(state field) : 단순히 값을 저장하기 위한 필드 또는 프로퍼티
  • 연관 필드(association field) : 연관관계를 위한 필드 또는 프로퍼티
    • 임베디드 타입 포함
    • 종류
      • 단일 값 연관 필드
        • @ManyToOne, @OneToOne
        • 대상 : 엔티티
      • 컬렉션 값 연관 필드
        • @OneToMany, @ManyToMany
        • 대상 : 컬렉션

💻 예제

@Entity
public class Member {

    @Id @GeneratedValue
    private Long id;
    
    @Column(name = "name")
    private String username;  //상태 필드
    private Integer age;  //상태 필드
    
    @ManyToOne(..)
    private Team team;  //연관 필드 - 단일 값 연관 필드
    
    @OneToMany(...)
    private List<Order> orders;  //연관 필드 - 컬렉션 값 연관 필드
}

✅ 경로 표현식과 특징

종류설명탐색
상태 필드 경로경로 탐색의 끝X
단일 값 연관 경로묵시적 내부 조인O
컬렉션 값 연관 경로묵시적 내부 조인X (단, FROM 절에서 조인을 통해
별칭을 얻으면 별칭으로 탐색 가능)

💡 참고

  • 명시적 조인 : JOIN을 직접 적어주는 것
    ex) SELECT m FROM Member m JOIN m.team t
  • 묵시적 조인
    • 경로 표현식에 의해 묵시적으로 조인이 일어나는 것
      ex) SELECT m.team FROM Member m
    • 내부 조인(INNER JOIN)만 가능

📝 상태 필드 경로 탐색

//JPQL
select m.username, m.age from Member m

//SQL
select m.name, m.age
from Member m
  • m.username, m.age → 상태 필드 경로 탐색

📝 단일 값 연관 경로 탐색

//JPQL
select o.member from Order o

//SQL
select m.*
from Orders o
    inner join Member m on o.member_id=m.id
  • o.member → 주문에서 회원으로 단일 값 연관 필드로 경로 탐색

📝 컬렉션 값 연관 경로 탐색

select t.members from Team t  //성공
select t.members.username from Team t  //실패

//조인을 사용해서 새로운 별칭 획득 → 컬렉션 경로 탐색 가능
select m.username from Team t join t.members m

//size : 컬렉션의 크기를 구할 수 있는 기능
//COUNT 함수를 사용하는 SQL로 변환
select t.members.size from Team t

✅ 경로 탐색을 사용한 묵시적 조인 시 주의사항

  • 항상 내부 조인이다.
  • 컬렉션은 경로 탐색의 끝이다. 컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 한다.
  • 경로 탐색은 주로 SELECT절, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM절에 영향을 준다.
  • 조인이 일어나는 상황을 한눈에 파악하기 어렵다.
    → 성능이 중요하면 분석하기 쉽도록 명시적 조인을 사용하는 것을 권장


10.2.9 서브 쿼리

  • 제약 사항
    • WHERE절, HAVING절에서만 사용 가능
    • SELECT절, FROM절에서는 사용 불가

      💡 참고

      • 하이버네이트의 HQL은 SELECT절의 서브 쿼리도 허용
      • 일부 JPA 구현체는 FROM 절의 서브 쿼리도 지원

💻 예제

//나이가 평균보다 많은 회원 조회
select m from Member m
where m.age > (select avg(m2.age) from Member m2)

//한 건이라도 주문한 고객 조회
select m from Member m
where (select count(o) from Order o where m = i.member) > 0

//size 기능을 사용한 쿼리(위의 쿼리와 같음)
select m from Member m
where m.orders.size > 0

✅ 서브 쿼리 함수

📝 EXISTS

  • 문법 : [NOT] EXISTS (subquery)
  • 설명 : 서브쿼리에 결과가 존재하면 참
    • NOT → 반대
      ex) 팀A 소속인 회원

📝 {ALL | ANY | SOME}

  • 문법 : {ALL | ANY | SOME} (subquery)
  • 설명 : 비교 연산자와 같이 사용(=, >, >=, <, <=, <>)
    • ALL : 조건을 모두 만족하면 참
    • ANY 혹은 SOME : 조건을 하나라도 만족하면 참
      ex1) 전체 상품 각각의 재고보다 주문량이 많은 주문들

      ex2) 어떤 팀이든 팀에 소속된 회원

📝 IN

  • 문법 : [NOT] IN (subquery)
  • 설명 : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
    • 서브쿼리가 아닌 곳에서도 사용
      ex) 20세 이상을 보유한 팀


10.2.10 조건식

✅ 타입 표현

종류설명예제
문자◾ 작은 따옴표 사이에 표현
◾ 작은 따옴표를 표현하고 싶으면
   작은 따옴표 연속 두 개('') 사용
'HELLO'
'She''s'

숫자◾ L(Long 타입 지정)
◾ D(Double 타입 지정)
◾ F(Float 타입 지정)
10L
10D
10F
날짜◾ DATE {d 'yyyy-mm-dd'}
◾ TIME {t 'hh-mm-ss'}
◾ DATETIME {ts 'yyyy-mm-dd hh:mm:ss.f'}

{d '2012-03-24'}
{t '10-11-11'}
{ts '2012-03-24 10:11:11.123'}
m.createDate = {d '2012-03-24'}
BooleanTRUE, FALSE
Enum패키지명을 포함한 전체 이름 사용해야 함jpabook.MemberType.Admin
엔티티 타입◾ 엔티티 타입 표현
◾ 주로 상속 관련하여 사용
TYPE(m) = Member

✅ 연산자 우선 순위

  1. 경로 탐색 연산 : .
  2. 수학 연산 : +, -(단항 연산자), *, /, +, -
  3. 비교 연산 : =, >, >=, <, <=, <>(다름), [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF], [NOT] EXISTS
  4. 논리 연산 : NOT, AND , OR

✅ 논리 연산과 비교식

📝 논리 연산

  • AND : 둘 다 만족하면 참
  • OR : 둘 중 하나만 만족해도 참
  • NOT : 조건식의 결과 반대

📝 비교식

=, >, >=, <, <=, <>


Between, IN, Like, NULL 비교

📝 Between

  • 문법 : X [NOT] BETWEENT A AND B
  • 설명 : X가 A ~ B 사이의 값이면 참(A, B 값 포함)
    ex) 나이가 10~20인 회원 조회

📝 IN

  • 문법 : x [NOT] IN (예제)
  • 설명 : X와 같은 값이 예제에 하나라도 있으면 참
    • 서브쿼리 사용 가능
      ex) 이름이 회원1 혹은 회원2인 회원 조회

📝 Like

  • 문법 : 문자표현식 [NOT] LIKE 패턴값 [ESCAPE 이스케이프문자]

    💡 이스케이프(Escape) 문자

    이스케이프 시퀀스를 따르는 문자들로서 다음 문자가 특수 문자임을 알리는 백슬래시()를 사용한다.
    ex) \n → 개행, \t → 탭(tab)

  • 설명 : 문자표현식과 패턴값 비교

    • %(퍼센트) : 아무 값들이 입력되어도 됨(값이 없어도 됨)
    • _(언더라인) : 한 글자는 아무 값이 입력되어도 되지만 값이 존재해야 함
//중간에 원이라는 단어가 들어간 회원(좋은회원, 회원, 원)
select m from Member m
where m.username like '%원%'

//처음에 회원이라는 단어가 포함(회원1, 회원ABC)
where m.username like '회원%'

//마지막에 회원이라는 단어가 포함(좋은 회원, A회원)
where m.username like '%회원'

//회원A, 회원1
where m.usernmae like '회원_'

//회원3
where m.username like '__3'

//회원%
where m.usernmae like '회원\%' ESCAPE '\'

📝 NULL 비교식

  • 문법 : {단일값 경로 | 입력 파라미터} IS [NOT] NULL
  • 설명 : NULL인지 비교
    • IS NULL 사용 필수(= 사용 x)

✅ 컬렉션 식

컬렉션에서만 사용하는 특별한 기능


📝 빈 컬렉션 비교 식

  • 문법 : {컬렉션 값 연관 경로} IS [NOT] EMPTY
  • 설명 : 컬렉션에 값이 비어있으면 참
//JPQL : 주문이 하나라도 있는 회원 조회
select m from Member m
where m.orders is not empty

//select m from Member m where m.orders is null → 오류 발생

//실행된 SQL
select m.* from Member m
where
    exist (
        select o.id
        from Orders o
        where m.id=o.member_id
    )

📝 컬렉션의 멤버 식

  • 문법 : `{엔티티나 값} [NOT] MEMBER [OF] {컬렉션 값 연관 참조}
  • 설명 : 엔티티나 값이 컬렉션에 포함되어 있으면 참
select t from Team t
where :memberParam member of t.members

✅ 스칼라 식

💡 스칼라(Scala)

가장 기본적인 타입들
ex) 숫자, 문자, 날짜, case, 엔티티 타입(엔티티의 타입 정보) 등


📝 수학 식

  • +, - : 단항 연산자
  • *, /, +, - : 사칙 연산

📝 문자함수

함수설명예제
CONCAT(문자1, 문자2, ...)◾ 문자를 합함
◾ HQL에서는 ‖로 대체 가능
CONCAT('A','B') = AB
SUBSTRING(문자, 위치, [길이])◾ 위치부터 시작해 길이만큼 문자를 구함
◾ 길이 값 x → 나머지 전체 길이
SUBSTRING('ABCDEF', 2, 3) = BCD
TRIM([[LEADING │
TRAILING │ BOTH]
[트림문자] FROM] 문자)
◾ 트림 문자 제거
  - LEADING : 왼쪽만
  - TRAILING : 오른쪽만
  - BOTH : 양쪽 다(기본값)
◾ 트림 문자의 기본값 → 공백(SPACE)
TRIM(' ABC ') = 'ABC'
LOWER(문자)소문자로 변경LOWER('ABC') = 'abc'
UPPER(문자)대문자로 변경UPPER('abc') = 'ABC'
LENGTH(문자)문자 길이LENGTH('ABC') = 3
LOCATE(찾을 문자,
원본 문자, [검색시작위치])
검색위치부터 문자를 검색
◾ 1부터 시작
◾ 못 찾으면 0 반환
LOCATE('DE', 'ABCDEFG') = 4

📝 수학함수

함수설명예제
ABS(수학식)절대값 구함ABS(-10) = 10
SQRT(수학식)제곱근 구함SQRT(4) = 2.0
MOD(수학식, 나눌 수)나머지 구함MOD(4,3) = 1
SIZE(컬렉션 값 연관 경로식)컬렉션의 크기 구함SIZE(t.members)
INDEX(별칭)LIST 타입 컬렉션의 위치값을 구함
◾ 컬렉션이 @OrderColumn을 사용하는
   LIST 타입일 때만 사용 가능
t.members m where INDEX(m) > 3

📝 날짜함수

데이터베이스의 현재 시간 조회

  • 종류

    함수설명
    CURRENT_DATE현재 날짜
    CURRENT_TIME현재 시간
    CURRENT_TIMESTAMP현재 날짜 시간
/**
 * ex1) 현재 날짜, 현재 시간, 현재 날짜 시간 차례대로 출력
 */
select CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP from Team t
//결과 : 2013-08-19, 23:38:17, 2013-08-19 23:38:17.736

/**
 * ex2) 종료 이벤트 조회
 */
select e from Event e where e.endDate < CURRENT_DATE

  • 하이버네이트에서 지원하는 기능

    함수설명
    YEAR
    MONTH
    DAY
    HOUR시간
    MINUTE
    SECOND
// 하이버네이트 지원 기능 사용
select year(CURRENT_TIMESTAMP), month(CURRENT_TIMESTAMP), day(CURRENT_TIMESTAMP)
from Member

CASE

특정 조건에 따라 분기할 때 사용


📝 기본 CASE

문법

CASE
    {WHEN <조건식> THEN <스칼라식>}+
    ELSE <스칼라식>
END

② 예제

select
    case when m.age <= 10 then '학생요금'
         when m.age >= 60 then '경로요금'
         else '일반요금'
    end
from Member m

💡 표준 명세 문법 정의

CASE when_절 {when_절}* ELSE 스칼라식 END
When_::= WHEN 조건식 THEN 스칼라식

📝 심플 CASE

  • 조건식 사용 불가능
  • 단순한 문법
  • 자바의 switch case 문과 비슷

문법

CASE <조건대상>
    {WHEN <스칼라식1> THEN <스칼라식2>}+
    ELSE <스칼라식>
END

② 예제

select
    case t.name
        when '팀A' then '인센티브110%'
        when '팀B' then '인센티브120%'
        else '인센티브105%'
    end
from Team t

💡 표준 명세 문법 정의

CASE case_피연산자 심플_when_절 {심플_when_절}* ELSE 스칼라식 END
case_피연산자::= 상태 필드 경로식 | 타입 구분자
심플_when_절::= WHEN 스칼라식 THEN 스칼라식

📝 COALESCE

  • 문법 : COALESCE(<스칼라식> {, <스칼라식>}+)
  • 설명 : 스칼라식을 차례대로 조회해서 null이 아니면 반환
//m.username이 null이면 '이름 없는 회원' 반환
select coalesce (m.username, '이름 없는 회원') from Member m

📝 NULLIF

  • 문법 : NULLIF (<스칼라식>, <스칼라식>)
  • 설명
    • 두 값이 같으면 NULL 반환
    • 두 값이 다르면 첫 번째 값 반환
    • 보통 집합 함수와 함께 사용
//사용자 이름이 '관리자'면 null 반환, 나머지는 본인의 이름 반환
select NULLIF (m.username, '관리자') from Member m


10.2.11 다형성 쿼리

  • JPQL로 부모 엔티티를 조회하면 자식 엔티티도 함께 조회 됨

💻 다형성 쿼리 엔티티

@Entity
@Inheritance(starategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {...}

@Entity
@DiscriminatorValue("B")
public class Book extends Item {
    ...
    private String author;
}

//Album, Movie 생략
  • Item의 자식 → Book, Album, Movie

💻 JPQL

// Item의 자식도 함께 조회
List resultList = em.createQuery("select i from Item i").getResultList();

💻 단일 테이블 전략(InheritanceType.SINGLE_TABLE) 사용

//SQL
SELECT * FROM ITEM

💻 조인 전략(InheritanceType.JOINED) 사용

//SQL
SELECT
    i.ITEM_ID, i.DTYPE, i.name, i.price, i.stockQuantity,
    b.author, b.isbn,
    a.artist, a.etc,
    m.actor, m.director
FROM
    Item i
left outer join
    Book b on i.ITEM_ID=b.ITEM_ID
left outer join
    Album a on i.ITEM_ID=a.ITEM_ID
left outer join
    Movie m on i.ITEM_ID=m.ITEM_ID

TYPE

엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 사용

//Item 중에 Book, Movie 조회

//JPQL
select i from Item i
where type(i) IN (Book, Movie)

//SQL
SELECT i FROM Item i
WHERE i.DTYPE in ('B', 'M')

TREAT(JPA 2.1)

상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용

  • ≒ 자바의 타입 캐스팅
  • JPA 2.1에 추가된 기능
  • JPA 표준 → FROM, WHERE절에서 사용 가능
  • 하이버네이트 → FROM, WHERE, SELECT절에서 사용 가능
// 부모 : Item, 자식 : Book

//JPQL - 부모 타입인 Item을 자식 타입인 Book으로 변환
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'


10.2.12 사용자 정의 함수 호출(JPA 2.1)

  • JPA 2.1부터 사용자 정의 함수 지원
  • 문법 : function_invocation::= FUNCTION(function_name {, function_arg}*)
//예제
select function('group_concat', i.name) from Item i
  • 하이버네이트 구현체 사용
    • 방법
      1. 방언 클래스를 상속해서 구현하고 사용할 데이터베이스 함수를 미리 등록
      2. hibernate.dialect에 해당 방언 등록
    • 축약해서 사용 가능


10.2.13 기타 정리

  • enum= 비교 연산만 지원
  • 임베디드 타입 → 비교 지원 x

‘’

  • JPA 표준 : 길이 0인 Empty String으로 지정
  • 데이터베이스에 따라 NULL로 사용하는 경우도 존재

NULL 정의

  • 조건을 만족하는 데이터가 하나도 없으면 NULL
  • 알 수 없는 값(unknown value)
  • NULL과의 모든 수학적 계산 결과는 NULL
  • Null == Null → 알 수 없는 값
  • Null is Null → 참

📝 JPA 표준 명세 - Null(U), TRUE(T), FALSE(F)의 논리 계산 정의

  • AND 연산

    ANDTFU
    TTFU
    FFFF
    UUFU
  • OR 연산

    ORTFU
    TTTT
    FTFU
    UTUU
  • NOT 연산

    NOT
    TF
    FT
    UU


10.2.14 엔티티 직접 사용

✅ 기본 키 값

💡 식별 기준

  • 객체 인스턴스 : 참조 값
  • 테이블 로우 : 기본 키 값
    → JPQL에서 엔티티 객체 직접 사용 시 SQL에서는 해당 엔티티의 기본 키 값 사용

💻 엔티티를 파라미터로 직접 받는 코드

//JPQL
String qlString = "select m from Member m where m = :member";
List resultList = em.createQuery(qlString)
                    .setParameter("member", member)
                    .getResultList();
                    
//실행된 SQL
select m.*
from Member m
where m.id=?
  • JPQL의 엔티티 직접 사용 부분이 SQL의 기본 키 값 사용 부분으로 변환
    where m = :memberwhere m.id=?

💻 식별자 값을 직접 사용하는 코드

String qlString = "select m from Member m where m.id = :memberId";
List resultList = em.createQuery(qlString)
                    .setParameter("memberId", 4L)
                    .getResultList();
                    
//실행된 SQL
select m.*
from Member m
where m.id=?

✅ 외래 키 값

💻 외래 키 대신에 엔티티를 직접 사용하는 코드

Team team = em.find(Team.class, 1L);

String qlString = "select m from Member m where m.team = :team";
List resultList = em.createQuery(qlString)
                    .setParameter("team", team)
                    .getResultList();
                    
//실행된 SQL
select m.*
from Member m
where m.team_id=?(팀 파라미터의 ID 값)
  • m.team → 외래키 team_id와 매핑된 상태

💻 외래 키에 식별자를 직접 사용하는 코드

String qlString = "select m from Member m where m.team.id = :teamId";
List resultList = em.createQuery(qlString)
                    .setParameter("teamId", 1L)
                    .getResultList();
                    
//실행된 SQL
select m.*
from Member m
where m.team_id=?(팀 파라미터의 ID 값)
  • MEMBER 테이블이 team_id 외래키 소유 → 묵시적 조인 발생 x
  • m.team.name 호출 → 묵시적 조인 발생


10.2.15 Named 쿼리: 정적 쿼리

  • JPQL 쿼리

    • 동적 쿼리

      • JPQL을 문자로 완성해서 직접 넘기는 것
        ex) em.createQuery("select ..")
      • 런타임에 특정 조건에 따라 JPQL을 동적으로 구성 가능
    • 정적 쿼리(= Named 쿼리)

      • 미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있는 것
      • 한 번 정의하면 변경할 수 없음
      • 애플리케이션 로딩 시점에 JPQL 문법 체크 및 미리 파싱
        • 빠른 오류 확인
        • 사용 시점에 파싱된 결과 재사용 → 성능 향상
      • 정적 SQL 생성 → 데이터베이스의 조회 성능 최적화에 도움

✅ Named 쿼리를 어노테이션에 정의

📝 @NamedQuery 사용

/**
 * @NamedQuery 어노테이션으로 Named 쿼리 정의
 */
@Entity
@NamedQuery(
    name = "Member.findByUsername",  //쿼리 이름 부여
    query = "select m from Member m where m.username = :username"  //사용할 쿼리 입력
)
public class Member {
    ...
}

...

/**
 * @NamedQuery 사용
 */
List<Member> resultList =
    em.createNamedQuery("Member.findByUsername", Member.class)  //Named 쿼리 이름 입력
      .setParameter("username", "회원1")
      .getResultList();

💡 Named 쿼리 이름 앞에 엔티티 이름을 명시한 이유

  • Named 쿼리는 영속성 유닛 단위로 관리 → 충돌 방지
  • 관리의 편리성

📝 @NamedQueries 사용

  • 하나의 엔티티에 2개 이상의 Named 쿼리 정의
/**
 * @NamedQueries 사용
 */
@Entity
@NamedQueries({
    @NamedQuery(
        name = "Member.findByUsername",
        query = "select m from Member m where m.username = :username"
    ),
    @NamedQuery(
        name = "Member.count",
        query = "select count(m) from Member m"
    )
})
public class Member {...}

📝 @NamedQuery 어노테이션

@Target({TYPE})
public @interface NamedQuery {

    String name();  //Named 쿼리 이름(필수)
    String query(); //JPQL 정의(필수)
    LockModeType lockMode() default NONE;  //쿼리 실행 시 락모드 설정
    QueryHint[] hints() default ();  //JPA 구현체에 쿼리 힌트 제공
                                     //ex) 2차 캐시 다룰 때 사용
}

✅ Named 쿼리를 XML에 정의

  • 어노테이션을 사용하는 것이 직관적이로 편리하나, Named 쿼리를 작성할 때는 XML을 사용하는 것이 더 편리함

💻 META-INF/ormMember.xml, XML에 정의한 Named 쿼리

<?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[  //<![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>

💻 META-INF/persistence.xml에 코드 추가

...
<persistence-unit name="jpabook" >
    // 정의한 ormMember.xml 인식
    <mapping-file>META-INF/ormMember.xml</mapping-file>
...

💡 META-INF/orm.xml

  • JPA가 기본 매핑파일로 인식 → 별도 설정 필요 x
  • 이름이나 위치가 다를 경우 설정 추가 필요

✅ 환경에 따른 설정

  • XML과 어노테이션에 같은 설정이 존재 → XML이 우선
  • 애플리케이션이 운영 환경에 따라 다른 쿼리 실행해야 하는 경우
    → 각 환경에 맞춘 XML을 준비해서 XML만 변경해서 배포
profile
기쁘게 코딩하고 싶은 백엔드 개발자

2개의 댓글

comment-user-thumbnail
2022년 12월 28일

SELECT username FROM Member m //잘못된 문법
//username을 m.username으로 고쳐야 함에서 username을 생략하고 별칭인 m만 쓸수 있나요?

1개의 답글