Spring/JPA

스프링 데이터 JPA - (2)

ummchicken 2022. 12. 20. 09:37

출처 : 실전! 스프링 데이터 JPA

 

내가 보려고 씀

 

섹션 4. 쿼리 메소드 기능

8. 순수 JPA 페이징과 정렬

JPA에서 페이징을 어떻게 할 것인가?

 

 

9. 스프링 데이터 JPA 페이징과 정렬

페이지를 유지하면서 엔티티를 DTO로 변환하기

→ 엔티티를 외부에 그대로 반환하면 안 됨.

 

 

10. 벌크성 수정 쿼리

JPA는 엔티티 객체 중심

 

 

11. @EntityGraph

※ 선행 : 페치조인이란?

→ 지연로딩과 그로인해 발생하는 문제들을 이해해야 함.

N + 1 문제 : 페치조인으로 해결

// 영속성 컨텍스트에 있는 캐시 정보들을 DB에 완전히 다 반영을 해서 
// insert를 정확하게 다 하고, 
// 데이터베이스에 다 반영을 시킨 다음에
// 영속성 컨텍스트를 다 날리는 것
em.flush();
em.clear();

 

* [Member.java]

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;

 지연로딩을 하면 Member 쿼리를 가져올 때 Team은 가짜객체(프록시)를 가져온다.

 

* [Member.java]

// fetch : Member를 조회할 때 연관된 팀을 한방 쿼리로 다 끌고온다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();

 

* fetch join을 하지 않을 시 team class

member.teamClass = class study.datajpa.entity.Team$HibernateProxy$x50Fg8Vr

* fetch join을 하지 않을 시 쿼리 : 총 3번 나감

select
    member0_.member_id as member_i1_0_,
    member0_.age as age2_0_,
    member0_.team_id as team_id4_0_,
    member0_.username as username3_0_ 
from
    member member0_
select
    team0_.team_id as team_id1_1_0_,
    team0_.name as name2_1_0_ 
from
    team team0_ 
where
    team0_.team_id=?
select
    team0_.team_id as team_id1_1_0_,
    team0_.name as name2_1_0_ 
from
    team team0_ 
where
    team0_.team_id=?

 

* fetch join 시 team class

member.teamClass = class study.datajpa.entity.Team

* fetch join 시 쿼리 : 총 1번 나감

select
    member0_.member_id as member_i1_0_0_,
    team1_.team_id as team_id1_1_1_,
    member0_.age as age2_0_0_,
    member0_.team_id as team_id4_0_0_,
    member0_.username as username3_0_0_,
    team1_.name as name2_1_1_ 
from
    member member0_ 
left outer join
    team team1_ 
        on member0_.team_id=team1_.team_id

 

EntityGraph 정리

  • 사실상 페치 조인(FETCH JOIN)의 간편 버전
  • LEFT OUTER JOIN 사용

 

 

12. JPA Hint & Lock

JPA Hint

JPA 쿼리 힌트 (SQL 힌트가 아니라 JPA 구현체(하이버네이트)에게 제공하는 힌트)

 

 


 

섹션 5. 확장 기능

1. 사용자 정의 리포지토리 구현

Querydsl 사용할 때 많이 씀

 

사용자 정의 구현 클래스

  • 규칙: 리포지토리 인터페이스 이름 + Impl
  • 스프링 데이터 JPA가 인식해서 스프링 빈으로 등록

 

 

2. Auditing

엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶으면?

예를 들어, 

  • 등록일
  • 수정일
  • 등록자
  • 수정자

 

3. Web 확장 - 도메인 클래스 컨버터

HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩

 

권장하진 않음.

 

 

4. Web 확장 - 페이징과 정렬

스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있다.

 

🚨 엔티티를 외부에 노출하는 것은 내부 설계를 밖에 노출하는 것과 같다.

규약이 의미가 없음.

→ API를 반환할 때는 DTO로 반환하는 게 좋다.

(반환할 것만...)

 

💡 엔티티는 DTO를 가급적 보지 않는 게 좋다.
같은 패키지 안에 있는 경우 제외.
DTO라는 것은 공통으로 다 보는 것.
따라서 DTO는 엔티티를 봐도 된다.

 

 


 

섹션 6. 스프링 데이터 JPA 분석

1. 스프링 데이터 JPA 구현체 분석

  • @Repository 적용: JPA 예외를 스프링이 추상화한 예외로 변환
  • @Transactional 트랜잭션 적용
    • JPA의 모든 변경은 트랜잭션 안에서 동작
    • 스프링 데이터 JPA는 변경(등록, 수정, 삭제) 메서드를 트랜잭션 처리 (아니면 예외 터짐)
    • 서비스 계층에서 트랜잭션을 시작하지 않으면 리파지토리에서 트랜잭션 시작
    • 서비스 계층에서 트랜잭션을 시작하면 리파지토리는 해당 트랜잭션을 전파 받아서 사용
    • 그래서 스프링 데이터 JPA를 사용할 때 트랜잭션이 없어도 데이터 등록, 변경이 가능했음 (사실은 트랜잭션이 리포지토리 계층에 걸려있는 것임)

 

  • @Transactional(readOnly = true)
    • 데이터를 단순히 조회만 하고 변경하지 않는 트랜잭션에서 readOnly = true 옵션을 사용하면, 
      플러시를 생략해서 약간의 성능 향상을 얻을 수 있음
트랜잭션이 끝날 때 플러시를 하고, 커밋을 한다.
readOnly = true가 있으면 플러시를 안 함.
💡 플러시를 안 함 : DB에 변경감지가 안 일어나고, DB에 데이터를 안 보내겠다는 것
(∵ 읽기전용이기 때문)

 

매우 중요!!!

save() 메서드

  • 새로운 엔티티면 저장( persist ) (en.persist())
  • 새로운 엔티티가 아니면 병합( merge ) (em.merge()) : 기존에 한번 DB에 들어갔다가 나온 애야 라는 뜻
    • DB에 가져온 데이터를 바꿔치기 한다.
    • merge를 호출하는 순간, DB에서 꺼내고 파라미터를 넘긴 애로 다 교체를 한다. 트랜잭션이 끝날 때 데이터가 바꼈기 때문에 DB에 반영이 되는 메커니즘으로 동작함
    • 웬만하면 쓰면 안 된다. 데이터를 업데이트 하고 싶으면, 변경감지를 써야 함.

 

2. 새로운 엔티티를 구별하는 방법

새로운 엔티티를 판단하는 기본 전략

  1. 식별자가 객체일 때 null 로 판단
  2. 식별자가 자바 기본 타입일 때 0 으로 판단

※ 참고

@NoArgsConstructor(access = AccessLevel.PROTECTED)

// protected 기본 생성자 대신
protected Member() {}

 

 


섹션 7. 나머지 기능들

실무에서 잘 사용하진 않음.

편하게 들어도 된다.실무에서 쓰기 애매한 기능들.

 

JPA Criteria : 실무에서 안 씀

→ 실무에서는 JPA Criteria를 거의 안쓴다! 대신에 QueryDSL을 사용하자.