jpa, jpql 의 기초 1
JPQL 이란?
- 객체지향 쿼리.
- 테이블이 아닌 객체를 대상으로 하는 SQL.
기본 문법
-
select m from Member m where m.age > 18
- 엔티티와 속성은 대소문자를 구분. Member는 테이블 MEMBER를 의미하는 것이 아니라, 엔티티 객체 Member 클래스를 의미한다.
- 엔티티는 별칭(위에서는 Member 의 m)을 필요로 한다.
- JPQL 키워드(select, from 등)는 대소문자를 구분하지 않는다.
- 그 외 문법은 SQL과 거의 일치한다.
주요 매서드
- TypeQuery : 반환 타입이 명확할 때
- Query : 반환 타입이 명확하지 않을 때
- getSingleResult : 유일한 결과값을 리턴 할 때. 그렇지 않은 경우 예외가 발생한다.
- getResultList : 리스트 형태로 결과값을 리턴 할 때. 없을 경우 null을 반환. 주로 사용한다.
Member member1 = new Member();
member1.setName("kim");
em.persist(member1);
Member member2 = new Member();
member2.setName("lee");
em.persist(member2);
// 타입이 명확할 때 TypedQuery 를 리턴한다.
final TypedQuery<Member> typedQueryResult = em.createQuery("SELECT m FROM Member m", Member.class);
// 타입이 명확하지 않을 때 Query 를 리턴한다.
final Query query = em.createQuery("SELECT m FROM Member m where m.name = 'kim'");
// 결과가 컬렉션일 경우 getResultList()를 사용한다.
// 없을 경우 null을 반환한다. 그러므로 getSingleResult() 보다 선호한다.
final List<Member> resultList = typedQueryResult.getResultList();
System.out.println("size : " + resultList.size());
for (Member member : resultList) {
System.out.println("member = " + member);
}
// 결과가 정확하게 하나이다.
// 정확하게 하나가 아닐 경우 예외가 발생한다.
try {
final Member singleResult = typedQueryResult.getSingleResult();
}catch (Exception e){
System.out.println("예외 발생!!");
e.printStackTrace();
}
// 스트림을 지원한다.
// 동적 파라미터를 지원한다.
final Member singleResult2 =
em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", "kim")
.getSingleResult();
System.out.println("singleResult2.getName() = " + singleResult2.getName());
tx.commit();
프로젝션 SELECT
- select를 넘어서 다양한 값타입을 지원한다. select 절을 통해 스칼라(기본타입), 임베디드 타입, 컬렉션 타입 등을 리턴한다.
- 프로젝션으로 출력한 데이터는 엔티티 컨텍스트가 관리한다.
프로젝션과 영속성 컨텍스트
- 프로젝션으로 꺼내온 데이터는 영속성 컨텍스트가 관리한다.
- 객체를 변경할 경우 update 쿼리가 생성된다.
Member member = new Member();
member.setName("kim");
em.persist(member);
em.flush();
em.clear();
final List<Member> resultList = em.createQuery("select m from Member m", Member.class).getResultList();
final Member findMember = resultList.get(0);
findMember.setName("new Name"); // 수정할 경우?
Hibernate:
/* update
jpa8_query.b_jpql.Member */ update
Member
set
age=?,
name=?
where
MEMBER_ID=?
엔티티 프로젝션
- 프로젝션을 통하여 필드 중 엔티티를 출력할 수 있다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setName("kim");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
// final Team findTeam = em.createQuery("select m.team from Member m", Team.class).getResultList().get(0);
final Team findTeam = em.createQuery("select t from Member m join m.team t", Team.class).getResultList().get(0);
System.out.println("findTeam.getName() = " + findTeam.getName());
tx.commit();
Hibernate:
/* select
m.team
from
Member m */ select
team1_.id as id1_2_,
team1_.name as name2_2_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
findTeam.getName() = teamA
- 쿼리는
select m.team from Member m
지만, 실제로는 쿼리에 join이 함께 생성된다. (묵시적 join / 명시적 join을 참고) - 예상하지 못한 쿼리가 발생할 수 있다. 그러므로 jpql에 join을 명시한다.
- jpql은 on을 사용하지 않는 대신 m.team t 의 형태로 join 한다.
임베디드 타입 프로젝션
- 임베디드 타입에 대하여 출력할 수 있다.
Order order = new Order();
order.setAddress(new Address("busan", "bukkan-gil", "1234"));
em.persist(order);
em.createQuery("select o.address from Order o").getResultList();
Hibernate:
/* select
o.address
from
Order o */ select
order0_.city as col_0_0_,
order0_.street as col_0_1_,
order0_.zipcode as col_0_2_ from
ORDERS order0_
스칼라 타입 프로젝션
- 기본 타입을 출력한다.
final List resultList = em.createQuery("select m.name, m.age from Member m").getResultList();
Hibernate:
/* select
m.name,
m.age
from
Member m */ select
member0_.name as col_0_0_,
member0_.age as col_1_0_
from
Member member0_
- 스칼라 타입이 다양할 경우, Object, Object[], new SomethingDTO 로 받는다.
Member member = new Member();
member.setName("kim");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
// 타입을 알 수 없는 경우 Object로 받는다.
final List resultList = em.createQuery("select m.name, m.age from Member m").getResultList();
for (Object obj : resultList) {
final Object[] oArray = (Object[]) obj;
for (Object oValue : oArray) {
System.out.println("oValue = " + oValue);
}
}
// Object[] 로 리턴할 수도 있다.
final List<Object[]> resultList1 = em.createQuery("select m.name, m.age from Member m", Object[].class).getResultList();
for (Object[] objects : resultList1) {
for (Object object : objects) {
System.out.println("object = " + object);
}
}
// DTO를 활용한다.
// new 명령어로 조회하며, 해당 인자를 받는 생성자를 만들어야 한다. 생성자의 인자의 순서가 일치해야 한다.
final List<MemberDTO> resultList2 = em.createQuery("select new jpa8_query.b_jpql.MemberDTO(m.name, m.age) from Member m", MemberDTO.class).getResultList();
for (MemberDTO memberDTO : resultList2) {
System.out.println("memberDTO = " + memberDTO);
}
- 스칼라 타입을 하나로만 받을 경우, 해당 타입을 String.class로 명시하고 List
으로 리턴할 수 있다. - 하지만 여러 개를 받을 경우 불가피하게 Object 타입으로 받는다.
- Object가 아닌 객체를 통해 받을 수 있으며, 이 방법을 권장한다. 다만 new SomethingDTO를 쿼리 내부에 작성해야 하며, 그것의 패키지를 복잡하게 적어야 한다.
- QueryDSL은 이에 대한 간단한 빌드패턴을 제공한다.
페이징
- JPA는 페이징 기능을 아주 쉽게 제공한다.
- 페이징을 위한 다양한 방언을 매우 쉽게 해결한다.
for(int i=0; i<100; i++){
Member member = new Member();
member.setName("kim"+i);
member.setAge(10);
em.persist(member);
}
final List<Member> resultList = em.createQuery("select m from Member m order by m.id desc", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
for (Member member : resultList) {
System.out.println("member.getName() = " + member.getName());
}
조인
- 조인은 inner join, outer join, cross join 등을 지원한다.
- 연관관계가 있을 경우 join에 on을 생략한다. 다만 연관관계의 객체를 엔티티 그래프로 표현한다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setName("kim");
member.setAge(10);
member.changeTeam(team);
em.persist(member);
final String query = "select m from Member m join m.team t"; // 이너 조인
final String query = "select m from Member m left join m.team t"; // 아우터 조인
final String query = "select m from Member m, Team t where m.name = t.name"; // 크로스 조인
final List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member m : result) {
System.out.println("m.getName() = " + m.getName());
}
- on 의 경우 연관관계 없는 경우 사용한다.
- 문자열을 삽입하거나, 연관관계가 없는 테이블 간 조인할 때 사용한다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setName("kim");
member.setAge(10);
member.changeTeam(team);
em.persist(member);
// on 을 사용 가능. 연관관계가 없는 경우 사용한다.
// 아래의 경우 특정 문자열을 비교한다.
final String query = "select m from Member m join m.team t on t.name like '%team%'";
final List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member m : result) {
System.out.println("m.getName() = " + m.getName());
}
// 아래의 경우 연관관계 없는 객체 간 비교를 한다.
Order order = new Order();
order.setProductName("apple");
em.persist(order);
Member member2 = new Member();
member2.setName("apple");
em.persist(member2);
final List<Member> resultList = em.createQuery("select m from Member m join Order o on o.productName = m.name", Member.class).getResultList();
for (Member m : resultList) {
System.out.println("m.getName() = " + m.getName());
}
서브쿼리
- where, having 절에서 서브쿼리 지원.
- select 절에서는 하이버네이트에서 지원.
- from 은 하이버네이트를 포함한 모든 jpa 구현체에서 지원하지 않음.
- from의 경우
- 최대한 join으로 해결하지만, 실패할 경우
- 쿼리를 두 번 날린다음 자바에서 해결하지만, 실패할 경우
- nativeSQL로 해결.
- 그 외 exists, any, all, some, in 등 다양한 기능 제공
JPQL 타입 표현
- ‘HELLO’, ‘She’’s’
- 10L(Long), 10D(Double), 10F(Float)
- TRUE, FALSE
- pacakge.enums.MemberType.ADMIN (이넘타입. 패키지 포함)
Member member = new Member();
member.setName("kim");
member.setAge(10);
member.setType(MemberType.ADMIN);
em.persist(member);
em.flush();
em.clear();
final String query = "select m.name, 'HELLO', m.type, true from Member m where m.type = :usertype";
final List<Object[]> resultList = em.createQuery(query, Object[].class)
.setParameter("usertype", jpa8_query.b_jpql.MemberType.ADMIN) // enum 타입은 경로를 모두 지정해야 한다.
.getResultList();
for (Object[] objects : resultList) {
for (Object object : objects) {
System.out.println("object = " + object);
}
}
- 상속관계로서 타입을 비교할 경우 아래와 같이 type() 을 사용한다.
@Entity
@Inheritance
public abstract class Item {
@Id
@GeneratedValue
private Long id;
private int price;
private String name;
}
@Entity
@Getter
@Setter
public class Book extends Item{
private String author;
}
em.createQuery("select i from Item i where type(i)= Book ").getResultList();
CASE
- JPQL에서는 CASE를 지원한다.
Member member = new Member();
member.setName("kim");
member.setAge(10);
member.setType(MemberType.ADMIN);
em.persist(member);
// case 사용
final String query =
" select " +
"case when m.age <= 10 then '학생' " +
" when m.age >= 60 then '경로' " +
" else '일반' " +
"end " +
" from Member m ";
final List<String> resultList = em.createQuery(query, String.class)
.getResultList();
for (String s : resultList) {
System.out.println("s = " + s);
}
- coalesce, nullif 등도 지원한다.
Member member2 = new Member();
member2.setName(null);
member2.setAge(10);
member2.setType(MemberType.ADMIN);
em.persist(member2);
String query2 = "select coalesce(m.name, '이름없음') from Member m";
final List<String> resultList1 = em.createQuery(query2, String.class).getResultList();
for (String s : resultList1) {
System.out.println("s = " + s);
}
표준함수
- JPA는 표준 함수를 제공한다.
- JPA 표준함수 이외에, 각 DB마다의 함수, 그리고 사용자 정의 함수까지 지원한다.
-
DB에 의존적인 함수는 각 방언(MysqlDialect, H2Dialect)에 이미 등록되어, 바로 사용하면 된다. 사용자 정의 함수는 정의하여 사용하면 된다.
- 아래의 코드는 JPA 표준 함수 중, JPA 만 가지고 있는 함수인 size를 구현한 내용이다.
- 엔티티의 갯수를 구한다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setName("kim");
member.changeTeam(team);
em.persist(member);
Member member2 = new Member();
member2.setName("lee");
member2.changeTeam(team);
em.persist(member2);
em.flush();
em.clear();
String query1 = "select t.members.size from Team t";
final Integer singleResult = em.createQuery(query1, Integer.class).getSingleResult();
System.out.println(singleResult);