작업하면서 배우는 것들

QueryDsl Left Outer Join, Tuple, Projections

Jungsoomin :) 2020. 12. 27. 13:41

삽질한 기록은 아래!

 

 

Left outer Join , on() 으로 추가 조건을 내새울 수도 있다.

@Override
    public List<Post> findMyPostByIdWithReplies(Long id) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);

        List<Post> fetch = queryFactory.select(post)
                .from(post)
                .leftJoin(post.replies, reply).fetchJoin().fetch();
        return fetch;
    }

 

테스트 코드

 

@Test
    @Transactional
    @Rollback(value = false)
    public void joinTest(){
        Post post = postRepository.findById(1L).get();
        List<Reply> replies =new ArrayList<>();
        for(int i=0; i<5; i++){
            Reply reply = new Reply();
            reply.setAuthor("Jung " +i);
            reply.setAccountId(1L);
            reply.setSubject("Reply Subject " + i);
            reply.setContent("Reply Content "+i);
            reply.setPost(post);
            replies.add(reply);
        }
        replyRepository.saveAll(replies);


        List<Post> myPostByIdWithReplies = postRepository.findMyPostByIdWithReplies(1L);

        myPostByIdWithReplies.get(0).getReplies().forEach(reply -> System.out.println(reply.getId()));
    }

 


하나의 글에 따른 페이지 네이션 정보를 가져오고 싶을때, 가져오고 싶지않다면 post 속성은 빼주자.

 

@Override
    public Page<Reply> findByPostIdWithPagination(Long id, Pageable pageable) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QueryResults<Reply> registerDate = queryFactory.select(reply).from(reply)
                .where(reply.post.id.eq(id))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(OrderSpecifierFactory.getSortColumn(Order.DESC, reply, "registerDate"))
                .fetchResults();
        return new PageImpl(registerDate.getResults(),pageable,registerDate.getTotal());
    }

 

테스트 코드

 

@Test
    public void crud() {
        PageRequest request = PageRequest.of(0,10);
        Page<Reply> byPostIdWithPagination = replyRepository.findByPostIdWithPagination(1L, request);

        System.out.println(byPostIdWithPagination.getContent().get(0).getPost().getSubject());

        System.out.println("Check=======================");

        byPostIdWithPagination.getContent().forEach(reply ->
                System.out.println(reply.toString()));

    }

 

결과

 

 

추가 쿼리문과 결과

 


댓글 페이지 네이션 정보에 글정보까지 함께  가져오는 게 너무...너무 아닌것 같아서 실험 시작

 

  • 일단, post는 가져오지 못하게 막아주고 제네릭 클래스로 서비스에서 가져올 계획으로 응용해보려 시작
  • Tuple 을 간과한 대가를 치름.
@Override
    public Page<Reply> findByPostIdWithPagination(Long id, Pageable pageable) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QueryResults<Tuple> registerDate = queryFactory.select(reply.id, reply.subject,
                reply.author, reply.registerDate, reply.updateDate, reply.content)
                .from(reply)
                .where(reply.post.id.eq(id))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(OrderSpecifierFactory.getSortColumn(Order.DESC, reply, "registerDate"))
                .fetchResults();
        return new PageImpl(registerDate.getResults(),pageable,registerDate.getTotal());
    }

 

 

Stack OverFlow 와 Github 질문들을 해석하며 문제를 파악한 결과 Tuple 은 직렬화 문제때문에 Jackson이 바인딩을 못한다고 함.

 

  • 그리하여 문제를 해결하려고 한 결과 QueryDsl 에서 지원하는 Projections 를 채용
public interface PostCustomRepository<T> {

    Post findOnePostByIdAndNotFetchReplies(Long id);
}

// 구현
@Repository
@Transactional
public class PostCustomRepositoryImpl implements PostCustomRepository<Post> {

    @Autowired
    EntityManager entityManager;


    @Override
    public Post findOnePostByIdAndNotFetchReplies(Long id) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);

        Post findPost = queryFactory.select(Projections.fields(Post.class, 
                post.id,post.subject,post.author,post.registerDate,post.updateDate,post.content,post.accountId))
                .from(post)
                .where(post.id.eq(id))
                .fetchOne();
        return findPost;
    }


}

 

Post 서비스 테스트 결과 확인 후 원하는 쿼리 도출 확인 한 뒤 댓글 쿼리작업 진행

 

  • 알아보기 힘들어서 메서드 명이 길더라도 확실하게 알아볼 수 있게 기입하기 시작.
  • Projections 의 자태에 놀라기 시작. 피로도가 확 몰려오기 시작.
public interface ReplyCustomRepository<T> {

    Page<Reply> findByPostIdWithPagination(Long id, Pageable pageable);
}

//구현
@Repository
@Transactional
public class ReplyCustomRepositoryImpl implements ReplyCustomRepository<Reply>{

    @Autowired
    private EntityManager entityManager;


    @Override
    public Page<Reply> findByPostIdWithPagination(Long id, Pageable pageable) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QueryResults<Reply> registerDate = queryFactory.select(Projections.fields(
                Reply.class, reply.id, reply.subject,
                reply.author, reply.registerDate, reply.updateDate, reply.content
        ))
                .from(reply)
                .where(reply.post.id.eq(id))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(OrderSpecifierFactory.getSortColumn(Order.DESC, reply, "registerDate"))
                .fetchResults();
        return new PageImpl(registerDate.getResults(),pageable,registerDate.getTotal());
    }
}

바라고 바라던 제네릭 클래스 생성

  • 제네릭 지식을 바등바등 기억하며 어설프게 작성
  • 이후 서비스, 컨트롤러 코드 간소하게 작성
@AllArgsConstructor
public class ArticleInfo<P,R> implements Serializable {
    public P postInfo;

    public R replyInfo;
}

//서비스
public interface ArticleService {
    ArticleInfo<Post,Page<Reply>> getPostById(Long id);
}
//구현
@Service
@Slf4j
public class ArticleServiceImpl implements ArticleService{

    @Autowired
    private PostRepository postRepository;

    @Autowired
    ReplyRepository replyRepository;

    @Override
    @Transactional
    public ArticleInfo<Post, Page<Reply>> getPostById(Long id) {
        log.warn("getPostById, Parameter : {}",id);

        Post notFetchReplies = postRepository.findOnePostByIdAndNotFetchReplies(id);

        PageRequest request = PageRequest.of(0,10);

        Page<Reply> replyPage = replyRepository.findByPostIdWithPagination(id, request);

        ArticleInfo<Post, Page<Reply>> articleInfo = new ArticleInfo<>(notFetchReplies,replyPage);

        return articleInfo;

    }
}

///컨트롤러
@RestController
@Slf4j
public class TestController {

    @Autowired
    private ArticleService articleService;

    @GetMapping("/test/{id}")
    public ResponseEntity<Object> test(@PathVariable("id") Long id) {

        try {
            ArticleInfo<Post, Page<Reply>> articleInfo = articleService.getPostById(id);
            return ResponseEntity.ok(articleInfo);
        }catch (Exception ex) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
        }
    }
}

 

일단 JSON 데이터는 잘보내지는데, 쿼리 생성 정보 기록

  • id 를 기준으로 Post 정보 추출
  • reply 의 총 레코드 수 추출
  • RowNum 으로 페이지 네이션 잡아서 추출

'작업하면서 배우는 것들' 카테고리의 다른 글

MapStruct  (0) 2021.01.12
SpringFox:Swagger3 사용  (0) 2021.01.05
QueryDsl 과 커스텀 리포지토리를 이용한 쿼리문 사용  (0) 2020.12.27
JPA Projection FindAll  (0) 2020.12.24
Vue Router 와 Href 속성  (0) 2020.12.24