📌 목차
- Proxy
- proxy의 개념
- FetchType.LAZY , FetchType.EAGER
- 패치조인
- N+1 문제
- 참조) @ManyToMany
- @Embedded 값 타입
- 이후 학습해야 할 사항
- JPQL -> ( QueryDSL : 스터디에서는 학습 x )
- Spring Data JPA
📌 Proxy
- em.find() - em.getReference()
- em.find()
- DB에서 실제 Entity 객체를 조회
- em.getReference()
- DB조회를 아직 하지 않은, 가짜 객체인 Proxy객체를 조회
- em.find()
▸ proxy 예제
import entity.Member;
import entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("teamA");
Member memberA = new Member();
memberA.setUsername("memberA");
memberA.setTeam(teamA);
em.persist(memberA);
em.persist(teamA);
em.flush();
em.clear();
Member reference = em.getReference(Member.class, memberA.getId());
System.out.println(reference.getUsername());
System.out.println(reference.getClass());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
proxy 객체가 조회되는 것을 확인할 수 있다.
또한 현재 member를 찾아왔음에도 select query가 나가지 않는 것을 볼 수 있다.
이는 member가 proxy객체로 찾아와 졌기 때문에, 실제로 조회되기 전까지는 select query가 나가지 않는 것이다.
해당 System.out.println() 코드의 주석을 풀면 그때서야 select query가 나가는 것을 확인할 수 있다.
📌 프록시의 특징
DB 트랜잭션 격리수준에서 repeatable read를 보장한다.
트랜잭션 격리수준
https://imsfromseoul.tistory.com/156?category=867222
▸ repeatableRead 보장 예제
import entity.Member;
import entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("teamA");
Member memberA = new Member();
memberA.setUsername("memberA");
memberA.setTeam(teamA);
em.persist(memberA);
em.persist(teamA);
em.flush();
em.clear();
Member reference = em.getReference(Member.class, memberA.getId());
Member member = em.find(Member.class, memberA.getId());
System.out.println(reference.getClass());
System.out.println(member.getClass());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Member member = em.find(Member.class, memberA.getId());
Member reference = em.getReference(Member.class, memberA.getId());
순서만 바꿔서 다시 출력해보자.
이는 모두 한 trasaction 안에서 repeatable read를 보장해주기 위함이다.
📌 Proxy 조회 - LAZY
▸ Member.class
package entity;
import lombok.*;
import javax.persistence.*;
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="team_id")
private Team team;
}
- fetch = FetchType.LAZY 를 설정해준다.
▸ Team 조회
import entity.Member;
import entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("teamA");
Member memberA = new Member();
memberA.setUsername("memberA");
memberA.setTeam(teamA);
em.persist(memberA);
em.persist(teamA);
em.flush();
em.clear();
Member member = em.find(Member.class, memberA.getId());
System.out.println(member.getTeam().getClass());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Team의 경우 proxy 객체가 조회된다.
proxy 예제와 마찬가지로, 실제 team 객체 조회시 쿼리가 나간다.
📌 EAGER의 경우
EAGER로 매핑돼 있는 해당 Entity의 연관관계 Entity를 전부 가져온다.
( @ManyToOne의 경우 default 가 EAGER이다. )
위의 LAZY예제에서 Team과의 연관관계를 EAGER로 바꾸면 Team객체가 proxy객체가 아닌 실제 객체로 조회되는 것을 볼 수 있다.
▸ EAGER 실습 예제
- 참고) Order
Entity이름을 Order로 생성하면 생성이 안된다. Orders로 생성해야 한다. 키워드가 겹치기 때문이다.
- em.find() 를 해서 찾으면 select query가 join이 돼서 찾아와진다. 문제는 JPQL을 사용할 때의 문제.
- JPA에서 제공하는 method만으로 db에 있는 데이터를 다 조회하는 것은 불가능하다. 결국은 query를 생성해서 db의 값을 조회해야 한다.
import entity.Member;
import entity.OrderStatus;
import entity.Orders;
import entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Team teamA = new Team();
teamA.setName("teamA");
Team teamB = new Team();
teamB.setName("teamB");
Team teamC = new Team();
teamC.setName("teamC");
Member memberA = new Member();
memberA.setUsername("memberA");
memberA.setOrderStatus(OrderStatus.cancel);
memberA.setTeam(teamA);
Member memberB = new Member();
memberB.setUsername("memberB");
memberB.setOrderStatus(OrderStatus.cancel);
memberB.setTeam(teamB);
Member memberC = new Member();
memberC.setUsername("memberC");
memberC.setOrderStatus(OrderStatus.cancel);
memberC.setTeam(teamB);
Member memberD = new Member();
memberD.setUsername("memberD");
memberD.setOrderStatus(OrderStatus.cancel);
memberD.setTeam(teamC);
Orders orderA = new Orders();
orderA.setName("orderA");
orderA.setMember(memberA);
Orders orderB = new Orders();
orderA.setName("orderB");
orderB.setMember(memberB);
em.persist(teamA);
em.persist(teamB);
em.persist(teamC);
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
em.persist(memberD);
em.persist(orderA);
em.persist(orderB);
em.flush();
em.clear();
System.out.println("===========LINE===========");
List<Orders> ordersList = em.createQuery("select o from Orders o", Orders.class)
.getResultList();
// ordersList.forEach(System.out::println);
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
Order를 조회하는 첫번째의 쿼리 1, 나머지 data의 개수만큼 N번의 쿼리가 추가로 나갔다. 이런 문제를 N+1문제라고 한다.
▸ 궁금증?
- 만약에 Orders > member 만 LAZY로 잡는다면?
select query가 하나만 나간다.
▸ EAGER는 어떤 상황에서 사용할까?
▸ 그렇다면 LAZY에서는 N+1문제가 없을까?
- Orders안에 있는 Member와의 관계를 LAZY로 설정해놓고, OrderA의 Member를 가져와서 getName()을 출력해보자.
- 해당 Member의 수만큼 쿼리가 추가로 나간다.
- 이 또한 N+1문제를 야기한다.
▸ N+1문제 해결방법
- fetch join(패치 조인)
- 처음에 가져올 때 전부 다 조회해서 가져오는 방법
List<Orders> ordersList = em.createQuery("select o from Orders o join fetch o.member", Orders.class)
.getResultList();
for(Orders orders : ordersList){
String username = orders.getMember().getUsername();
}
한 번에 조인해서 가져온다.
그런데 fetch join은 한 번만 사용이 가능하다.
만약 위의 예에서 아래와 같이 Team을 조회하려고 하면 다시 Team에 대한 쿼리가 나간다.
for(Orders orders : ordersList){
String username = orders.getMember().getTeam().getName();
}
최상위 조건에서 모든 조건을 한 번에 join해서 가져오는 방법은 없을까?
잘 몰라서, 검색을 해보니 여러가지를 더 알아야 하나 보다.
(참조 링크 : https://www.inflearn.com/questions/205272)
📌 참조 @ManyToMany
다대다 관계에서 @ManyToMany로 매핑을 할 수는 있다.
그러나 사용하면 안된다.
@ManyToMany는 DB에서 중간 테이블이 만들어지는데, Jpa에서 해당 중간 테이블에 column을 추가할 방법이 없다.
그런데 실무에서는 무조건 해당 테이블에 createdAt 이라든지, 여러 column을 추가할 일이 무조건 생긴다.
📌 값 타입
값 타입은 int, String 과 같은 말 그대로 '값' 타입이다.
JPA에서는 이를 임베디드 타입(embedded type)이라고 한다.
잘 설계한 ORM application은 매핑한 테이블의 수보다 클래스의 수가 더 많다.
이는 높은 응집도에 대한 설계를 바탕으로 한다.
- @Embeddable : 값 타입을 정의하는 곳에 표시
- @Embedded : 값 타입을 사용하는 곳에 표시
▸ Period.java
@Embeddable
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public Period(LocalDateTime startDate, LocalDateTime endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
}
▸ Member.java
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus;
@Embedded
private Period workPeriod;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="team_id")
private Team team;
}
▸ 참조 ) ddl auto option으로 table 생성시 위와 같이 erd diagram에서 화살표가 보이지 않는 문제
https://stackoverflow.com/questions/42506921/er-diagram-not-displaying-relationships-in-datagrip
table 생성시 query 방식에서 ddl auto option으로 날린 쿼리로 table을 생성하면 화살표가 생성되지 않는 것 같다.
▸ 에러 참조 )
unexpected token: Member
Member가 내가 생성한 Member가 아닌 다른 Member가 import 돼서 생긴 문제.
entity인 Member 구분을 위해 entity 폴더를 따로 생성해서 진행하자.
https://m.blog.naver.com/writer0713/221498059985
'SSAFY 6기 > 📌project' 카테고리의 다른 글
jpa study #4 - jpql & spring data jpa (0) | 2022.01.20 |
---|---|
jpa study #2 - jpa의 동작 원리 영속성 컨텍스트 (0) | 2022.01.11 |
jpa study #1 - jpa의 동작 원리 영속성 컨텍스트 (0) | 2022.01.09 |
Apato project (0) | 2021.11.18 |
댓글