본문 바로가기
Dev/JPA

[JPA] 객체지향 쿼리 언어 - 기본문법 (2)

by dev_jsk 2021. 9. 8.
728x90
반응형

조인

내부 조인 (INNER JOIN)

SQL INNER JOIN과 동일하며 INNER는 생략 가능하다.

// 내부조인 (inner 생략 가능)
String query = "select m from Member m inner join m.team t";
List<Member> result = em.createQuery(query, Member.class).getResultList();

내부 조인 SQL

외부 조인 (LEFT OUTER JOIN)

SQL LEFT OUTER JOIN과 동일하며 OUTER는 생략 가능하다.

// 외부조인 (outer 생략 가능)
String query = "select m from Member m left outer join m.team t";
List<Member> result = em.createQuery(query, Member.class).getResultList();

외부 조인 SQL

세타 조인

연관관계가 전혀 없는 조인으로 WHERE절을 사용하여 조인한다. (INNER JOIN 방식)

// 세타조인 (크로스 조인, where 절)
String query = "select m from Member m, Team t where m.username = t.name";
List<Member> result = em.createQuery(query, Member.class).getResultList();

세타 조인 SQL

ON절

조인 대상 필터링

ON절을 사용하여 조인 대상을 필터링 할 수 있다.

// 조인 대상 필터링
String query = "select m from Member m left join m.team t on t.name = 'teamA'";
List<Member> result = em.createQuery(query, Member.class).getResultList();

조인 대상 필터링 SQL

연관관계 없는 엔티티 외부 조인

ON절을 사용하여 연관관계가 없는 엔티티를 조인할 수 있다.

// 연관관계 없는 외부조인
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();

연관관계 없는 엔티티 외부 조인

서브쿼리

지원 함수

EXISTS / NOT EXISTS

서브쿼리 결과가 존재하면 참

// EXISTS
String query = "select m from Member m where exists (select t from m.team t where t.name = 'teamA')";
List<Member> result = em.createQuery(query, Member.class).getResultList();

EXISTS SQL

IN / NOT IN

서브쿼리 결과 중 하나라도 같은 것이 있으면 참

ALL

조건을 모두 만족하면 참

// ALL
String query = "select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)";
List<Order> result = em.createQuery(query, Order.class).getResultList();

ALL SQL

ANY / SOME

조건을 하나라도 만족하면 참

// ANY
String query = "select m from Member m where m.team = ANY (select t from Team t)";
List<Member> result = em.createQuery(query, Member.class).getResultList();

ANY SQL

JPA 서브쿼리 한계

  • JPA는 WHERE, HAVING절에서만 서브쿼리를 사용 가능하고 하이버네이트는 SELECT절에서도 사용 가능하다.
  • FROM절의 서브쿼리는 현재 JPQL에서 불가능하다.
  • FROM절의 서브쿼리는 조인으로 풀어서 해결하거나 조인이 불가능하면 Native SQL을 사용 또는 쿼리를 분해해서 실행하여 결과를 애플리케이션에서 조립하는 방법을 사용한다.

JPQL 타입 표현과 기타식

타입 표현

  • 문자, 숫자, Boolean형은 SQL과 동일하게 사용
  • ENUM 타입의 경우 보통 파라미터 바인딩으로 사용하지만 쿼리에 작성해야 한다면 패키지명까지 작성해야 한다.
  • 엔티티 타입의 경우 TYPE(부모 엔티티) = 자식 엔티티방식으로 사용한다. (상속 관계에서만 사용)

예제 (기본 타입 + ENUM)

기본타입 및 ENUM 타입 예제로 회원 유형이 관리자인 데이터 조회

@Entity
public class Member {
    
    @Id @GeneratedValue
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    // ENUM 타입
    @Enumerated(EnumType.STRING)
    private MemberType type;

    // getter, setter, toString
}

 

public enum MemberType {
    ADMIN, USER
}

 

Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setTeam(team);
member.setType(MemberType.ADMIN);  // JPQL 타입 표현
em.persist(member);

em.flush();
em.clear();

// 타입 표현 (기본타입 + ENUM)
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();

for(Object[] objects : result) {
    System.out.println("object[0] = " + objects[0]);
    System.out.println("object[1] = " + objects[1]);
    System.out.println("object[2] = " + objects[2]);
}

타입 표현 SQL 및 결과

예제 (기타식)

기타식 예제로 나이가 0 ~ 10세인 데이터 조회

// 기타식(between)
String query = "select m.username, 'HELLO', true from Member m where m.age between 0 and 10";
List<Object[]> result = em.createQuery(query).getResultList();

for(Object[] objects : result) {
    System.out.println("object[0] = " + objects[0]);
    System.out.println("object[1] = " + objects[1]);
    System.out.println("object[2] = " + objects[2]);
}

기타식 SQL 및 결과

예제 (엔티티 타입)

엔티티 타입 표현 예제로 이전 실습 패키지를 사용하여 DTYPE = 'Book'인 데이터 조회

// jpashop package
Book book = new Book();
book.setName("JPA");
book.setAuthor("jsk");
em.persist(book);

// 엔티티 타입
em.createQuery("select i from Item i where type(i) = Book", Item.class).getResultList();

엔티티 타입 SQL

조건식

CASE

기본 SQL 문법과 동일하게 사용

// CASE
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).getResultList();

for(String s : result) {
    System.out.println("s : " + s);
}

CASE SQL 및 결과

COALESCE

하나씩 조회해서 NULL이 아니면 반환

Member member = new Member();
member.setUsername(null); // coalesce
member.setAge(10);
em.persist(member);

em.flush();
em.clear();

// COALESCE
String query = "select coalesce(m.username, '이름 없는 회원') from Member m";
List<String> result = em.createQuery(query).getResultList();

for(String s : result) {
    System.out.println("s : " + s);
}

COALESCE SQL 및 결과

NULLIF

두 값이 같으면 NULL 반환, 다르면 첫번째 값 반환

Member member = new Member();
member.setUsername("관리자"); // nullif
member.setAge(10);
em.persist(member);

em.flush();
em.clear();

// NULLIF
String query = "select nullif(m.username, '관리자') from Member m";
List<String> result = em.createQuery(query).getResultList();

for(String s : result) {
    System.out.println("s : " + s);
}

NULLIF SQL 및 결과

JPQL 함수

기본함수

JPA가 제공하는 표준 함수로 데이터베이스에 상관 없이 사용 가능하다.

 

CONCAT

문자열 더하기

// CONCAT
String query = "select concat('a', 'b') from Member m";
List<String> result = em.createQuery(query).getResultList();

for(String s : result) {
    System.out.println("s : " + s);
}

CONCATE SQL 및 결과

SUBSTRING

문자열 자르기

// SUBSTRING
String query = "select substring('abc', 2, 3) from Member m";
List<String> result = em.createQuery(query).getResultList();

for(String s : result) {
    System.out.println("s : " + s);
}

SUBSTRING SQL 및 결과

TRIM

공백 제거

 

LOWER, UPPER

대/소문자 변경

 

LENGTH

문자열 길이 측정

 

LOCATE

특정 문자 시작 위치 찾기

// LOCATE
String query = "select locate('ef', 'abcdefg') from Member m";
List<Integer> result = em.createQuery(query).getResultList();

for(Integer i : result) {
    System.out.println("i : " + i);
}

LOCATE SQL 및 결과

ABS, SQRT, MOD

절대값, 제곱근, 나머지 구하기

 

SIZE

컬렉션 크기

// SIZE
String query = "select size(t.members) from Team t";
List<Integer> result = em.createQuery(query).getResultList();

for(Integer i : result) {
    System.out.println("i : " + i);
}

SIZE SQL 및 결과

INDEX

값 타입 컬렉션에서 위치값 찾기는 함수. 중간에 리스트에서 값 빠질경우 NULL로 들어오기 때문에 거의 사용하지 않는다.

사용자 정의 함수

사용자가 구성한 함수로 해당 함수를 만들고 사용하기 위해서는 다음과 같이 설정해야 한다.

  • 사용하는 데이터베이스 방언 클래스를 상속받는 클래스를 생성한다.
  • 해당 클래스에 사용자 정의 함수를 구성한다.
  • persistence.xml내 데이터베이스 dialect 옵션을 해당 클래스로 지정한다.
  • SQL에서 사용자 정의 함수를 이용한다.

예제

- MyH2Dialect.java

public class MyH2Dialect extends H2Dialect {  // 사용하는 DB 방언 상속
    // 사용자 정의 함수 구성
    public MyH2Dialect() {
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}

- 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="hello">
        <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="dialect.MyH2Dialect" />  <!-- 사용자 정의 함수 -->

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.use_sql_comments" value="true" />
            <property name="hibernate.jdbc.batch_size" value="10" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

- JpaMain.java

Member member = new Member();
member.setUsername("관리자");
member.setAge(10);
em.persist(member);

Member member2 = new Member();
member2.setUsername("관리자2");
member2.setAge(11);
em.persist(member2);

em.flush();
em.clear();

// 사용자 정의 함수
String query = "select group_concat(m.username) from Member m";
List<String> result = em.createQuery(query).getResultList();

for(String s : result) {
    System.out.println("s : " + s);
}

사용자 정의 함수 SQL 및 결과

728x90
반응형

댓글