본문 바로가기

백엔드/JPA

스프링 데이터 jpa - 구현체 코드 구경, 쿼리 dsl

구현체 살펴보기

스프링 데이터 jpa 레포지토리의 구현체이다.

특징 1. @Repository

  1. 스프링 빈의 컴포넌트 스캔대상
  2. Exception을 서비스, 컨트롤러로 넘길때 스프링의 Exception으로 변경되어 올라감.

특징 2. @Transactional

그래서 레포지토리나 서비스에서 @Transactional이 안달려있어도 잘 동작했던 것.

 

save 동작과정

디비에 결과를 반영해야하는 save같은 경우에는 readonly 옵션이 false이다.
save 메소드 내부에서는 엔티티의 pk가 null이면 if문 내부로 들어갈 것이다. 하지만 만약에 다음과 같은 상황이라면?

public class Member extends JpaBaseEntity {
    @Id
    private Long id;

//		기본생성자
//    id 받는 생성자
}
Member m = new Member("id");
memberRepository.save(m);

id를 직접 만들어서 넣어주는 거라면 pk값이 null이 아니기 때문에 merge가 실행된다.

merge는 기본적으로 디비에 있을거라 가정하고 디비에 select를 날린다. 결과가 없으면 그 다음에야 아 없구나 하고 insert를 날린다. 그래서 비효율적이다.  (merge는 거의 쓰면 안됨 거의 쓸일이 없음. 데이터 변경은 변경감지! 저장은 persist!)

 

나머지 기능들

1. Specifications

정리할 가치도 없어보인다.

결론 쿼리 DSL

2. Query By Example

JpaRepository 인터페이스를 다시보면

옆에 하나가 더 있다. 저기에 정의되어있는 기능을 이용하는 것이다.

@Test
public void QeruyByExam() {
    Team team = new Team("taem");
    em.persist(team);
														// 이름, 나이, 팀
    Member m1 = new Member("m1", 0, team);
    Member m2 = new Member("m2", 10, team);
    em.persist(m1);
    em.persist(m2);

    em.flush();
    em.clear();

    //엔티티 자체가 검색 컨디션
    Member searchMember = new Member("m1");
    Example<Member> of = Example.of(searchMember );
    List<Member> all = memberRepository.findAll(of);

    assertThat(all.get(0).getUsername()).isEqualTo("m1");
}

searchMember 를 가지고 검색조건을 만들어버린다. 하지만 위에서 m1의 나이를 0이 아닌 값으로 바꿔버리면 에러가 난다. 이유는 기본 타입의 값은 0으로 설정하기 때문에 따로 searchMember에서 나이를 설정하지 않으면 0으로 검색조건을 만들어 검색하기 때문이다. 그래서 나이 조건은 무시하도록 해줘야한다.

memberRepository.findByUsername("m1");
Member member = new Member("m1");
//==== 이부분 추가, 수정
ExampleMatcher matcher = ExampleMatcher.matching().withIgnorePaths("age");
Example<Member> of = Example.of(member, matcher);
//======
List<Member> all = memberRepository.findAll(of);
assertThat(all.get(0).getUsername()).isEqualTo("m1");

이렇게 하면 age값이 무시되어서 m1의 나이가 0이 아니어도 잘 가져온다.

검색 엔티티를 Probe라고 한다 이 Probe와 ExampleMatcher를 가지고 Example을 만들어 쿼리를 날린다.

한계점

outer 조인이 불가능하다. 매칭 조건이 매우 단순하다.

결론 QueryDSL

 

3. Projections

엔티티 조회 말고 특정 컬럼만 조회하고 싶은 경우에 이 방법을 씀 - 가끔 도움이 될 때가 있다.

//인터페이스 하나 생성
public interface UsernameOnly {
    String getUsername();
}

//스프링 데이터 jpa 레포지토리에 메소드 추가
List<UsernameOnly> findProjectByUsername(@Param("username") String username);
//테스트 코드
@Test
public void projections() {
    Team team = new Team("taem");
    em.persist(team);
    Member m1 = new Member("m1", 10, team);
    Member m2 = new Member("m2", 10, team);
    em.persist(m1);
    em.persist(m2);
    em.flush();
    em.clear();

    List<UsernameOnly> m11 = memberRepository.findProjectByUsername("m1");
}

프로젝션의 대상이 root 엔티티면 좋다. 하지만 root 엔티티를 넘어가면 최적화가 안된다.단순할때만 사용 가능하고 복잡해지면 QueryDSL을 써야한다.

결론 - QueryDSL

 

4. 네이티브 쿼리

jpa를 쓰면 가급적이면 사용하지 않는게 좋다.

@Query(value = "select * from member where username=?", nativeQuery = true)
Member findByNativeQuery(String username);

위와 같이 @Query에 nativeQuery = true 옵션만 주면 된다.

🎃 한계 : 데이터를 엔티티에 맞게 select절 다 해야하고 동적 쿼리가 불가능하다. JPQL 처럼 애플리케이션 로딩 시점에 문법 확인이 불가하다.

 

DTO 뽑을때 네이티브 써야한다 하면 이 방법으로 하기!!

 

 

 

마무리

쿼리 DSL을 꼭 공부하고 싶어졌다..

 

관련글

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-%EC%8B%A4%EC%A0%84/dashboard

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다

www.inflearn.com