[자바 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로 변환된다.

객체모델

contact

DB모델

contact

JPQL을 예제 작성을 위한 Maven 프로젝트를 생성 하겠습니다.

contact

jpa-basic\jpql

contact

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 를 누르면, 아래와 같이 메서드 콜하는 구문에서 자동으로 리턴값 변수 생성해준다.

contact

createQuery에 2번째 파라미터를 넣으면 반환 타입은 자동으로 TypedQuery 으로 설정 해 주는것을 알 수 있습니다.

반면에 createQuery에 2번째 파라미터를 안넣으면 반환 타입은 자동으로 Query로 설정 해 주는것을 알 수 있습니다.

contact

그리고 하나의 값을 반환 받고자 할때,

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}

위치 기준 보다 이름 기준을 권장합니다. 위치 기반이면, 중간에 하나가 끼어들면 다 수정해야하며, 잘못 매핑되면 장애로 이어질 가능성이 다분합니다.

참고#