[자바 ORM 표준 JPA] JPQL(Java persistence Query Language)
[자바 ORM 표준 JPA] JPQL(Java persistence Query Language)
JPQL(Java persistence Query Language)
JPQL - 기본 문법과 기능#
JPA에서 제공되는 다양한 쿼리 방법#
- JPQL은 객체지향 쿼리 언어다. 따라서 테이블을 대상으로 쿼리 하는 것이 아니라 엔티티를 대상으로 쿼리 한다.
- JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.
객체모델
DB모델
JPQL을 예제 작성을 위한 Maven 프로젝트를 생성 하겠습니다.
jpa-basic\jpql
pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jpql</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- JPA 하이버네이트 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.22.Final</version>
</dependency>
<!-- H2 데이터베이스 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
</dependencies>
</project>
/resources/META-INF/persistence.xml 추가
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="jpql">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<!--
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle8iDialect"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
-->
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/> <!-- 실행 sql 로깅 -->
<property name="hibernate.format_sql" value="true"/> <!-- 실행 sql 포메팅 -->
<!-- /* insert relativemapping.Member */ JPA가 Inser 를 해서 이 쿼리가 나왔다는 것을 주석으로 설명 -->
<property name="hibernate.use_sql_comments" value="true"/>
<!-- 한번에 같은 데이터 베이스에 데이터를 집어넣을때 모아서 한번에 인서트 하는 jdbc batch의 수를 지정-->
<property name="hibernate.jdbc.batch_size" value="10"/>
<property name="hibernate.hbm2ddl.auto" value="create" /> <!-- create, create-drop, update, validate, none -->
</properties>
</persistence-unit>
</persistence>
src/main/java/jpql/domain/Member.java
Member.java
package jpql.domain;
import javax.persistence.*;
@Entity
public class Member {
public Member(){
}
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team = new 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;
}
public jpql.domain.Team getTeam() {
return team;
}
public void setTeam(jpql.domain.Team team) {
this.team = team;
}
@Override
public String toString() {
return "Member{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}
}
src/main/java/jpql/domain/Team.java
Team.java
package jpql.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
public Team() {
}
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
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;
}
public List<Member> getMembers() {
return members;
}
public void setMembers(List<Member> members) {
this.members = members;
}
}
src/main/java/jpql/domain/Order.java
Order.java
package jpql.domain;
import javax.persistence.*;
@Entity
@Table(name = "ORDERS") //ORDER 가 예약어라 ORDERS로 테이블 명칭 지정
public class Order {
public Order() {
}
@Id @GeneratedValue
private Long id;
private int orderAmount;
@Embedded
private Address orderAddress;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getOrderAmount() {
return orderAmount;
}
public void setOrderAmount(int orderAmount) {
this.orderAmount = orderAmount;
}
public Address getOrderAddress() {
return orderAddress;
}
public void setOrderAddress(Address orderAddress) {
this.orderAddress = orderAddress;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
src/main/java/jpql/domain/Address.java
Address.java
package jpql.domain;
import javax.persistence.Embeddable;
import java.util.Objects;
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public String getCity() {
return city;
}
private void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
private void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
private void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Address)) return false;
Address address = (Address) o;
return Objects.equals(getCity(), address.getCity()) && Objects.equals(getStreet(), address.getStreet()) && Objects.equals(getZipcode(), address.getZipcode());
}
@Override
public int hashCode() {
return Objects.hash(getCity(), getStreet(), getZipcode());
}
}
src/main/java/jpql/domain/Product.java
Product.java
package jpql.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Product {
public Product() {
}
@Id @GeneratedValue
private Long id;
private String name;
private int price;
private int stockAmount;
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;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getStockAmount() {
return stockAmount;
}
public void setStockAmount(int stockAmount) {
this.stockAmount = stockAmount;
}
}
src/main/java/jpql/JpqlMain.java
JpqlMain.java
package jpql;
import jpql.domain.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpqlMain {
//psvm 단축키로 생성 가능
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // [트랜잭션] 시작
try{
Team team1 = new Team();
team1.setName("팀1");
Member member1 = new Member();
member1.setUsername("오과장");
member1.setAge(35);
member1.setTeam(team1);
Member member2 = new Member();
member2.setUsername("육대리");
member2.setAge(28);
member2.setTeam(team1);
em.persist(team1);
em.persist(member1);
em.persist(member2);
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
JPQL 문법#
SELECT_문 :: =
SELECT_절
FROM_절
[WHERE_절]
[GROUP BY_절]
[HAVING_절]
[ORDER BY_절]
UPDATE_문 ::
UPDATE_절
[WHERE_절]
DELETE_문 ::
DELETE_절
[WHERE_절]
SQL 문법과 동일 하다고 보시면 됩니다.
UPDATE 문은 뒤에 벌크 연산이라는 새로운 기능 전 사원 10% 연봉 인상 등 내용 설명 예정
JPQL 문법#
- SELECT M FROM Member as m WHERE m.age > 18
- 엔티티 속성은 대소문자 구분 O (Member, age)
- JPQL 키워드는 대소문자 구분 X (SELECT, FROM, where)
- 엔티티 이름 사용, 테이블 이름이 아님 (Member) (컬럼명 대신 엔티티 속성명)
- 별칭은 필수(m) (as는 생략 가능)
JPQL 집합과 정렬#
ansi 표준 SQL의 기능과 사용법 동일
SELECT
COUMT(m) // 회원수
, SUM(m.aget) // 나이 합
, AVG(m.aget) // 나이 평균
, MAX(m.aget) // 최대 나이
, MIN(m.aget) // 최소 나이
FROM Member m
- GROUP BY, HAVING
- ORDER BY
TypeQuery, Query#
- TypeQuery : 반환 타입이 명확할 떄 사용
- Query : 반환 타입이 명확하지 않을 때 사용
TypeQuery
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m", Member.class);
Query
Query<Member> query =
em.createQuery("SELECT m.username, m.age FROM Member m");
인텔리 제이에서
em.createQuery("SELECT m from Member m", Member.class);
이 로우를 블록으로 잡고 Ctrl + Alt + V 를 누르면, 아래와 같이 메서드 콜하는 구문에서 자동으로 리턴값 변수 생성해준다.
createQuery에 2번째 파라미터를 넣으면 반환 타입은 자동으로 TypedQuery
으로 설정 해 주는것을 알 수 있습니다.
반면에 createQuery에 2번째 파라미터를 안넣으면 반환 타입은 자동으로 Query로 설정 해 주는것을 알 수 있습니다.
그리고 하나의 값을 반환 받고자 할때,
TypedQuery<String> query = em.createQuery("SELECT m.username from Member m", String.class);
이런식으로 문자열로 이름을 반환 타입을 지정해 받을 수 있지만, 이름과, 나이 복합적인 값을 받아와 타입이 불명확한 경우 Query로 반환 타입을 지정하여 사용합니다.
Query query = em.createQuery("SELECT m.username, m.age from Member m");
결과 조회 API#
- query.getResultList() : 결과가 하나 이상일 때, 리스트 반환
- 결과가 없으면 빈 리스트 반환
- query.getSingleResult() : 결과가 정확히 하나일 때, 단일 객체 반환
- 결과가 없으면 : javax.persistence.NoResultException
- 둘 이상이면 : javax.persistence.NonUniqueResultException
query.getResultList();
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
//iter + tab 자동 이터레이터 생성
for (Member member : resultList) {
System.out.println(member);
}
query.getSingleResult();
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.id = 1L", Member.class);
Member singleResult = query.getSingleResult();
System.out.println(singleResult);
getSingleResult()는 값 1건이 정확히 있을 때 사용
1건 이상일 때 javax.persistence.NonUniqueResultException는 것은 그래도 괜찮지만, 결과가 없을 때도 Exception 터지기 때문에 Try Catch로 예외처리를 각각 해줘야 하기 때문에 불편한 점이 있습니다. 그래서 논란이 많은데
Spring Date JPA 를 사용하게 되면은 여기에서는 추상화 되어, Null을 반환 하거나 Optional을 반환하게 되어있습니다. Spring Date JPA와 헤깔리면 안됩니다.
파라미터 바인딩 - 이름 기준, 위치 기준#
이름 기준 매핑
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.username= :username);
query.setParameter("username", usernameParam);
Member.java
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.username = :username", Member.class);
query.setParameter("username", "오과장");
Member singleResult = query.getSingleResult();
System.out.println(singleResult.toString());
console
Hibernate:
/* SELECT
m
FROM
Member m
WHERE
m.username = :username */ 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_
where
member0_.username=?
Member{id=2, username='오과장', age=35, team=jpql.domain.Team@c7a977f}
순서 기준 매핑
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.username= ?1);
query.setParameter(1, usernameParam);
Member.java
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.username = ?1", Member.class);
query.setParameter(1, "오과장");
Member singleResult = query.getSingleResult();
System.out.println(singleResult.toString());
console
Hibernate:
/* SELECT
m
FROM
Member m
WHERE
m.username = ?1 */ 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_
where
member0_.username=?
Member{id=2, username='오과장', age=35, team=jpql.domain.Team@c7a977f}
위치 기준 보다 이름 기준을 권장합니다. 위치 기반이면, 중간에 하나가 끼어들면 다 수정해야하며, 잘못 매핑되면 장애로 이어질 가능성이 다분합니다.