본문 바로가기
Dev/JPA

[JPA] 객체지향 쿼리 문법 - 중급문법(2)

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

다형성 쿼리

상속 관계에 있는 엔티티 조회 시 다형성을 사용하여 특정 엔티티 타입만 조회할 수 있다.

 

예제 구조

Item (부모) / Album, Movie, Book (자식)

Type

  • 조회 대상을 특정 자식으로 한정
  • TYPE(부모 엔티티 별칭)
  • JPQL의 TYPE()이 SQL에서는 DTYPE(= 구분컬럼)으로 변경되어 실행된다.
Book book = new Book();
book.setName("new bookname");
book.setAuthor("new author");
em.persist(book);

Movie movie = new Movie();
movie.setActor("new actor");
movie.setDirector("new director");
em.persist(movie);

// TYPE()
String query = "select i from Item i where type(i) in (Book, Movie)";
em.createQuery(query, Item.class).getResultList();

TYPE SQL

Treat

  • 자바의 형변환과 유사하다.
  • 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
  • FROM, WHERE, SELECT(하이버네이트 지원)절에서 사용한다.
  • TREAT(부모 엔티티AS 자식 엔티티).자식 엔티티가 갖고있는 필드
Book book = new Book();
book.setName("new bookname");
book.setAuthor("new author");
em.persist(book);

Movie movie = new Movie();
movie.setActor("new actor");
movie.setDirector("new director");
em.persist(movie);

// TREAT()
String query = "select i from Item i where treat(i as Book).author = 'new author'";
List<Item> result = em.createQuery(query, Item.class).getResultList();

for(Item i : result) {
    System.out.println("item : " + i.getName());
}

TREAT SQL

엔티티 직접 사용

JPQL에서 엔티티를 직접 사용하게 되면 해당 엔티티의 기본키 값을 사용한다.

기본키 값

엔티티나 식별자를 파라미터로 전달해도 동일하게 기본값을 사용하여 동작한다.

 

엔티티 파라미터 바인딩

// 엔티티를 파라미터로 전달
String query = "select m from Member m where m = :member";
List<Member> result = em.createQuery(query, Member.class).setParameter("member", member1).getResultList();

for (Member member : result) {
    System.out.println("member : " + member);
}

식별자 파라미터 바인딩

// 식별자를 파라미터로 전달
String query = "select m from Member m where m.id = :memberId";
List<Member> result = em.createQuery(query, Member.class).setParameter("memberId", member1.getId()).getResultList();

for (Member member : result) {
    System.out.println("member : " + member);
}

동작 결과

엔티티 파라미터 (좌측) / 식별자 파라미터 (우측)

외래키 값

외래키는 엔티티 내 @JoinColumn으로 설정된 컬럼 값을 사용한다.

@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;

    @Enumerated(EnumType.STRING)
    private MemberType type;

    // getter, setter, toString
}
// 외래키 값
// 비교 컬럼은 @JoinColumn으로 지정된 컬럼
// 비교 값은 엔티티 기본키 값
String query = "select m from Member m where m.team = :team";
List<Member> result = em.createQuery(query, Member.class).setParameter("team", teamA).getResultList();

비교 컬럼은 @JoinColumn으로 지정된 컬럼, 비교 값은 엔티티 기본키 값

Named 쿼리

미리 정의해서 이름을 부여해두고 사용하는 JPQL

특징

  • 정적 쿼리
  • 어노테이션 또는 XML에 정의한다.
  • XML 설정이 우선권을 가지며 애플리케이션 운영 환경에 따라 다른 XML을 배포할 수 있다.
  • 애플리케이션 로딩 시점에 쿼리를 검증하고 초기화 후 재사용한다.
  • 실무에서는 Spring Data JPA를 사용하여 DAO 단에서 @Query 어노테이션을 사용하여 Named쿼리처럼 사용하며 해당 방식이 더 효율적이다.

예제

어노테이션 방식

@Entity
@NamedQuery(
    name = "Member.findByUsername"
    , query = "select m from Member m where m.username = :username"
)
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;

    @Enumerated(EnumType.STRING)
    private MemberType type;

    // getter, setter, toString
}

XML 방식

<!-- META-INF/ormMember.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
    <named-query name="Member.findByUsername">
        <query>
            <![CDATA[
                select m
                from Member m
                where m.username = :username
            ]]>
        </query>
    </named-query>
    <named-query name="Member.count">
        <query>select count(m) from Member m</query>
    </named-query>
</entity-mappings>
<!-- META-INF/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">
        <mapping-file>META-INF/ormMember.xml</mapping-file>  <!-- Named쿼리 설정 -->
        <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" />
            <property name="hibernate.default_batch_fetch_size" value="100" />
        </properties>
    </persistence-unit>
</persistence>

Main

// Named 쿼리
List<Member> result = em.createNamedQuery("Member.findByUsername", Member.class)
                        .setParameter("username", "member1")
                        .getResultList();

for (Member member : result) {
    System.out.println("member : " + member);
}

동작 결과

Named쿼리 SQL 및 결과

벌크 연산

특정 PK로 지정된 데이터의 UPDATE, DELETE를 제외한 나머지 UPDATE, DELETE 동작 (쿼리 한번으로 여러 테이블 로우 변경)

특징

  • executeUpdate()를 사용하여 영향받은 엔티티 수를 반환 받을 수 있다.
  • 기본적으로 UPDATE, DELETE를 지원하며 하이버네이트 구현체 사용 시 INSERT INTO SELECT를 사용할 수 있다.

예제

// 벌크 연산
// 전체 회원 나이 20살 변경
int updateCount = em.createQuery("update Member m set m.age = 20").executeUpdate();

System.out.println("update count : " + updateCount);

UPDATE SQL 및 결과

주의

  • 벌크 연산은 영속성 컨텍스트를 무시하고 바로 데이터베이스에 직접 쿼리한다.
  • 데이터베이스에 직접 쿼리하기 때문에 영속성 컨텍스트에 있는 엔티티 값 변경 후 조회를 해도 원하는 결과를 얻을 수 없다. 따라서 영속성 컨텍스트가 비어있는 상태로 벌크 연산을 먼저 실행하거나 벌크 연산 후 영속성 컨텍스트를 초기화 한 뒤 조회동작을 하게되면 원하는 결과를 얻을 수 있다.

영속성 컨텍스트 미초기화 예제

// 벌크 연산
// 영속성 컨텍스트 초기화 X
// 벌크연산으로 나이 20세로 변경했지만 조회 시 변경되어있지 않음.
// Query 실행 시 자동 flush
em.createQuery("update Member m set m.age = 20").executeUpdate();

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember.age : " + findMember.getAge());

조회한 회원의 나이가 변경되지 않음

영속성 컨텍스트 초기화 예제

// 벌크 연산
// 영속성 컨텍스트 초기화 후 조회
// 벌크연산으로 나이 20세로 변경 후 영속성 컨텍스트 초기화하고 조회
// Query 실행 시 자동 flush
em.createQuery("update Member m set m.age = 20").executeUpdate();

em.clear();

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember.age : " + findMember.getAge());

조회한 회원의 나이가 변경되었다.

728x90
반응형

댓글