2020년 7월 22일 수요일

JPA, Querydsl, 엔티티그래프(EntityGraph)

JPA, Querydsl, 엔티티그래프(EntityGraph)

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

3.3.7. EntityGraph

엔티티 그래프는 엔티티매니저에게 전달하는 쿼리힌트입니다. 엔티티 그래프로 데이터 페치타입을 코드적으로 변경할 수 있습니다.

1. @NamedEntityGraph

엔티티 그래프 설정을 엔티티 클래스에 어노테이션으로 설정하는 방법을 먼저 살펴보겠습니다.

Emp.java

package com.example.employee.model;

import java.util.ArrayList;

import java.util.Date;

import java.util.List;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.FetchType;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;

import javax.persistence.NamedAttributeNode;

import javax.persistence.NamedEntityGraph;

import javax.persistence.NamedEntityGraphs;

import javax.persistence.OneToMany;

import javax.persistence.Temporal;

import javax.persistence.TemporalType;

import com.fasterxml.jackson.annotation.JsonIgnore;

import lombok.Data;

import lombok.ToString;

@NamedEntityGraphs(

@NamedEntityGraph(

name = "graph.Emp.dept",

attributeNodes = @NamedAttributeNode(value = "dept")))

@Data

@ToString(exclude={"staff"})

@Entity

public class Emp {

@Id

private Long empno;

@Column(length = 10, nullable = false)

private String ename;

@Column(length = 9)

private String job;

@ManyToOne(fetch=FetchType.LAZY)

@JoinColumn(name = "mgr")

private Emp mgr;

@OneToMany(mappedBy = "mgr")

@JsonIgnore

private List<Emp> staff = new ArrayList<Emp>();

@Temporal(TemporalType.DATE)

private Date hiredate;

private Double sal;

private Double comm;

@ManyToOne(fetch=FetchType.LAZY)

@JoinColumn(name = "deptno")

private Dept dept;

public void setDept(Dept dept){

this.dept = dept;

if (!dept.getEmps().contains(this)) {

dept.getEmps().add(this);

}

}

}

@NamedEntityGraphs

다수의 @NamedEntityGraph를 선언하기 위해서 사용합니다.

@NamedEntityGraph(name = "graph.Emp.dept")

엔티티매니저 또는 Querydsl을 통해 쿼리를 선언할 때 사용하는 엔티티 그래프 이름을 설정합니다.

@NamedEntityGraph(attributeNodes = @NamedAttributeNode(value = "dept"))

쿼리힌트 대상 필드변수를 지정합니다.

@ManyToOne(fetch=FetchType.LAZY)

N:1 연관관계에서 1에 해당하는 기본 페치정책은 EAGER입니다. 엔티티 그래프로 제어하기 위해서 기본 페치정책을 LAZY로 변경합니다. 필드변수 mgr, dept가 설정대상입니다.

EmpDaoEntityGraph.java

package com.example.employee.repository;

import javax.persistence.EntityGraph;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

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

import org.springframework.stereotype.Repository;

import com.example.employee.model.Emp;

import com.example.employee.model.QEmp;

import com.querydsl.jpa.impl.JPAQuery;

import com.querydsl.jpa.impl.JPAQueryFactory;

@Repository

public class EmpDaoEntityGraph {

@PersistenceContext

private EntityManager em;

@Autowired

private JPAQueryFactory queryFactory;

QEmp emp = QEmp.emp;

public Emp getEmpNotUsingEntityGraph(Long empno) {

JPAQuery<Emp> query = queryFactory.selectFrom(emp).where(emp.empno.eq(empno));

return query.fetchOne();

}

public Emp getEmpUsingEntityGraph(Long empno) {

EntityGraph<?> graph = em.getEntityGraph("graph.Emp.dept");

JPAQuery<Emp> query = queryFactory.selectFrom(emp).where(emp.empno.eq(empno));

query = query.setHint("javax.persistence.fetchgraph", graph);

return query.fetchOne();

}

public Emp getEmpUsingDynamicEntityGraph(Long empno) {

EntityGraph<Emp> graph = em.createEntityGraph(Emp.class);

graph.addAttributeNodes("dept");

graph.addAttributeNodes("mgr");

JPAQuery<Emp> query = queryFactory.selectFrom(emp).where(emp.empno.eq(empno));

query = query.setHint("javax.persistence.fetchgraph", graph);

return query.fetchOne();

}

}

EntityGraph<?> graph = em.getEntityGraph("graph.Emp.dept")

엔티티 클래스에 설정한 @NamedEntityGraph(name = "graph.Emp.dept") 이름을 엔티티매니저에게 알려주고 엔티티 그래프 객체를 얻습니다. 이는 결국 대상 필드변수를 알려준 것입니다.

query = query.setHint("javax.persistence.fetchgraph", graph)

첫 번째 파라미터로 엔티티 그래프 페치전략을 가리키는 문자열을 설정합니다.

앞서 얻은 엔티티 그래프 객체를 두 번째 파라미터로 설정합니다.

Querydsl JPAQuery 객체에 엔티티 그래프 힌트를 설정하였습니다. 따라서 엔티티매니저는 엔티티 Emp의 데이터를 요청하는 쿼리 수행 시 dept 필드변수 정보를 얻기 위해 EAGER 로딩정책을 사용합니다.

엔티티 그래프 페치전략의 차이

페치전략

설명

javax.persistence

.fetchgraph

설정된 대상은 FetchType.EAGER 페치전략을 사용한다.

설정되지 않은 대상은 FetchType.LAZY 페치전략을 사용한다.

javax.persistence

.loadgraph

설정된 대상은 FetchType.EAGER 페치전략을 사용한다.

설정되지 않은 대상은 설정된 디폴트 페치전략을 사용한다.

2. Dynamic Enitity Graph

@NamedEntityGraph 어노테이션으로 엔티티 클래스에 엔티티 그래프를 설정해서 사용하지 않고 프로그램 코드에서 동적으로 엔티티 그래프를 생성해서 사용하는 방법입니다.

엔티티매니저를 통한 엔티티 그래프 사용법

EntityGraph<Emp> graph = em.createEntityGraph(Emp.class);

graph.addAttributeNodes("dept");

Map<String, Object> hints = new HashMap<String, Object>();

hints.put("javax.persistence.fetchgraph", graph);

em.find(Emp.class, empno, hints);

Querydsl을 통한 엔티티 그래프 사용법

EntityGraph<Emp> graph = em.createEntityGraph(Emp.class);

graph.addAttributeNodes("dept");

graph.addAttributeNodes("mgr");

JPAQuery<Emp> query = queryFactory.selectFrom(emp).where(emp.empno.eq(empno));

query = query.setHint("javax.persistence.fetchgraph", graph);

query.fetchOne();

3. 테스트

EmpDaoEntityGraphTest.java

package com.example.employee.repository;

import javax.persistence.FetchType;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;

import javax.persistence.NamedAttributeNode;

import javax.persistence.NamedEntityGraph;

import javax.persistence.NamedEntityGraphs;

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 org.springframework.transaction.annotation.Transactional;

import com.example.employee.model.Dept;

import com.example.employee.model.Emp;

@Transactional

@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

public class EmpDaoEntityGraphTest {

@Autowired

private EmpDaoEntityGraph dao;

private final Long TEST_EMPNO = 7788L;

@Test

public void testGetDeptNotUsingEntityGraph() {

Emp emp = dao.getEmpNotUsingEntityGraph(TEST_EMPNO);

// Emp 클래스의 toString 메소드를 호출하지 않고 테스트 한다.

}

@Test

public void testGetDeptUsingEntityGraph() {

Emp emp = dao.getEmpUsingEntityGraph(TEST_EMPNO);

}

@Test

public void testGetDeptUsingDynamicEntityGraph() {

Emp emp = dao.getEmpUsingDynamicEntityGraph(TEST_EMPNO);

}

}

#1 testGetDeptNotUsingEntityGraph테스트 메소드를 실행

사용 쿼리

select

emp0_.empno as empno1_1_, emp0_.comm as comm2_1_,

emp0_.deptno as deptno7_1_, emp0_.ename as ename3_1_,

emp0_.hiredate as hiredate4_1_, emp0_.job as job5_1_,

emp0_.mgr as mgr8_1_, emp0_.sal as sal6_1_

from emp emp0_

where emp0_.empno=7788

emp 테이블 조회 시 연관관계 로딩정책은 모두 LAZY이므로 추가작업은 수행되지 않습니다.

#2 testGetDeptUsingEntityGraph 테스트 메소드를 실행

사용 쿼리

select

emp0_.empno as empno1_1_0_, dept1_.deptno as deptno1_0_1_,

emp0_.comm as comm2_1_0_, emp0_.deptno as deptno7_1_0_,

emp0_.ename as ename3_1_0_, emp0_.hiredate as hiredate4_1_0_,

emp0_.job as job5_1_0_, emp0_.mgr as mgr8_1_0_,

emp0_.sal as sal6_1_0_, dept1_.dname as dname2_0_1_,

dept1_.loc as loc3_0_1_

from emp emp0_ left outer join dept dept1_

on emp0_.deptno=dept1_.deptno

where emp0_.empno=7788

Emp 엔티티 클래스의 dpet 필드변수에 EAGER 로딩정책이 적용되었습니다. 따라서 dept 필드변수의 객체 그래프 탐색을 위하여 left outer join 쿼리가 사용되었습니다.

#3 testGetDeptUsingDynamicEntityGraph 테스트 메소드를 실행

사용쿼리

select

emp0_.empno as empno1_1_0_, emp1_.empno as empno1_1_1_,

dept2_.deptno as deptno1_0_2_, emp0_.comm as comm2_1_0_,

emp0_.deptno as deptno7_1_0_, emp0_.ename as ename3_1_0_,

emp0_.hiredate as hiredate4_1_0_, emp0_.job as job5_1_0_,

emp0_.mgr as mgr8_1_0_, emp0_.sal as sal6_1_0_,

emp1_.comm as comm2_1_1_, emp1_.deptno as deptno7_1_1_,

emp1_.ename as ename3_1_1_, emp1_.hiredate as hiredate4_1_1_,

emp1_.job as job5_1_1_, emp1_.mgr as mgr8_1_1_,

emp1_.sal as sal6_1_1_, dept2_.dname as dname2_0_2_,

dept2_.loc as loc3_0_2_

from emp emp0_ left outer join emp emp1_

on emp0_.mgr=emp1_.empno left outer join dept dept2_

on emp0_.deptno=dept2_.deptno

where emp0_.empno=7788

프로그램적으로 dept, mgr에 EAGER 페치 전략을 설정했기 때문에 관련된 데이터를 구하기 위한 join 쿼리가 수행되었습니다.

사용하지 않는 데이터를 매번 연속적으로 쿼리가 수행되게 나두는 것은 그리 현명한 판단이 아니므로 엔티티 클래스의 설정은 LAZY로 설정하고 엔티티 그래프를 사용하여 필요 시 EAGER 페치전략을 사용하는 방법을 살펴 보았습니다.

댓글 없음:

댓글 쓰기

(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...