이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.
- JPA는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리 기술을 지원
- JPQL은 가장 중요한 객체지향 쿼리 언어이며, 나머지 쿼리 언어들은 JPQL을 편리하게 사용하도록 도와주는 기술
JPA가 공식 지원하는 기능
그 외
MyBatis
엔티티 객체를 조회하는 객체지향 쿼리
기존의 검색 방법
EntityManager.find()
a.getB().getC()
JPQL 등장 배경
@Entity(name="Member") //name 속성의 기본값은 클래스 명
public class Member {
@Column(name = "name")
private String username;
//...
}
//쿼리 생성
String jpql = "select m from Member as m where m.username = 'kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
select m from Member as m where m.username = 'kim'
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'
SELECT
, UPDATE
, DELETE
문 사용 가능INSERT
문 xEntityManager.persist()
사용하여 엔티티 저장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) Member
≠ member
ex2) username
≠ Username
JPQL 키워드 → 구분 x
ex1) SELECT
= select
ex2) FROM
= from
ex3) AS
= as
Member
→ 클래스 명 x, 엔티티 명 o@Entity(name="XXX")
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
: 반환 타입 명확하게 지정 불가능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
= 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
반환Query
반환Query
객체SELECT
절에서 여러 엔티티나 컬럼을 선택 시 반환 타입이 명확하지 않으므로 사용Object[]
Object
query.getResultList()
: 결과를 리스트로 반환query.getSingleResult()
: 결과가 정확히 하나일 때 사용javax.persistence.NoResultException
javax.persistence.NonUniqueResultException
기준 | JDBC | JPQL |
---|---|---|
위치 | O | O |
이름 | X | O |
:
사용 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();
List<Member> members =
em.createQuery("SELECT m FROM Member m WHERE m.username = :username",
Member.class)
.setParameter("username", usernameParam)
.getResultList();
?
다음에 위치 값 지정List<Member> members =
em.createQuery("SELECT m FROM Member m WHERE m.username = ?1", Member.class)
.setParameter(1, usernameParam)
.getResultList();
💡 참고
- 위치 기준 파라미터 방식보다 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확함
💡 참고
- 파라미터 바인딩 방식은 필수
- 장점
- SQL 인젝션 공격 방지
- 애플리케이션과 데이터베이스 모두 해당 쿼리의 파싱 결과 재사용 가능
→ 전체 성능 향상
SELECT
절에 조회할 대상 지정
[SELECT {프로젝션 대상} FROM]
SELECT m FROM Member m //회원
SELECT m.team FROM Member m //팀
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];
}
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
명령어 사용하여 반환받을 클래스 지정TypeQuery
사용 가능 → 지루한 객체 변환 작업 감소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번 데이터 조회
함수 | 설명 | 반환 타입 |
---|---|---|
COUNT | 결과 갯수 반환 | Long |
MAX, MIN | ◾ 최대, 최소 값 반환 ◾ 문자, 숫자, 날짜 등에 사용 | |
AVG | ◾ 평균값 반환 ◾ 숫자타입만 사용 가능 | Double |
SUM | ◾ 합계 반환 ◾ 숫자타입만 사용 가능 | ◾ 정수합 : Long ◾ 소수합 : Double ◾ BigInteger합 : BigInteger ◾ BigDecimal합 : BigDecimal |
NULL
값은 무시하므로 통계에 잡히지 않음DISTINCT
가 정의되어 있어도 무시)SUM
, AVG
, MAX
, MIN
→ NULL
COUNT
→ 0DISTINCT
를 집합 함수 안에 사용하여 중복 값 제거 후 집합 구할 수 있음select COUNT(DISTINCT m.age) from Member m
DISTINCT
를 COUNT
에서 사용할 때 임베디드 타입 지원 xgroupby_절 ::= 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
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
결과가 아주 많은 경우, 통계 결과만 저장하는 테이블을 별도로 만들어 두고, 사용자가 적은 새벽에 통계 쿼리를 실행해서 결과 보관(권장)
orderby_절 ::= ORDER BY {상태필드 경로 | 결과 변수 [ASC | DESC]}+
SELECT
절에 나타나는 값ASC
: 오름차순(기본값)DESC
: 내림차순select m from Member m order by m.age DESC, m.username ASC
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
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();
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
설정JOIN
명령어 다음에 조인할 객체의 연관 필드 사용SQL
조인처럼 사용하면 문법 오류 발생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];
}
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
OUTER
생략 가능 → 보통 LEFT JOIN
으로 사용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) 일대다 관계, 다대다 관계
//팀과 팀이 보유한 회원목록을 컬렉션 값 연관 필드로 외부 조인
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
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' //조인 시점에 조인 대상 필터링
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());
}
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
)도 함께 조회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
테이블과 조인하면서 결과 증가💡 참고
일대일, 다대일 조인은 결과가 증가하지 않음
DISTINCT
SQL에서 중복 결과 제거 + 애플리케이션에서 중복 결과 제거
select distinct t from Team t join fetch t.members where t.name = '팀A'
SELECT DISTINCT
추가됨distinct
명령어를 보고 중복 데이터 거름teamname = 팀A, team = Team@0x100
->username = 회원1, member = Member@0x200
->username = 회원2, member = Member@0x300
일반 조인 : 연관된 엔티티 조회 x
페치 조인 : 연관된 엔티티 함께 조회
성능 최적화
글로벌 로딩 전략보다 우선
📕 글로벌 로딩 전략
- 엔티티에 직접 적용하는 로딩 전략 → 애플리케이션 전체에 영향 미침
ex)@OneToMany(fetch = FetchType.LAZY)
- 즉시 로딩으로 설정 시 성능에 악영향 미칠 수 있음
- 애플리케이션 전체에서 항상 즉시 로딩 발생
- 사용하지 않는 엔티티를 자주 로딩하게 됨
연관된 엔티티를 쿼리 시점에 조회 → 지연 로딩 발생 x
∴ 준영속 상태에서도 객체 그래프 탐색 가능
페치 조인 대상에는 별칭을 줄 수 없음
SELECT
절, WHERE
절, 서브 쿼리에 페치 조인 대상 사용 x둘 이상의 컬렉션을 페치할 수 없음
"javax.persistence.PersistenceException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags"
컬렉션을 페치 조인하면 페이징 API(setFirstResult
, setMaxResults
) 사용 불가
💡 참고
- 객체 그래프를 유지하는 경우 → 페치 조인
- 여러 테이블을 조인하여 엔티티가 가진 모양과 전혀 다른 결과를 내야하는 경우
→ 여러 테이블에서 필요한 필드들만 조회하여 DTO로 반환
.
(점)을 찍어 객체 그래프를 탐색하는 방법
ex)m.username
,m.team
@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
절에 영향을 준다.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
→ 반대ALL
| ANY
| SOME
}{ALL | ANY | SOME} (subquery)
ALL
: 조건을 모두 만족하면 참ANY
혹은 SOME
: 조건을 하나라도 만족하면 참IN
[NOT] IN (subquery)
종류 | 설명 | 예제 |
---|---|---|
문자 | ◾ 작은 따옴표 사이에 표현 ◾ 작은 따옴표를 표현하고 싶으면 작은 따옴표 연속 두 개('') 사용 | '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 |
.
[NOT] BETWEEN
, [NOT] LIKE
, [NOT] IN
, IS [NOT] NULL
, IS [NOT] EMPTY
, [NOT] MEMBER [OF]
, [NOT] EXISTS
NOT
, AND
, OR
AND
: 둘 다 만족하면 참OR
: 둘 중 하나만 만족해도 참NOT
: 조건식의 결과 반대=, >, >=, <, <=, <>
Between
, IN
, Like
, NULL
비교Between
식X [NOT] BETWEENT A AND B
IN
식x [NOT] IN (예제)
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
)
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
@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
// 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)상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
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'
function_invocation::= FUNCTION(function_name {, function_arg}*)
//예제
select function('group_concat', i.name) from Item i
hibernate.dialect
에 해당 방언 등록enum
→ =
비교 연산만 지원‘’
Empty String
으로 지정NULL
로 사용하는 경우도 존재NULL
정의NULL
NULL
과의 모든 수학적 계산 결과는 NULL
Null == Null
→ 알 수 없는 값Null is Null
→ 참Null
(U), TRUE
(T), FALSE
(F)의 논리 계산 정의AND
연산
AND | T | F | U |
---|---|---|---|
T | T | F | U |
F | F | F | F |
U | U | F | U |
OR
연산
OR | T | F | U |
---|---|---|---|
T | T | T | T |
F | T | F | U |
U | T | U | U |
NOT
연산
NOT | |
---|---|
T | F |
F | T |
U | U |
💡 식별 기준
- 객체 인스턴스 : 참조 값
- 테이블 로우 : 기본 키 값
→ 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=?
where m = :member
→ where 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
외래키 소유 → 묵시적 조인 발생 xm.team.name
호출 → 묵시적 조인 발생JPQL 쿼리
동적 쿼리
em.createQuery("select ..")
정적 쿼리(= 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
사용/**
* @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차 캐시 다룰 때 사용
}
<?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>
...
<persistence-unit name="jpabook" >
// 정의한 ormMember.xml 인식
<mapping-file>META-INF/ormMember.xml</mapping-file>
...
💡
META-INF/orm.xml
- JPA가 기본 매핑파일로 인식 → 별도 설정 필요 x
- 이름이나 위치가 다를 경우 설정 추가 필요
SELECT username FROM Member m //잘못된 문법
//username을 m.username으로 고쳐야 함에서 username을 생략하고 별칭인 m만 쓸수 있나요?