beadss / jpa-study

jpa슽터디입니다
1 stars 2 forks source link

10장 정리(정리중) #30

Open 2xel opened 5 years ago

2xel commented 5 years ago

10. 객체지향 쿼리언어

2 JPQL

2.1 기본 문법과 쿼리 API

SELECT

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

TypeQuery, Query

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

List<Member> resultList = query.getResultList();
for(Member memeber : resultList) {
    System.out.println("member = " + member);
}
Query query = em.createQuery("SELECT m.username, m.age from Member m");
List resultList = query.getResultList();

for(Object o : resultList) {
    Object[] result = (Object[]) o;
    System.out.println("username = " + result[0]);
    System.out.println("age = " + result[1]);
}

결과 조회

2.2 파라미터 바인딩

String usernameParam = "User1";

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

query.setParameter("useranme", usernameParam); // :username이라는 이름 기준 파라미터를 정의하고 username이라는 이름으로 파라미터를 바인딩한다.

List<Member> resultList = query.getResultList();

또는

List<Member> members = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class)
                         .setParameter("username", username)
                         .getResultList();  // 메소드 체인 방식으로 설계되어 있어 연속해서 작성할 수 있다.
List<Member> members = em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
                         .setParameter(1, usernameParam)
                         .getResultList();

2.3 프로젝션

  1. SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라 한다.
  2. 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다.
  3. 스칼라 타입은 숫자, 문자 등 기본 데이터 타입을 뜻한다.

엔티티 프로젝션

SELECT m FROM Member m  -- 회원
SELECT m.team FROM Member m -- 팀

임베디드 타입 프로젝션

String query = "SELECT o.address FROM Address a";
List<Address> addresses = em.createQuery(query, Address.class)
                            .getResultList();
SELECT order.city, order.street, order.zipcode FROM Orders order

스칼라 타입 프로젝션

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

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

여러 값 조회

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 username = (String) row[0];
    Integer age = (Integer) row[1];
}

// 제네릭 Object[]를 사용하면
List<Object[]> resultList = query.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 명령어

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

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

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();

2.4 페이징 API

TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);

query.setFirstResult(10);
query.setMaxResult(20);
query.getResultList();

2.5 집합과 정렬

집합 함수

함수 설명 반환타입
COUNT 결과 수를 구한다. Long
MAX, MIN 최대, 최소 값을 구한다. 문자, 숫자, 날짜 등에 사용한다.
AVG 평균값을 구한다. 숫자타입만 사용가능. Double
SUM 합을 구한다. 숫자타입만 사용할 수 있다. 정수합 : Long, 소수합 : Double, BigInteger합 : BigInteger, BigDecimal합 : BigDecimal

집합 함수 사용 시 참고사항

GROUP BY, 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

-- 다음 데이터 중 평균나이가 10살 이상인 그룹을 조회한다.

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

-- 문법은 다음과 같다.
groupby_절 ::= GROUP BY {단일값 경로 | 별칭}+
having_절 ::= HAVING 조건식

정렬(ORDER BY)

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

-- 문법은 다음과 같다.
orderby_절 ::= ORDER BY {상태필드 경로 | 결과 변수 [ASC | DESC]}+

select t.name, COUNT(m.age) as cnt
from Member m LEFT JOIN m.team t
GROUP BY t.name
ORDER BY cnt

2.6 JPQL 조인

내부 조인

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();
SELECT m
FROM Member m INNER JOIN m.team t
where t.name = :teamName

-- 생성된 내부 조인 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=?

=========== 추가 필요====================

외부 조인

SELECT m
FROM Member m LEFT (OUTER) JOIN m.team t
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 TEAMM T ON M.TEAM_ID = T.ID
WHERE
    T.NAME=?

컬렉션 조인

SELECT t, m FROM Team t LEFT JOIN t.members m

세타 조인

-- 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)

-- 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'

2.7 페치 조인

엔티티 페치 조인

select m
from Member m join fetch m.team

-- 실행된 sql
SELECT
    M.*, T.*
FROM MEMBER T
INNER JOIN TEAM T ON M.TEAM_ID = T.ID
String jpql = "select m from Member m join fetch m.team";

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

for(Member member : members) {
    // 페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩 발생 안함
    System.out.println("username = " + member.getUsername() + ", " + "teamname = " + member.getTeam().name());
}

컬렉션 페치 조인

select t
from Team t join fetch t.members
where t.name = '팀A'

-- 실행된 SQL

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

2.8 경로 표현식

경로 표현식의 용어 정리

경로 표현식과 특징

==== 추가필요 ====

2.9 서브쿼리

서브 쿼리 함수

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'}
Boolean TRUE, FALSE
Enum 패키지명을 포함한 전체 이름을 사용 jpabook.MemberType.Admin
엔티티 타입 엔티티의 타입을 표현, 주로 상속과 관련해 사용 TYPE(m) = Member

2.11 다형성 쿼리

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

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

// ... Album, Movie 생략

// 다음과 같이 조회하면 Item의 자식도 함께 조회한다.
List resultList = em.createQuery("select i from Item i").getResultList();
-- 단일 테이블 전략을 사용할 때 실행되는 SQL (SINGLE_TABLE)
SELECT * FROM ITEM

-- 조인 전략을 사용할 떄 실행되는 SQL (JOINED)
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=b,ITEM_ID
left outer join
    Movie m on i.ITEM_ID=m.ITEM_ID

TYPE

-- Item 중 Book, Movice를 조회
-- 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)

-- 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'

2.12 사용자 정의 함수 호출

2.13 기타 정리

2.14 엔티티 직접 사용

기본 키 값

select count(m.id) from Member m
select count(m) from Member m

-- 실제 실행된 SQL
select count(m,id) as cnt
from Member m
-- 엔티티를 직접 사용하면 JPQL이 SQL로 변환될 때 해당 엔티티의 기본 키를 사용한다. 따라서 실제 실행된 SQL은 같다.
// 엔티티를 파라미터로 직접 받는 코드
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=?

외래 키 값

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();
// 기본 키 값이 1L인 팀 엔티티를 파라미터로 사용하고 있고 m.team은 현재 team.id라는 외래 키와 매핑되어 있다.
select m.*
from Member m
where m.team_id=?(팀 파리미터의 ID 값)

2.15 Named 쿼리 : 정적 쿼리'

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

@Entity
@NamedQuery(name = "Member.findByUsername", query="select m from Member m where m.username = :username")
public class Member {
    // ....
}

@NamedQuery.name에 쿼리 이름을 부여하고 @NamedQuery.query에 사용할 쿼리를 입력하면 된다.

List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
                            .setParamter("username", "회원1")
                            .getResultList();
@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 NameQuery{
    String name();
    String query();
    LockModeType lockMode() default NONE;

    QueryHint[] hints() default {};
}