10. [JPA] JPQL

프로그래밍|2017. 8. 28. 17:14

테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리이다.

이게 뭔 말이냐 하면

다음과 같이 테이블 명이 JPQL_PERSON 인 Person 엔티티 클래스가 있다.

1
2
3
4
5
6
@Data
@Table(name = "JPQL_PERSON")
@Entity
class Person {
    ...    
}
cs


JPQL_PERSON 테이블의 데이터를 모두 추출하고자 할 때 SQL로 작성하게 되면 "select * from JPQL_PERSON" 처럼 될 것이다.

그럼 JPQL을 이용하면 어떻게 작성되어야 할까?

"select p from Person p" 처럼 하면 된다.

이처럼 JPQL은 객체지향쿼리이기 때문에 엔티티 클래스를 기반으로 쿼리를 작성해야 한다.


한번쯤은 궁금해 할지도 모르겠다.

왜? "select p from JPQL_PERSON p" 로 작성하면 안 되는 것인지?

JPQL로 작성된 객체지향 쿼리는 DB에 질의할 때 SQL로 변환되기 때문이다. 즉, 엔티티 객체를 기반으로 JPQL 쿼리를 분석하여 SQL로 변환하는데 JPQL_PERSON 이라는 엔티티 객체를 찾을 수 없기 때문이다.


코드를 보며 JPQL과 친숙해져 보자.

EntityManager 에는 createQuery 메서드가 존재한다. 해당 메서드의 첫 번째 아규먼트에 JPQL을 작성하면 된다.

엔티티 객체인 Person 의 모든 데이터를 추출하는 테스트 코드이다.

1
2
3
em.createQuery("SELECT p FROM Person p")
    .getResultList()
    .forEach(System.out::println);
cs

다음처럼 5건 추출하는 방법도 있다.

1
2
3
4
em.createQuery("SELECT p FROM Person p")
    .setMaxResults(5)
    .getResultList()
    .forEach(System.out::println);
cs


파라미터 바인딩은 다음과 같이 작성하면 된다.

1
2
3
TypedQuery typedQuery = em.createQuery("select p from Person p where p.department.name = :name", Person.class);
typedQuery.setParameter("name""development");
List<Person> list = typedQuery.getResultList();
cs


JPQL을 다루면서 몇 가지 중요하게 알아야 할 내용들이 있다.

 

첫 번째가 JPQL을 실행하게 되면 영속성 컨텍스트가 flush 된다.

흔히 영속성 컨텍스트가 flush 되는 경우는 엔티티 매니저의 flush 메서드를 강제로 호출하거나 트랜잭션이 종료되는 경우이다.

허나 JPQL을 이용하게 되면 실행되는 시점에 영속성 컨텍스트가 flush 된다.

이는 쓰기 지연의 성능 이점을 누리고 있는 상황에서 JPQL을 실행하게 되면 flush 되는 상황이 발생한다는 것이다.


아래 테스트 코드를 통해서 검증해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
@Transactional
public void 쓰기지연_처리후에_JPQL호출하면_flush되는지확인() {
    Person person = new Person();
    person.setId(20);
    person.setName("nklee");
    em.persist(person);
 
    // 영속성 컨텍스트에 where id = 20 인 Person 엔티티 존재 여부
    Person returnPersonEntity = em.find(Person.class20);
    assertThat("nklee", is(returnPersonEntity.getName()));
 
    System.out.println("-----------------------------------------");
    System.out.println("execute JPQL");
    System.out.println("-----------------------------------------");
    TypedQuery typedQuery = em.createQuery("select p from Person p where p.id = 20", Person.class);
    List<Person> list = typedQuery.getResultList(); // JPQL을 실행하게 되면서 영속성 컨텍스트를 flush 한다. 이때 DB로 insert문이 전송
    assertThat(1, is(list.size()));
    System.out.println("-----------------------------------------");
}
cs

위의 테스트 코드를 실행하면 typedQuery.getResultList(); 시점에 JPQL이 실행되면서 영속성 컨텍스트가 flush 된다.


로그에 출력되는 것과 같이 트랜잭션이 완료되지 않았는데 insert문이 DB로 전송되는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-----------------------------------------
execute JPQL
-----------------------------------------
[2017-08-28T15:44:54.902] main:INFO  - QueryTranslatorFactoryInitiator.initiateService(47) | HHH000397: Using ASTQueryTranslatorFactory
Hibernate: 
    insert 
    into
        jpql_person
        (dept_id, name, person_id) 
    values
        (?, ?, ?)
[2017-08-28T15:45:00.231] main:TRACE - BasicBinder.bind(53) | binding parameter [1] as [INTEGER] - [null]
[2017-08-28T15:45:00.232] main:TRACE - BasicBinder.bind(65) | binding parameter [2] as [VARCHAR] - [nklee]
[2017-08-28T15:45:00.233] main:TRACE - BasicBinder.bind(65) | binding parameter [3] as [INTEGER] - [20]
Hibernate: 
    select
        person0_.person_id as person_i1_19_,
        person0_.dept_id as dept_id3_19_,
        person0_.name as name2_19_ 
    from
        jpql_person person0_ 
    where
        person0_.person_id=20
[2017-08-28T15:45:00.253] main:TRACE - BasicExtractor.extract(61) | extracted value ([person_i1_19_] : [INTEGER]) - [20]
cs


두 번째는 JPQL로 조회한 엔티티는 영속 상태이다.

JPQL을 이용하여 쿼리하게 되면 리턴받는 엔티티들은 모두 영속 상태이다.

시퀀스 다이어그램을 통해서 좀더 자세히 이해해 보겠다.


1
2
3
4
5
1. JPQL을 작성한 후 실행하면 영속성 컨텍스트로 요청을 보낸다.
2. 영속성 컨텍스트는 1차 캐쉬에 엔티티 존재 여부 상관 없이 DB에 질의한다.
3. DB 질의를 통해 조회된 데이터는 영속성 컨텍스트가 다시 받고
4. 데이터를 전달 받은 영속성 컨텍스트는 엔티티를 초기화하고 캐쉬에 저장한다.
5. 캐쉬에 저장 후 엔티티를 반환
cs


테스트를 통해 검증해 보자.

1
2
3
4
5
6
TypedQuery typedQuery = em.createQuery("select p from Person p where p.id = 1", Person.class);
List<Person> persons = typedQuery.getResultList();
Person person = persons.get(0);
 
boolean isLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(person);
assertThat(true, is(isLoaded));
cs


여기까지 JPQL에 대해서 알아보았다.

실제 실무에서 JPQL 쿼리를 직접 작성한적은 없다. 복잡하기도 하고 type safe 하지 않은 코드가 작성되기 때문이다.

나는 QueryDSL 이라는 JPQL 빌더를 이용하였다.


[전체 소스]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class JPQLTest {
 
    public static final int CNT = 10;
 
    @PersistenceContext
    private EntityManager em;
 
    @Before
    public void before() {
        Department department = new Department();
        department.setId(1);
        department.setName("development");
 
        for (int i = 0; i < CNT; i++) {
            Person person = new Person();
            person.setId(i);
            person.setName("nklee" + i);
            person.setDepartment(department);
            em.persist(person);
        }
 
        em.flush();
        em.clear();
    }
 
    @Test
    @Transactional
    public void testJPQL() {
        // JPQL은 객체지향 쿼리 (Person은 class 이름이다. Table 이름이 아님)
        em.createQuery("SELECT p FROM Person p")
                .getResultList()
                .forEach(System.out::println);
 
        System.out.println("===================================================");
        em.createQuery("SELECT p FROM Person p")
                .setMaxResults(5)
                .getResultList()
                .forEach(System.out::println);
 
        Query query = em.createQuery("select p from Person p");
        List<Person> list = query.getResultList();
        assertThat(list.size(), is(CNT));
    }
 
    @Test
    @Transactional
    public void testCompositTypeQuery() {
        TypedQuery typedQuery = em.createQuery("select p from Person p", Person.class);
        List<Person> personList = typedQuery.getResultList();
        personList.forEach(System.out::println);
 
        TypedQuery typedQuery1 = em.createQuery("select p.department from Person p", Department.class);
        List<Department> departmentList = typedQuery1.getResultList();
        departmentList.forEach(System.out::println);
    }
 
    @Test
    @Transactional
    public void testFilter() {
        TypedQuery typedQuery = em.createQuery("select p from Person p where p.department.name = :name", Person.class);
        typedQuery.setParameter("name""development");
 
        List<Person> list = typedQuery.getResultList();
        assertThat(list.size(), is(10));
    }
 
    @Test
    @Transactional
    public void 쓰기지연_처리후에_JPQL호출하면_flush되는지확인() {
        Person person = new Person();
        person.setId(20);
        person.setName("nklee");
        em.persist(person);
 
        // 영속성 컨텍스트에 where id = 20 인 Person 엔티티 존재 여부
        Person returnPersonEntity = em.find(Person.class20);
        assertThat("nklee", is(returnPersonEntity.getName()));
 
        System.out.println("-----------------------------------------");
        System.out.println("execute JPQL");
        System.out.println("-----------------------------------------");
        TypedQuery typedQuery = em.createQuery("select p from Person p where p.id = 20", Person.class);
        List<Person> list = typedQuery.getResultList(); // JPQL을 실행하게 되면서 영속성 컨텍스트를 flush 한다. 이때 DB로 insert문이 전송
        assertThat(1, is(list.size()));
        System.out.println("-----------------------------------------");
    }
 
    @Test
    @Transactional
    public void JPQL로_조회한_엔티티는_영속상태이다() {
        TypedQuery typedQuery = em.createQuery("select p from Person p where p.id = 1", Person.class);
        List<Person> persons = typedQuery.getResultList();
        Person person = persons.get(0);
 
        boolean isLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(person);
        assertThat(true, is(isLoaded));
    }
}
 
 
/**
 * create table jpql_person (
 * person_id integer generated by default as identity,
 * name varchar(255),
 * dept_id integer,
 * primary key (person_id)
 * )
 * <p>
 * alter table jpql_person
 * add constraint FK7hjuswhyf2f04k4ene1hlj17l
 * foreign key (dept_id)
 * references jpql_dept
 */
@Data
@Table(name = "JPQL_PERSON")
@Entity
class Person {
 
    @Id
    @Column(name = "PERSON_ID")
    private int id;
 
    private String name;
 
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "DEPT_ID")
    private Department department;
 
    public void setDepartment(Department department) {
        this.department = department;
        department.addPerson(this);
    }
}
 
@Data
@ToString(exclude = "personList")
@Table(name = "JPQL_DEPT")
@Entity
class Department {
 
    @Id
    @Column(name = "DEPT_ID")
    private int id;
 
    private String name;
 
    @OneToMany(mappedBy = "department")
    private List<Person> personList = new ArrayList<>();
 
    public void addPerson(Person person) {
        personList.add(person);
    }
}
cs


댓글()