10. [JPA] JPQL
테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리이다.
이게 뭔 말이냐 하면
다음과 같이 테이블 명이 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.class, 20); 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.class, 20); 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 |
'프로그래밍' 카테고리의 다른 글
13. [JPA] 트랜잭션 격리 수준 (Transaction Isolation Level) (0) | 2017.09.12 |
---|---|
12. [JPA] 복합키 (0) | 2017.09.08 |
11. [JPA] Querydsl (0) | 2017.09.01 |
spring data elasticsearch 사용해 보기 (6) | 2017.08.31 |
9. [JPA] @Enumerated (0) | 2017.08.23 |
8. [JPA] Attribute Converter (0) | 2017.08.23 |
7. [JPA] fetch type - 로딩 기법 (0) | 2017.08.21 |
6. [JPA] 영속성 전이 - Cascade (0) | 2017.08.17 |