Java / / 2023. 1. 18. 09:50

순수 JPA를 Spring data JPA로 변경하는 법

반응형

해당 글은 김영한 님의 querydsl을 수강하며 정리하려고 적는 포스팅입니다.

 

스프링 데이터 JPA 리포지토리로 변경

 

스프링 데이터 JPA - MemberRepository 생성

public interface MemberRepository extends JpaRepository<Member, Long> {
    //select m from Member m where m.username =:?
    List<Member> findByUsername(String username);
}
  • 기본 CRDU(정적쿼리) → Spring Data JPA가 interface만 만들면 이름으로 매칭해서 자동으로 구현체를 매칭

 

스프링 데이터 JPA 테스트

@SpringBootTest
@Transactional
class MemberRepositoryTest {

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemberRepository memberRepository;

    @Test
    public void basicTest() {
        Member member = new Member("member1", 10);
        memberRepository.save(member);

        Member findMember = memberRepository.findById(member.getId()).get();
        assertThat(findMember).isEqualTo(member);

        List<Member> result1 = memberRepository.findAll();
        assertThat(result1).containsExactly(member);

        List<Member> result2 = memberRepository.findByUsername("member1");
        assertThat(result2).containsExactly(member);
    }
}
------------------------------------------------------------------------------
2023-01-18T09:02:22.551+09:00 DEBUG 16408 --- [           main] org.hibernate.SQL                        : 
    /* <criteria> */ select
        m1_0.member_id,
        m1_0.age,
        m1_0.team_id,
        m1_0.username 
    from
        member m1_0
        
2023-01-18T09:02:22.597+09:00 DEBUG 16408 --- [           main] org.hibernate.SQL                        : 
    /* <criteria> */ select
        m1_0.member_id,
        m1_0.age,
        m1_0.team_id,
        m1_0.username 
    from
        member m1_0 
    where
        m1_0.username=?

 


 

사용자 정의 Repository

CustomRepository

 

사용자 정의 인터페이스 작성

public interface MemberRepositoryCustom {
    List<MemberTeamDto> search(MemberSearchCondition condition);
}

 

사용자 정의 인터페이스 구현

public class MemberRepositoryImpl implements MemberRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    public MemberRepositoryImpl(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    @Override
    public List<MemberTeamDto> search(MemberSearchCondition condition) {
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")
                ))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe()))
                .fetch();
    }

    private BooleanExpression usernameEq(String username) {
        return hasText(username) ? member.username.eq(username) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return hasText(teamName) ? team.name.eq(teamName) : null;
    }

    private BooleanExpression ageGoe(Integer ageGoe) {
        return ageGoe != null ? member.age.goe(ageGoe) : null;
    }

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe != null ? member.age.loe(ageLoe) : null;
    }
}
MemberRepository + Impl : 명명규칙

 

스프링 데이터 리포지토리에 사용자 인터페이스 상속

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom{
    //select m from Member m where m.username =:?
    List<Member> findByUsername(String username);
}
  • interface는 여러 개 상속받을 수 있다.

 

커스텀 리포지토리 동작 테스트

@SpringBootTest
@Transactional
class MemberRepositoryTest {

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemberRepository memberRepository;

    @Test
    public void searchTest() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        MemberSearchCondition condition = new MemberSearchCondition();
        condition.setAgeGoe(35);
        condition.setAgeLoe(40);
        condition.setTeamName("teamB");

        List<MemberTeamDto> result = memberRepository.search(condition);

        assertThat(result).extracting("username").containsExactly("member4");
    }
}
-------------------------------------------------------------------------------------
2023-01-18T09:29:20.934+09:00 DEBUG 9048 --- [           main] org.hibernate.SQL                        : 
    /* select
        member1.id as memberId,
        member1.username,
        member1.age,
        team.id as teamId,
        team.name as teamName 
    from
        Member member1   
    left join
        member1.team as team 
    where
        team.name = ?1 
        and member1.age >= ?2 
        and member1.age <= ?3 */ select
            m1_0.member_id,
            m1_0.username,
            m1_0.age,
            m1_0.team_id,
            t1_0.name 
        from
            member m1_0 
        left join
            team t1_0 
                on t1_0.id=m1_0.team_id 
        where
            t1_0.name=? 
            and m1_0.age>=? 
            and m1_0.age<=?

 

참고

만약 Search가 쿼리가 복잡하고 특정 화면이나, API에 특화된 것이라고 가정하자.

그렇다면 따로 MemberQueryRepository와 같은 class로 생성하고 injection 받아서 사용하면 된다.

 

공용성이 없고, 특정 API에 종속되어 있으면 수정 라이프 사이클 자체가 API와 화면에 맞추어져서 기능이 변경된다. 그래서 찾기도 편하고 수정도 편하게 된다. 따라서 별도로 조회용 repository를 만드는 것도 괜찮다. 너무 Custom에 억압 받지 않아도 된다.

 

기본은 Custom을 사용하고, 아키텍처에 맞게 분리해서 사용하는 것도 괜찮은 방법이라고 한다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유