객체지향 쿼리 언어 소개
1. JPA가 지원하는 다양한 쿼리 방법
- JPQL
- JPA Criteria
- QueryDSL
- 네이티브 SQL
- JDBC API 직접 사용 / MyBatis / SpringJdbcTemplate 함께 사용할 수 있다.
2. JPQL
- JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
- JPQL은 한마디로 객체지향 SQL이다.
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
- SQL과 문법이 유사하다.
- SELECT / FROM / WHERE / GROUP BY / HAVING / JOIN 등을 지원한다.
- JPQL은 엔티티 객체를 대상으로 쿼리문을 작성한다.
- SQL은 데이터베이스 테이블을 대상으로 쿼리문을 작성한다.
- JPQL은 결국 SQL로 변환된다.
package hellojpa;
import hellojpa.domain2.MemberV2;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
List<MemberV2> result = em.createQuery(
"select m from MemberV2 m where m.username like '%kim%'",
MemberV2.class
).getResultList();
tx.commit();
} catch (Exception e) {
System.out.println("e.getMessage=" +e.getMessage());
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
/* select
m
from
MemberV2 m
where
m.username like '%kim%' */ select
memberv2x0_.MEMBER_ID as MEMBER_I1_8_,
memberv2x0_.city as city2_8_,
memberv2x0_.street as street3_8_,
memberv2x0_.zipcode as zipcode4_8_,
memberv2x0_.TEAM_ID as TEAM_ID6_8_,
memberv2x0_.USERNAME as USERNAME5_8_
from
MemberV2 memberv2x0_
where
memberv2x0_.USERNAME like '%kim%'
2. Criteria
- 결론 : 실무에서 안쓰인다.
- 문자가 아닌 자바 코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할을 한다.
- JPA 공식 기능이다.
- 단점은 너무 복잡하고 실용성이 없다
- 그래서 Criteria 대신 QueryDSL을 사용할 것
3. QueryDSL
- 결론 : 실무에서 자주 쓰인다.
- 문자가 아닌 자바 코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할을 한다.
- 컴파일 시점에 문법 오류를 찾을 수 있다.
- 동적쿼리 작성이 매우 편하다.
- 단순하고 쉽다.
4. 네이티브 SQL
- JPA가 제공하는 SQL을 직접 사용 가능하다.
- JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능이다.
- 예) 오라클 CONNECT BY / 특정 DB만 사용하는 SQL 힌트
5. JDBC 직접 사용
- JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나 스프링 JdbcTemplate, 마이바티스 등을 함께 사용 가능하다.
- 단 영속성 컨텍스트를 적절한 시점에 강제로 em.flush() 시켜야 한다.
- 예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동 em.flush() 시켜야 한다.
JPQL 기본 문법과 기능
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
@Entity
@Getter @Setter
@Table(name = "ORDERS")
public class Order {
@Id @GeneratedValue
private Long id;
private int orderAmount;
@Embedded
private Address address;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
}
@Entity
@Getter @Setter
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
private int stockAmount;
}
@Embeddable
@Getter @Setter
public class Address {
private String city;
private String street;
private String zipcode;
}
1. JPQL 문법
// select문
select_
from_
[where_]
[group by_]
[having_]
[orderby_]
// update문
update_
[where_]
// delect문
delete_
[where_]
예시) select m from Member as m where m.age > 18
- 엔티티와 속성은 대소문자를 구분해야한다. (Member, age)
- JPQL 키워드는 대소문자 구분을 안한다. (select, from, where)
- 엔티티 이름을 사용해야한다. 테이블 이름이 아니다. (Member)
- 별칭은 필수다. (m)
- as는 생략 가능
2. 집합과 정렬
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
group by / having / order by 기능도 지원한다.
3. TypeQuery / Query
쿼리에는 두가지 타입이 있다. 첫 번째는 TypeQuery다. 타입이 확실할 때 사용한다. 두번 째는 Query다. 타입이 명확하지 않을 때 사용한다.
TypedQuery<Member> query = em.createQuery("select m form Member m", Member.class);
- TypeQuery : 반환 타입이 명확할 때 사용한다.
- 두 번째 파라미터에 타입 명을 지정하면 된다.
Query query = em.createQuery("select m form Member m");
- Query : 반환 타입이 명확하지 않을 때 사용한다.
4. 결과 조회 API
List<Member> result = em.createQuery("select m form Member m", Member.class)
.getResultList();
- getResultList() : 결과가 하나 이상일 때, 리스트 반환
- 결과가 없으면 빈 리스트를 반환한다.
Member result = em.createQuery("select m form Member m", Member.class)
.getSingleResult();
- getSingleResult() : 결과가 정확히 하나일 경우 사용한다. 단일 객체를 반환한다.
- 결과가 없으면 javax.persistence.NoResultException 예외가 발생한다.
- 결과가 둘 이상이면 javax.persistence.NonUniqueResultException 예외가 발생한다.
5. 파라미터 바인딩 - 이름 기준
쿼리문에서 파라미터를 사용할 수 있다. 파라미터로 쓰고 싶은 변수 앞에 ':' 를 붙여준다. 그리고 해당 변수에 값을 setParameter()로 넣어준다.
Member result = em.createQuery("select m from Member m where m.username= :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
System.out.println("result.getUsername() = " + result.getUsername());
- :username 을 통해서 username을 파라미터화 시켰다.
- 그리고 setParameter()를 통해 첫 번째 파라미터에 파라미터화 시킨 username을 넣는다. 두 번째 파라미터에 파라미터화 시킨 username에 들어갈 값인 member1을 넣는다.
- 그렇게 하여 select m from Member m where m.username= :username 을 select m from Member m where m.username= member1 로 바꿨다.
🔍 파라미터 바인딩 방식은 선택이 아닌 필수다.
- SQL Injection 공격 방지
- 파라미터 바인딩을 사용하지 않고 "select m from Member m where m.username= :username" + 변수 로 쓸경우 변수 부분에 누군가 다른 데이터를 넘길 수 있다. 그러면 사용자의 데이터가 해킹될 수 있다. 이것을 SQL Injection 공격이라고 한다. 파라미터 바인딩 방식을 사용하면 SQL Injection 공격을 막을 수 있다.
- 쿼리 재사용 가능
- DB에서는 파라미터만 다른 SQL에 대해서는 기존 SQL 파싱 결과를 재사용한다. 그렇기에 파라미터 바인딩 방식을 통해 해당 쿼리의 파싱 결과를 재사용할 수 있고 성능이 올라간다.
참고로 위치 기준 파라미터 바인딩도 있지만 사용하지 않는것이 좋다. 왜냐하면 중간에 파라미터를 추가할 경우 위치가 밀려서 서로 다른 파라미터가 매핑하게 되기 때문이다. 무조건 이름 기준 파라미터 바인딩을 사용하는 것이 좋다.
프로젝션
- 프로젝션은 select 절에 조회할 대상을 지정하는 것이다.
- 프로젝션 대상 : 엔티티 / 임베디드 타입 / 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
- 관계형 데이터 베이스는 스칼라 타입의 대상만 조회할 수 있다.
- 엔티티 프로젝션은 모두 영속성 컨텍스트의 적용 대상이다.
- select m from Member m
- 엔티티 프로젝션
- select m.team from Member m
- 엔티티 프로젝션
- 이 방식은 묵시적 조인이다. 명시적 조인으로 쓰는 것이 낫다.
- select m.address from Member m
- 임베디드 타입 프로젝션
- 임베디드 타입 프로젝션은 엔티티로부터 시작(from)해야한다.
- select m.username, m.age from Member m
- 스칼라 타입 프로젝션
DISTINCT로 중복을 제거할 수 있다.
🔍 묵시적 조인 / 명시적 조인
묵시적 조인
List<Team> result = em.createQuery("select m.team from Member m", Team class)
// SQL : SELECT t.id, t.name, FROM Member m inner join TEAM t on m.team_id = t.id
- JPQL에 JOIN 문법이 없지만 자연스럽게 JOIN을 해서 Team 엔티티를 조회한다.
명시적 조인
List<Team> result = em.createQuery("select t from Member m join m.team t", Team class)
// SQL : SELECT t.id, t.name, FROM Member m inner join TEAM t on m.team_id = t.id
- JOIN을 JPQL에 적어줬기에 가독성이 높아지고 JOIN 쿼리가 날아가겠다고 예측이 가능하다.
- 실행되는 SQL은 동일하나 명시적 조인이 묵시적 조인보다 낫다.
🔍 임베디드 타입 프로젝션
em.createQuery("select o.address from Order o", Address.class)
.getResultList();
// SQL : SELECT o.ctiy, o.street, o.zipcode FROM ORDERS o
- 임베디드 타입은 따로 조인을 해서 가져오지 않는다.
- 다만 from절에 Order가 아닌 Address를 적으면 에러가 난다. 엔티티로부터 시작되어야 한다.
🔍 여러 값 조회
'SELECT m.username, m.age FROM Member m' 의 sql처럼 여러 값을 조회하는 방법에는 3가지가 있다.
- Query 타입으로 조회하기
- Object[] 타입으로 조회하기
- new 명령어로 조회하기
3가지 방법 중에서는 new 명령어로 조회하는게 낫다. 그러나 QueryDSL을 사용하는게 제일 좋다.
💡 new 명령어로 조회
List<MemberDTO> result = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = result.get(0);
- 단순 값을 DTO로 바로 조회할 수 있다.
- SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
- new 연산자 다음에 패키지명을 포함한 전체 클래스 명을 입력해야 한다.
- 순서와 타입이 일치하는 생성자가 필요하다.
📌 MemberDTO
package jpql;
@Getter @Setter
public class MemberDTO {
private String username;
private int age;
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
}
페이징
- JPA는 페이징을 두 개의 API로 추상화한다.
- setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터 수
package jpql;
import javax.persistence.*;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
for (int i = 0; i < 100; i++) {
Member member = new Member();
member.setUsername("member" + i);
member.setAge(i);
em.persist(member);
}
em.flush();
em.clear();
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(0)
.setMaxResults(5)
.getResultList();
System.out.println("result.size() = " + result.size());
for (Member member1 : result) {
System.out.println("member1 = " + member1);
}
tx.commit();
} catch (Exception e) {
tx.rollback();
System.out.println("e.getMessage()= " + e.getMessage());;
} finally {
em.clear();
}
}
}
// 출력 결과
result.size() = 5
member1 = Member{id=100, username='member99', age=99}
member1 = Member{id=99, username='member98', age=98}
member1 = Member{id=98, username='member97', age=97}
member1 = Member{id=97, username='member96', age=96}
member1 = Member{id=96, username='member95', age=95}
조인
- 내부조인
- select m from Member m inner join m.team t
- Member 값이 있으나 team 값이 없을 경우 데이터가 안나온다.
- inner 생략 가능
- 외부 조인
- select m from Member m left outer join m.team t
- Member 값이 있으나 team 값이 없을 경우 멤버 조회는 가능하다.
- outer 생략 가능
- 세타 조인
- select count(m) from Member m, Team t where m.username = t.name
- 연관관계가 없는 조인이다.
📌 Member
package jpql;
import javax.persistence.*;
import static javax.persistence.FetchType.*;
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// 연관관계 편의 메서드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Member{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}
}
📌 Team
package jpql;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public List<Member> getMembers() {
return members;
}
public void setMembers(List<Member> members) {
this.members = members;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1. 내부 조인
📌 메인 메서드
package jpql;
import javax.persistence.*;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m join m.team t";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
System.out.println("e.getMessage()= " + e.getMessage());;
} finally {
em.clear();
}
}
}
Hibernate:
/* select
m
from
Member m
join
m.team t */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as TEAM_ID4_0_,
member0_.username as username3_0_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
2. 외부 조인
📌 메인 메서드
package jpql;
import javax.persistence.*;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m left join m.team t";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
System.out.println("e.getMessage()= " + e.getMessage());;
} finally {
em.clear();
}
}
}
Hibernate:
/* select
m
from
Member m
left join
m.team t */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as TEAM_ID4_0_,
member0_.username as username3_0_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.id
3. 세타 조인
📌 메인 메서드
package jpql;
import javax.persistence.*;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m, Team t where m.username = t.name";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
System.out.println("e.getMessage()= " + e.getMessage());;
} finally {
em.clear();
}
}
}
Hibernate:
/* select
m
from
Member m,
Team t
where
m.username = t.name */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as TEAM_ID4_0_,
member0_.username as username3_0_
from
Member member0_ cross
join
Team team1_
where
member0_.username=team1_.name
4. 조인 on 절
🔍 조인 대상 필터링
package jpql;
import javax.persistence.*;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m left join m.team t on t.name = :teamName";
List<Member> result = em.createQuery(query, Member.class)
.setParameter("teamName", "teamA")
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
System.out.println("e.getMessage()= " + e.getMessage());;
} finally {
em.clear();
}
}
}
Hibernate:
/* select
m
from
Member m
left join
m.team t
on t.name = :teamName */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as TEAM_ID4_0_,
member0_.username as username3_0_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.id
and (
team1_.name=?
)
- 회원과 팀을 조인하면서 팀이름이 A인 팀만 조인하는 경우다.
- join 조건문 안에 and 조건이 추가 됐다.
- 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'
🔍 연관관계가 없는 엔티티 외부 조인
package jpql;
import javax.persistence.*;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m left join Team t on m.username = t.name";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
System.out.println("e.getMessage()= " + e.getMessage());;
} finally {
em.clear();
}
}
}
Hibernate:
/* select
m
from
Member m
left join
Team t
on m.username = t.name */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as TEAM_ID4_0_,
member0_.username as username3_0_
from
Member member0_
left outer join
Team team1_
on (
member0_.username=team1_.name
)
- 회원의 이름과 팀의 이름이 같은 대상 외부 조인
- 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
서브 쿼리
- 나이가 평균보다 많은 회원
- select m from Member m where m.age > (select avg (m2.age) from Member m2)
- 서브쿼리인 m2는 메인 쿼리랑 관련이 없다.
- 서브쿼리랑 메인쿼리랑 연관이 없어야 성능이 잘 나온다.
- 한 건이라도 주문한 고객
- select m from Member m where (select count(o) from Order o where m = o.member) > o
- 메인 쿼리인 m은 서브 쿼리에도 쓰였다, 이런 경우 성능이 좋지 않다.
결론 : jpa에서도 일반적인 sql 서브 쿼리를 쓸 수 있다.
1.서브 쿼리 지원 함수
- [NOT] EXISTS (subquery) : 서브쿼리에 결과가 존재하면 참
- {ALL | ANY | SOME} (subquery)
- ALL : 모두 만족하면 참
- ANY / SOME : 조건을 하나라도 만족하면 참
- ANY와 SOM은 서로 같은 기능
- [NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
2. 서브쿼리 예제
- 팀A 소속인 회원
- select m from Member m where exists (select t from m.team t where t.name = "팀A")
- 전체 상품 각각의 재고보다 주문량이 많은 주문들
- select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
- 어떤 팀이든 팀에 소속된 회원
- select m from Member m where m.team = ANY (select from Team t)
3. JPA 서브 쿼리 한계
- JPA는 WHERE / HAVING 절에서만 서브 쿼리 사용이 가능하다.
- SELECT 절에서도 사용할 수 있다. (하이버네이트에서 지원)
- FORM 절의 서브쿼리는 현재 JPQL에서 불가능하다.
🔍 대안
- 조인으로 풀 수 있으면 풀어서 해결할 것.
- 쿼리를 분해해서 메인 쿼리에서 받은 데이터와 서브 쿼리에서 받은 데이터를 애플리케이션에서 처리해서 해결할 것.
- native sql 사용해서 해결 할 것
조인으로 풀 수 있으면 조인으로 풀어내는게 제일 낫다.
JPQL 타입 표현과 기타식
1. JPQL 타입 표현
- 문자 : 'HELLO', 'SHE''s'
- 문자를 표현하고 싶을 때 ' (작은 따옴표)를 사용한다.
- 만약에 문자 내에서 작은 따옴표가 있는 경우 '' (작은 따옴표 2개) 처럼 작은 따옴표를 2개 적는다.
- 숫자 : 10L (Long) , 10D (Double), 10F (Float)
- Boolean : true, false (대문자도 가능)
- ENUM : jpql.MemberType.Admin
- ENUM 타입의 값을 표기할 때는 패키지명도 적어야 한다.
- 엔티티 타입 : TYPE(m) = Member
- 엔티티 타입 정보가 member 인 경우에만 사용하고 싶다 할때 사용한다.
- 상속 관계에서 사용할 수 있다.
- 잘 안쓰인다.
🔍 JQPL 표현식 예제
💡 ex.1
String query = "select m.username, 'HELLO', true from Member m";
List<Object[]> result = em.createQuery(query)
.getResultList();
for (Object[] objects : result) {
System.out.println("objects[0] = " + objects[0]);
System.out.println("objects[0] = " + objects[1]);
System.out.println("objects[0] = " + objects[2]);
}
Hibernate:
/* select
m.username,
'HELLO',
true
from
Member m */ select
member0_.username as col_0_0_,
'HELLO' as col_1_0_,
1 as col_2_0_
from
Member member0_
objects[0] = member1
objects[0] = HELLO
objects[0] = true
💡 ex.2
package jpql;
public enum MemberType {
ADMIN, USER
}
/* 메인 메서드 */
String query = "select m.username, 'HELLO', true from Member m "
+ "where m.type = jpql.MemberType.ADMIN"; // 패키지명 까지 적어야한다.
List<Object[]> result = em.createQuery(query)
.getResultList();
for (Object[] objects : result) {
System.out.println("objects[0] = " + objects[0]);
System.out.println("objects[0] = " + objects[1]);
System.out.println("objects[0] = " + objects[2]);
}
Hibernate:
/* select
m.username,
'HELLO',
true
from
Member m
where
m.type = jpql.MemberType.ADMIN */ select
member0_.username as col_0_0_,
'HELLO' as col_1_0_,
1 as col_2_0_
from
Member member0_
where
member0_.type='ADMIN'
- ENUM 타입의 값을 사용하고 싶을 때는 패키지 명까지 다 적어줘야 한다.
💡 ex.3
String query = "select m.username, 'HELLO', true from Member m "
+ "where m.type = :userType";
List<Object[]> result = em.createQuery(query)
.setParameter("userType", MemberType.ADMIN)
.getResultList();
Hibernate:
/* select
m.username,
'HELLO',
true
from
Member m
where
m.type = jpql.MemberType.ADMIN */ select
member0_.username as col_0_0_,
'HELLO' as col_1_0_,
1 as col_2_0_
from
Member member0_
where
member0_.type='ADMIN'
- ex.2 예제와 내용이 같다.
- ex.2 예제를 파라미터 바인딩으로 사용한 것이다. 이런 경우 패키지명을 안적어도 된다.
💡 ex.4
/* Item 엔티티 */
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {
@Id @GeneratedValue
private Long id;
@Column(name = "item_name")
private String name;
}
/* Book 엔티티 */
@Entity
@DiscriminatorValue("BB")
public class Book extends Item{
}
/* 메인 메서드 */
String query = "select i from Item i where type(i) = Book";
List<Item> result = em.createQuery(query, Item.class)
.getResultList();
Hibernate:
/* select
i
from
Item i
where
type(i) = Book */ select
item0_.id as id2_0_,
item0_.item_name as item_nam3_0_,
item0_.DTYPE as DTYPE1_0_
from
Item item0_
where
item0_.DTYPE='BB'
2. JPQL 기타식
- SQL과 문법이 같은식
- exists / in 사용 가능
- and / or / not 사용 가능
- =, >, >=, <, <=, <> 사용 가능
- between / like / is null 사용 가능
조건식 - CASE 식
1. 기본 CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
- 조건을 넣을 수 있다.
🔍 ex.1
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
String query =
"select " +
"case when m.age <=10 then '학생요금' " +
" when m.age >=60 then '경로요금' " +
" else '일반요금'" +
"end " +
"from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
Hibernate:
/* select
case
when m.age <=10 then '학생요금'
when m.age >=60 then '경로요금'
else '일반요금'
end
from
Member m */ select
case
when member0_.age<=10 then '학생요금'
when member0_.age>=60 then '경로요금'
else '일반요금'
end as col_0_0_
from
Member member0_
s = 학생요금
2. 단순 CASE 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
- 정확한 값 매칭을 통해 행동을 부여한다.
3. 조건식 CASE 식
- coalesce : 하나씩 조회해서 null이 아니면 반환
- nullif : 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환
🔍 ex.1 - coalesce
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
String query = "select coalesce(m.username, '이름 없는 회원') from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
Hibernate:
/* select
coalesce(m.username,
'이름 없는 회원')
from
Member m */ select
coalesce(member0_.username,
'이름 없는 회원') as col_0_0_
from
Member member0_
s = member1
- m.username 값이 존재하므로 m.username 값이 출력된다.
🔍 ex.2 - coalesce
Member member = new Member();
member.setUsername(null); // 회원 명이 null 값
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
String query = "select coalesce(m.username, '이름 없는 회원') from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
Hibernate:
/* select
coalesce(m.username,
'이름 없는 회원')
from
Member m */ select
coalesce(member0_.username,
'이름 없는 회원') as col_0_0_
from
Member member0_
s = 이름 없는 회원
- m.username 값이 null 값이므로 두번째 값인 '이름 없는 회원' 값이 출력되었다.
🔍 ex.3 - nullif
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
String query = "select nullif(m.username, '관리자') as username " +
"from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
Hibernate:
/* select
nullif(m.username,
'관리자') as username
from
Member m */ select
nullif(member0_.username,
'관리자') as col_0_0_
from
Member member0_
s = member1
- m.username값과 '관리자' 값이 서로 다르므로 m.username 값이 출력됏다.
🔍ex.4 - nullif
Member member = new Member();
member.setUsername("관리자");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
String query = "select nullif(m.username, '관리자') as username " +
"from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
Hibernate:
/* select
nullif(m.username,
'관리자') as username
from
Member m */ select
nullif(member0_.username,
'관리자') as col_0_0_
from
Member member0_
s = null
- m.username값과 '관리자' 값이 같으므로 null 값이 출력됏다.
JPQL 함수
1. JPA에서 제공하는 기본 함수
🔍 문자 함수
함수 | 설명 | 예제 |
CONCAT(문자1, 문자2) | 문자를 합한다 | CONCAT(‘A’, ‘B’) = AB |
SUBSTRING(문자, 위치[, 길이]) | - 위치부터 시작해 길이만큼 문자를 구한다. - 길이 값이 없으면 나머지 전체 길이를 뜻한다 |
SUBSTRING(‘ABCDEF’, 2, 3) = BCD |
TRIM([[LEADING | TRAILING | BOTH] [트림 문자] FROM] 문자) |
LOWER(문자) | 소문자로 변경 | LOWER(‘ABC’) = abc |
UPPER(문자) | 대문자로 변경 | UPPER(‘abc’) = ABC |
LENGTH(문자) | 문자 길이 | LENGTH(‘ABC’) = 3 |
LOCATE(찾을 문자, 원본 문자[, 검색 시작 위치]) | - 검색위치부터 문자를 검색한다. - 1부터 시작하고 못찾으면 0을 반환한다. |
LOCATE(‘DE’, ‘ABCDEFG’) = 4 |
String query = "select concat('a', 'b') From Member m";
String query = "select substring(m.username, 2, 3)" From Member m";
String query = "select locate('de', 'abcdeaf') From Member m";
🔍 수학 함수
함수 | 설명 | 예제 |
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 | 현재 날짜 + 시간 |
2. 사용자 정의 함수 호출
하이버네이트는 사용전 방언에 추가해야한다.
사용하는 DB 방언을 상속 받고 사용자 정의 함수를 등록한다.
package jpql.dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;
public class MyH2Dialect extends H2Dialect {
public void MyH2Dialect() {
registerFunction(
"group_concat",
new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
- DB 방언 클래스를 상속받아서 사용할 데이터베이스 함수를 미리 등록한다.
- registerFunction의 두번째 인자로는 하이버네이트의 SQLFunction 구현체를 주면 된다.
- 기본 함수를 사용하겠다는 의미로 StandardFunction을 사용했다.
- 첫 번째 인자는 함수 이름, 두번째 인자로는 리턴타입을 주고 있다.
String query = "select function('group_concat', m.username) from Member m";
👀 참고 자료
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
https://joont92.github.io/jpa/JPQL/
[jpa] JPQL
JPA에서 현재까지 사용했던 검색은 아래와 같다. 식별자로 조회 EntityManager.find() 객체 그래프 탐색 e.g. a.getB().getC() 하지만 현실적으로 이 기능만으로 어플리케이션을 개발하기에는 무리이다. 그
joont92.github.io
'[JPA] > JPA 프로그래밍 - 기본편' 카테고리의 다른 글
[JPA] 객체지향 쿼리 언어2 - 중급 문법 (0) | 2022.04.03 |
---|---|
[JPA] 값 타입 (0) | 2022.03.31 |
[JPA] 프록시와 연관관계 관리 (0) | 2022.03.30 |
[JPA] 고급 매핑 (0) | 2022.03.29 |
[JPA] 다양한 연관관계 매핑 (0) | 2022.03.29 |