JPA repo에 querydsl 추가

JIWOO YUN·2024년 3월 25일
0

Querydsl

목록 보기
5/7
post-custom-banner

순수 JPA 리포지토리

  • JPA 리포지토리를 추가해서 실무에서 사용하듯이 적용해보자.

MemberJpaRepository

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;
import study.querydsl.entity.Member;

import java.util.Optional;
import java.util.List;

@Repository
public class MemberJpaRepository {

    private final EntityManager em;
    private final JPAQueryFactory queryFactory;

    public MemberJpaRepository(EntityManager em) {
        this.em = em;
        this.queryFactory = new JPAQueryFactory(em);
    }

    public void save(Member member){
        em.persist(member);
    }

    public Optional<Member> findById(Long id){
        Member findMember = em.find(Member.class, id);
        return Optional.ofNullable(findMember);
    }

    public List<Member> findAll(){
        return em.createQuery("select m from Member m",Member.class)
                .getResultList();
    }

    public List<Member> findByUsername(String username){
        return em.createQuery("select m from Member m where m.username = :username",Member.class)
                .setParameter("username",username)
                .getResultList();
    }
}

순수 repo에 Querydsl을 추가진행

  • static 으로 Qmember를 import 해주거나 이전에 Qmember를 사용하는 방법을 repo에 추가해야한다.
    • querydsl이 qEntity로 사용되기 때문에.
  • 확실히 자바 코드기 때문에 작성하는데 오타가 나거나 하면 바로 캐치가 되서 오타를 줄일 수 있다.
  • 파라미터 바인딩이 기본 이기 때문에 setParameter를 넣어줄 필요가 없다.

JPAQueryFactory의 경우 스프링 빈 등으로 해도된다.

  • 스프링이 주입해주는 엔티티 매니저의 경우 실제 동작 지점에서 매니저를 찾아주는 프록시용 가짜 엔티티 매니저기 때문이다. -> 가짜 엔티티 매니저는 실제 사용시점에 트랙잭션 단위로 실제 엔티티 매니저를 할당.
    • 동시성 문제 해결
      • 표준 JPA 책의 트랜잭션 범위의 영속성 컨텍스트를 보니 같은 트랜잭션 내에서는 같은 영속성 컨텍스트를 사용하고 다른 트랜잭션 범위일 경우 다른 영속성 컨텍스트를 사용하니 동시성 문제의 경우 문제가 없다고 하는 것 같다.
@Bean
JPAQueryFactory jpaQueryFactory(EntityManager em) {
	return new JPAQueryFactory(em);
}
  • 생성자를 통해서 넣어주는게 좋아 보인다.
  • 빈 등록을 해두고 ,@RequiredArgsConstructor 를 통해서 주입 시켜주는게 좋아보인다.
    • 단, 테스트 코드 작성시에 제대로 작성되지않을 수있다.
      • 테스트 코드 작성시에 주입 제대로 넣어주고 하자!

동적 쿼리와 성능 최적화 - Builder를 사용

조회 최적화용 Dto 추가
@Data
public class MemberTeamDto {

    private Long memberId;
    private String username;
    private int age;
    private Long teamId;
    private String teamName;

    @QueryProjection
    public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
        this.memberId = memberId;
        this.username = username;
        this.age = age;
        this.teamId = teamId;
        this.teamName = teamName;
    }
}

Query 중급문법에서 사용한 Builder 사용하기.

public List<MemberTeamDto> searchByBuilder(MemberSearchCond cond){

    BooleanBuilder builder = new BooleanBuilder();

    if(hasText(cond.getUsername())){
        builder.and(member.username.eq(cond.getUsername()));
    }

    if(hasText(cond.getTeamName())){
        builder.and(team.name.eq(cond.getTeamName()));
    }

    if(cond.getAgeGoe() != null){
        builder.and(member.age.goe(cond.getAgeGoe()));
    }



    return queryFactory
            .select(new QMemberTeamDto(
                    member.id,
                    member.username,
                    member.age,
                    team.id,
                    team.name
            ))
            .from(member)
            .leftJoin(member.team,team)
            .where(builder)
            .fetch();
}
  • 동적 쿼리를 짤때 조심해야하는 점
    • 데이터가 쌓이다보면 쿼리한방에 엄청난 데이터를 가져올 수있기 때문에 최소한 조건을 꼭 넣어줘야한다.
      • 최소한 limit를 통해서 데이터 개수를 제한 걸기.

조회 API 컨트롤러 개발

  • 먼저 프로파일 설정을 통해서 샘플 데이터 추가가 테스트 케이스 실행에 영향이 없도록 해주자.
  • yml 파일에 밑의 코드를 추가해준다.
spring:
  profiles:
    active: local

그 후 테스트에 resource 디렉토리를 만들고 yml 파일을 복사해서 넣어준 뒤에 위의 부분의 active를 test로 바꿔준다.

  • 이렇게 해주면 main 코드와 테스트 코드 실행시 프로파일을 분리하는게 가능해짐.
  • 어떻게 분리하는가?
    • 샘플 데이터를 추가하는 곳에 @Profile("local")이라고 어노테이션을 적어두게 될 경우 yml 파일에 profile의 active 부분이 local인 곳에서만 실행된다.
      • test에서는 실행이 되지 않게 됨.

샘플 데이터 추가 하는 코드

@Profile("local") // -> profile이 local인 곳에서만 돌아감.
@Component
@RequiredArgsConstructor
public class InitMember {

    private final InitMemberService initMemberService;

    @PostConstruct
    public void init(){
        initMemberService.init();
    }

    @Component
    static class InitMemberService{
        @PersistenceContext
        EntityManager em;

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

            for(int idx = 0; idx <100; idx++){
                Team selectTeam = idx%2 == 0 ? teamA : teamB;
                em.persist(new Member("member" + idx,idx,selectTeam));
     
            }
        }
    }
}

@PostConstruct 와 @Transactional을 분리하는 이유

  • @PostConstruct 는 해당 빈 자체만 생성되었다고 가정하고 호출된다.
    • 빈과 관련된 AOP 등을 포함한 전체 애플리케이션 컨텍스트가 초기화 된것을 의미하지 않는다고 한다.
  • @Transactional을 처리하는 AOP등은 스프링의 후 처리기가 완전히 동작을 끝내서, 스프링 애플리케이션 컨텍스트의 초기화가 완료되어야 적용됨.

결론적으로 @PostConstruct 는 해당 빈의 AOP 적용이 보장되지 않기 때문에 분리해야한다.

참고한 글 : @PostConstruct와 @Transactional 분리 - 인프런 (inflearn.com)

profile
열심히하자
post-custom-banner

0개의 댓글