본문 바로가기

백엔드/JPA

스프링 데이터 JPA 기초

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

위 강의를 들으며 정리한 글입니다.

 

스프링 데이터 jpa

기존의 jpa 레포지토리와는 다르게 스프링 데이터 레포지토리를 만드는 방법은 그냥 인터페이스를 하나 만들기만 하면 된다.

public interface TeamRepository extends JpaRepository<Team, Long> {
}

JpaRepositoty<타입, pk타입> 을 상속받아주면 된다. 이렇게 해놓으면 스프링에서 구현체를 만들어서 프록시를 통해 주입해준다. 이때는 @Repository 어노테이션이 없어도 동작한다.

 

메소드 명으로 쿼리 만들기

스프링데이터는 메소드 이름을 가지고 쿼리를 만들어주는 쿼리 메소드라는 엄청난 기능을 제공한다.
예를들면 특정 멤버의 name으로 찾되 몇살 이상인 조건을 주는 메소드는 아래와 같이 만들수 있다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
@Test
public void findByUsernameGreate2() {
    Member member1 = new Member("M11",10);
    Member member2 = new Member("M11",20);
    memberRepository.save(member1);
    memberRepository.save(member2);

    List<Member> m11 = memberRepository.findByUsernameAndAgeGreaterThan("M11", 15);
    assertThat(m11.get(0).getUsername()).isEqualTo("M11");
    assertThat(m11.get(0).getAge()).isEqualTo(20);
}

정말 신기하게도 테스트 코드가 통과한다 ㄷㄷ...... 그럼 변수명은 다르게 해도 괜찮을까

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsernameAndAgeGreaterThan(String aa, int bb);
}

문제 없이 돌아간다.. 중요한 것은 메소드명이기 때문이다.
findByUsernameAndAgeGreaterThan 이라고 하면
Username은 == 조건이고 GreaterThen은 > 조건으로 쿼리를 날리게 된다.

참고

https://docs.spring.io/spring-data/jpa/docs/2.5.4/reference/html/#jpa.query-methods.query-creation

 

Spring Data JPA - Reference Documentation

Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

https://docs.spring.io/spring-data/jpa/docs/2.5.4/reference/html/#reference

 

Spring Data JPA - Reference Documentation

Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

공식 문서를 보면 다양한 메소드들이 제공된다. 저중에 재밌어보이는 것 하나만 해보자면 StartingWith는 넘겨진 인자로 시작되는 이름을 찾아주는 기능이다.

List<Member> findByUsernameStartingWith(String n);

스프링 데이터 repository에 메소드를 하나 생성해주고

@Test
public void findByUsernameGreate2() {
    Member member3 = new Member("AAA",12);
    memberRepository.save(member3);

    List<Member> a = memberRepository.findByUsernameStartingWith("A");
    assertThat(a.size()).isEqualTo(1);
}

테스트를 해보면 역시나 잘 돌아간다. 해당 메소드는 A로 시작하는 멤버들을 찾아서 결과값으로 반환해줄 것이다.

 

네임드 쿼리

- jpa에서 네임드쿼리를 복습

사전에 쿼리를 이렇게 정의해두고 em.createNamedQuery를 통해 사용한다.

문제는 createQuery하고 setParameter ~ getResultList~ 하는게 좀 번거롭다. 그래서 스프링 데이터 jpa에서 제공하는 네임드쿼리는 조금 더 쉽다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsernameAndAgeGreaterThan(String n, int bbb);

    List<Member> findAaaaByUsername(String name);

    List<Member> findByUsernameStartingWith(String n);

    @Query(name="Member.findByUsername")
    List<Member> findByUsername(@Param("username") String username);
}

그냥 이렇게 하면 끝이다 ㅋㅋㅋㅋ 엄청 편하다. 위의 @Query(~~~) 는 빼도 상관이 없다. 메소드 이름으로 쿼리를 만들기 이전에 네임드 쿼리가 있는지 찾기 때문이다.

스프링 데이터 JPA의 메소드 만드는 순서

  1. 네임드 쿼리를 찾는다. 타입.메소드명 (위의 예시로는 Member.findByUsername)
  2. 메소드명을 가지고 쿼리를 만든다.

네임드쿼리의 큰 장점 한가지 쿼리에 오타가 있는지 로딩 시점에 찾아내준다.

 

리포지토리에 쿼리 정의

사실 네임드 쿼리는 잘 쓰이지 않는다. 이 방법이 너무 유용하기 때문에..

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsernameAndAgeGreaterThan(String n, int bbb);

    List<Member> findAaaaByUsername(String name);

    List<Member> findByUsernameStartingWith(String n);

    @Query("select m from Member m where m.username=:username and m.age = :age")
    List<Member> findUser(@Param("username") String username, @Param("age") int age);
}

이 기능도 마찬가지로 로딩 시점에 쿼리에 문법오류가 있는지 검사가 가능하다.

@Test
public void repoQuery() {
			Member member1 = new Member("M11",10);
			Member member2 = new Member("M11",20);
			Member member3 = new Member("AAA",12);
			memberRepository.save(member1);
			memberRepository.save(member2);
			memberRepository.save(member3);
			
			List<Member> m11 = memberRepository.findUser("M11", 10);
			assertThat(m11.size()).isEqualTo(1);
}

써보니 확실히 네임드쿼리보다 더 사용하기 편하다.

 

값과 DTO로 조회하기

@Query("select m.username from Member m")
List<String> findUsernameList();

단순히 값 하나만 조회하기위해선 위의 메소드를 넣어주면 된다. 하지만 실제로는 username뿐아니라 나이 등의 여러 값을 다같이 조회해야할 것이다. 이럴때 DTO를 사용하게 된다.

@Query("select new dataJPA.datajpa.dto(m.id m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();

//테스트 코드
@Test
public void repoQuery() {
    Team team = new Team("nasfd");
    teamRepository.save(team);

    Member member1 = new Member("M11",10);
    member1.setTeam(team);
    memberRepository.save(member1);

    List<MemberDto> memberDto = memberRepository.findMemberDto();
    for (MemberDto dto : memberDto) {
        System.out.println("dto = " + dto);
    }
}

dto 로 받아오는 쿼리를 쓰는 메소드를 하나 생성하고 테스트 해본다. 결과로는 dto들이 잘 출력되는 것을 확인할 수 있다.

마무리

공부하다보니 또 쿼리 dsl이 궁금해지기 시작한다...