2020년 7월 24일 금요일

Querydsl을 이용한 JPQL 쿼리

4장. Querydsl을 이용한 JPQL 쿼리

http://ojc.asia/bbs/board.php?bo_table=LecJpa&wr_id=344

이번 장에서 Querydsl을 이용한 JPQL 작성방법을 상세하게 살펴보겠습니다.

4.1. 테스트 프로젝트 만들기

"부록 7.1 Querydsl JPA Query with MySQL" 부분을 참고하여 진행합니다.

application.properties

환경설정 파일에서 다음처럼 변경합니다.

spring.datasource.initialize=true

spring.jpa.hibernate.ddl-auto=create

4.2. JPA Query 학습

4.2.1. Select

Q 타입 클래스를 사용하여 메소드 기반으로 쿼리를 작성할 수 있습니다.

1. 기본 사용법

DeptDao.java

package com.example.employee.repository;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;

import com.example.employee.model.Dept;

import com.example.employee.model.QDept;

import com.querydsl.jpa.impl.JPAQuery;

@Repository

public class DeptDao {

@PersistenceContext

private EntityManager entityManager;

public Dept getByDname(String dname){

// 쿼리타입 클래스내 static 멤버변수가 가리키는 인스턴스를 그대로 사용할 수 있다.

QDept dept = QDept.dept;

// 별칭을 생성자에 주고 새로 쿼리타입 클래스의 인스턴스를 만들어 사용할 수도 있다.

//QDept department = new QDept("department");

JPAQuery<?> query = new JPAQuery<Void>(entityManager);

Dept accounting = query.select(dept).from(dept)

.where(dept.dname.eq(dname)).fetchOne();

return accounting;

}

}

질의 결과가 하나의 로우라면 fetchOne 메소드를 사용합니다. 얻고자 하는 결과의 자료형에 따라 메소드를 선택합니다.

제네릭이 Long 인 경우

제네릭이 엔티티 클래스 Dept 인 경우

fetchOne 메소드는 결과가 하나 이상이면 TooManyRowsException 예외가 발생합니다. fetchFirst 메소드는 TooManyRowsException 예외가 발생하지 않고 데이터가 있다면 첫 번째 로우를 리턴합니다. 둘 다 데이터가 없으면 null을 리턴합니다.

DeptDaoTest.java

package com.example.employee.repository;

import static org.junit.Assert.*;

import static org.hamcrest.CoreMatchers.*;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import org.springframework.test.context.junit4.SpringRunner;

import com.example.employee.model.Dept;

@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

public class DeptDaoTest {

@Autowired

private DeptDao deptDao;

@Test

public void testGetByDname() {

String dname = "ACCOUNTING";

Dept accounting = deptDao.getByDname(dname);

System.out.println(accounting);

assertThat(accounting, is(notNullValue()));

}

}

SQL 로그

콘솔창에 기록된 SQL쿼리를 살펴보겠습니다.

결과 로그에서 앨리어스를 'dept0_'에서 'd'로 바꾸어 보기 편하게 수정했습니다.

select

d.deptno as deptno1_0_,

d.dname as dname2_0_,

d.loc as loc3_0_

from dept d

where d.dname='ACCOUNTING'

2. 1차 캐싱

1차 캐싱 서비스를 제공받는지 테스트해 보겠습니다.

DeptDaoTest.java

클래스에 testEntityManagerCaching 메소드를 직접 추가합니다.

@Transactional

@Test

public void testEntityManagerCaching() {

String dname = "ACCOUNTING";

// 쿼리 수행

Dept accounting1 = deptDao.getDeptByDname(dname);

System.out.println(accounting1);

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

// 한번 더 같은 쿼리를 수행

Dept accounting2 = deptDao.getDeptByDname(dname);

System.out.println(accounting2);

// 테이블의 로우(행)에 일치여부는 키 값이 같은지 여부로 판단한다.

// 객체도 그에 맞게 처리하기 위해서 equals, hashCode 메소드를

재정의해서 사용한다.

System.out.println(accounting1.hashCode() == accounting2.hashCode());

System.out.println(accounting1.equals(accounting2));

assertThat(accounting1, equalTo(accounting2));

// true 결과는 두 객체의 메모리 주소가 같다는 것을 의미한다.

System.out.println(accounting1 == accounting2);

assertThat(accounting1, is(accounting2));

assertThat(accounting1, sameInstance(accounting2));

}

EntityManager는 PersistenceContext로 데이터베이스로부터 질의하여 얻은 결과를 담고 있는 엔티티 객체를 보관합니다. 다음에 같은 결과를 요구하는 요청을 받으면 갖고 있는 엔티티 객체를 돌려준다는 것을 확인 할 수 있습니다. 이는 같은 트랜잭션 범위 안에서만 유효합니다.

JPAQuery, JPAQueryFactory 클래스를 이용하면 엔티티매니저가 제공하는 메소드를 직접 이용할 때와 마찬가지로 동일한 기능을 이용할 수 있습니다.

3. 필터 설정방법

DeptDao.java

public List<String> getDeptDnameWhenEmpCountExist(){

QDept dept = QDept.dept;

JPAQuery<?> query = new JPAQuery<Void>(entityManager);

List<String> dnames = query.select(dept.dname).from(dept)

.where(dept.emps.size().gt(0)).fetch();

return dnames;

}

DeptDaoTest.java

@Test

public void testGetDeptDnameWhenEmpCountExist(){

List<String> dnames = deptDao.getDeptDnameWhenEmpCountExist();

for (String dname : dnames) {

System.out.println(dname);

}

assertThat(dnames.size(), is(3));

}

SQL 로그

select

d.dname as col_0_0_

from dept d

where (

select

count(e.deptno)

from emp e

where d.deptno=e.deptno

)>0

where 조건절에 서브쿼리를 사용하여 부서에 배정된 직원이 있는 부서명만 조회합니다. 이 요청을 dept.emps.size().gt(0) 코드처럼 객체를 이용하는 방식 그대로 처리할 수 있습니다.

위 SQL을 exists 구문을 사용하는 것으로 바꾸면 다음과 같습니다.

select dname

from dept

where exists (

select 1 from emp where deptno=dept.deptno)

위 SQL을 사용하도록 요청하는 메소드 기반 쿼리 작성예시는 다음과 같습니다.

public List<String> getDeptDnameWhenEmpCountExist(){

QDept dept = QDept.dept;

QEmp emp = QEmp.emp;

JPAQuery<?> query = new JPAQuery<Void>(entityManager);

List<String> dnames = query.select(dept.dname).from(dept)

.where(JPAExpressions

.select(Expressions.constant(1))

.from(emp).where(emp.dept.deptno.eq(dept.deptno)).exists()).fetch();

return dnames;

}

댓글 없음:

댓글 쓰기

(C#교육동영상)C# ADO.NET 실습 ODP.NET/ODAC 설치 오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원

  (C#교육동영상)C# ADO.NET 실습  ODP.NET/ODAC 설치  오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원 https://www.youtube.com/watch?v=qIPU85yAlzc&list=PLxU-i...