관리 메뉴

bright jazz music

[bootBoard] N:1(다대일) 연관관계: 9-2. 프로젝트 적용: 게시물 목록 처리 본문

Framework/Spring

[bootBoard] N:1(다대일) 연관관계: 9-2. 프로젝트 적용: 게시물 목록 처리

bright jazz music 2022. 10. 5. 22:31

1. 게시물 목록 처리: PageRequestDTO와 PageResultDTO 추가

목록을 처리하기 위해 PageRequestDTO와 PageResultDTO 추가

PageRequestDTO.java

//PageRequestDTO.java

package com.example.bootboard.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; //페이지 관련 임포트
import org.springframework.data.domain.Sort;

@Builder
@AllArgsConstructor
@Data
public class PageRequestDTO {


    private int page;
    private int size;

    //검색을 위해 추가
    private String type;
    private String keyword;

    public PageRequestDTO(){
        this.page = 1;
        this.size = 10;
    }

    //  이 DTO의 목적은 JPA에서 사용하는 Pageable 타입의 객체를 생성하는 것이다.
    //  JPA를 이용하는 경우에는 페이지 번호가 0부터 시작한다.
    //  따라서 1페이지의 경우 0이 될 수 있도록 아래와 같이 작성한다.
    //  정렬(Sort)는 다양한 상황에서 사용하기 위해서 별도의 파라미터로 받도록 설계.
    public Pageable getPageable(Sort sort) {
        return PageRequest.of(page - 1, size, sort );
    }
}

 

PageResultDTO.java

//PageResultDTO.java

package com.example.bootboard.dto;
import lombok.Data;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Data
public class PageResultDTO<DTO, EN> {
    // 다양한 곳에서 사용할 수 있도록 제네릭 타입을 이용해서 DTO와 EN 타입을 지정. (EN = Entity)

    //DTO리스트
    private List<DTO> dtoList;

    //총 페이지 번호
    private int totalPage;

    //현재 페이지 번호
    private int page;

    //목록 사이즈
    private int size;

    //시작 페이지 번호, 끝 페이지 번호
    private int start, end;

    //이전, 다음
    private boolean prev, next;

    //페이지 번호 목록
    private List<Integer> pageList;




    //PageResultDTO는 Page<Entity> 타입을 이용해서 생성할 수 있도록 생성자로 작성.
    public PageResultDTO(Page<EN> result, Function<EN, DTO> fn){
        //Function: 엔티티 객체들을 DTO로 변환해 주는 기능

        dtoList = result.stream().map(fn).collect(Collectors.toList());

        totalPage = result.getTotalPages();

        makePageList(result.getPageable());
    }

    private void makePageList(Pageable pageable){
        this.page = pageable.getPageNumber() + 1; //0부터 시작하므로 1추가
        this.size = pageable.getPageSize();

        //temp end page
        int tempEnd = (int)(Math.ceil(page/10.0)) * 10;

        start = tempEnd - 9;

        prev = start > 1;

        end = totalPage > tempEnd ? tempEnd : totalPage;

        next = totalPage > tempEnd;

        pageList = IntStream.rangeClosed(start, end).boxed()
                .collect(Collectors.toList());

    }



    // 이와 같은 제네릭 방식을 적용하면 추후 추가적인 클래스를 작성하지 않고도 목록 데이터를 처리할 수 있다.

    /*
     * PageResultDTO는 List<DTO> 타입으로 DTO 객체들을 보관한다.
     * 그렇기 때문에 Page<Entity>의 내용물 중에서 엔티티 객체를 DTO로 변환하는 기능이 필요하다.
     * 가장 일반적인 형태는 추상 클래스를 이용해서 이를 처리하는 방식이다.
     * 그러한 경우 매번 새로운 클래스가 필요하다는 단점이 있다.
     *
     * 이번 프로젝트의 경우 엔티티 객체의 DTO 변환은 서비스 인터페이스에 정의한
     * entityToDto() 메서드와 별도로 Function객체로 만들어서 처리한다.
     * */

}
  • PageResultDTO의 핵심은 JPQL로 나오는 Object[ ]를 DTO타입으로 변환하는 기능이다.
  • 이 기능은 java.util.Function을 이용해 작성한다.
  • 현재 예제의 경우 JPQL의 실행 결과로 나오는 Object[ ]을 BoardDTO로 처리해 주어야 한다.

Object[ ]의 내용은 Board와 Member, 댓글의 수는 Long 타입으로 나온다. 따라서 이를 전달받아서 BoardDTO를 구성하도록 작성해야 한다.

 

이 기능은 BoardService 인터페이스에 entityToDTO()라는 메서드를 작성하여 처리한다.

 

2. BoardService에 entityToDTO()와 getList() 추가

 

  • entityToDTO()는 3개의 파라미터를 가진다. 그리고 이 파라미터들의 값을 이용해서 boardDTO 객체를 생성한다.
  • 게시물의 목록처리를 위해 getList()라는 메서드를 만들어 사용한다.
//BoardService.java

package com.example.bootboard.service;

import com.example.bootboard.dto.BoardDTO;
import com.example.bootboard.dto.PageRequestDTO;
import com.example.bootboard.dto.PageResultDTO;
import com.example.bootboard.entity.Board;
import com.example.bootboard.entity.Member;

public interface BoardService {

    Long register(BoardDTO dto);
    
    //getList() : 목록처리
    PageResultDTO<BoardDTO, Object[]> getList(PageRequestDTO pageRequestDTO);

    default Board dtoToEntity(BoardDTO dto) {
        Member member = Member.builder().email(dto.getWriterEmail()).build();

        Board board = Board.builder()
                .bno(dto.getBno())
                .title(dto.getTitle())
                .content(dto.getContent())
                .writer(member)
                .build();

        return board;
    }
    
    //entityToDTO()
    default BoardDTO entityToDTO(Board board, Member member, Long replyCount) {
        BoardDTO boardDTO = BoardDTO.builder()
                .bno(board.getBno())
                .title(board.getTitle())
                .content(board.getContent())
                .regDate(board.getRegDate())
                .modDate(board.getModDate())
                .writerEmail(member.getEmail())
                .writerName(member.getName())
                .replyCount(replyCount.intValue()) //long으로 나오므로 int처리
                .build();

        return boardDTO;
    }

}



 

BoardService 인터페이스에서 선언해줬으니 BoardServiceImpl에 오버라이드 필요.

 

 

3. BoardServiceImpl 수정(BoardService 인터페이스에 맞춰 오버라이드)

BoardServiceImpl.java

//BoardServiceImpl.java

package com.example.bootboard.service;

import com.example.bootboard.dto.BoardDTO;
import com.example.bootboard.dto.PageRequestDTO;
import com.example.bootboard.dto.PageResultDTO;
import com.example.bootboard.entity.Board;
import com.example.bootboard.entity.Member;
import com.example.bootboard.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.function.Function;

@Service
@RequiredArgsConstructor
@Log4j2
public class BoardServiceImpl implements BoardService {

    private final BoardRepository repository; // 자동 주입 final

    @Override
    public Long register(BoardDTO dto) {

        log.info(dto);
        Board board =  dtoToEntity(dto);

        repository.save(board);
        return board.getBno();
    }
	
    //페이지 목록 가져오는 getList()
    @Override
    public PageResultDTO<BoardDTO, Object[]> getList(PageRequestDTO pageRequestDTO) {
        log.info(pageRequestDTO);

        Function<Object[], BoardDTO> fn =
                (en -> entityToDTO((Board)en[0], (Member)en[1], (Long)en[2]));
                                    //Board board, Member member, Long replyCount

        Page<Object[]> result = repository.getBoardWithReplyCount(
                pageRequestDTO.getPageable(Sort.by("bno").descending()));

        return new PageResultDTO<>(result, fn);
    }
}

 

 

 

4. BoardServiceTests에 테스트 코드 추가

//BoardServiceTests.java
...

@Test
public void testList() {
    PageRequestDTO pageRequestDTO = new PageRequestDTO();

    PageResultDTO<BoardDTO, Object[]> result = boardService.getList(pageRequestDTO);

    for(BoardDTO boardDTO : result.getDtoList()){
        System.out.println(boardDTO);
    }
}

...

 

 

5. 테스트 결과

BoardDTO 객체 내에 목록 화면에 필요한 10개의 BoardDTO 객체가 만들어진다. 이 DTO에는 bno, title, content 등의 내용이 담겨 있다.

2022-10-06 22:57:02.347  INFO 27388 --- [    Test worker] c.e.bootboard.service.BoardServiceTests  : Started BoardServiceTests in 3.861 seconds (JVM running for 5.397)
2022-10-06 22:57:02.486  INFO 27388 --- [    Test worker] c.e.bootboard.service.BoardServiceImpl   : PageRequestDTO(page=1, size=10, type=null, keyword=null)
Hibernate: 
    select
        board0_.bno as col_0_0_,
        member1_.email as col_1_0_,
        count(reply2_.rno) as col_2_0_,
        board0_.bno as bno1_0_0_,
        member1_.email as email1_1_1_,
        board0_.moddate as moddate2_0_0_,
        board0_.reg_date as reg_date3_0_0_,
        board0_.content as content4_0_0_,
        board0_.title as title5_0_0_,
        board0_.writer_email as writer_e6_0_0_,
        member1_.moddate as moddate2_1_1_,
        member1_.reg_date as reg_date3_1_1_,
        member1_.name as name4_1_1_,
        member1_.password as password5_1_1_ 
    from
        board board0_ 
    left outer join
        member member1_ 
            on board0_.writer_email=member1_.email 
    left outer join
        reply reply2_ 
            on (
                reply2_.board_bno=board0_.bno
            ) 
    group by
        board0_.bno 
    order by
        board0_.bno desc limit ?
Hibernate: 
    select
        count(board0_.bno) as col_0_0_ 
    from
        board board0_
BoardDTO(bno=103, title=Test., content=Test ..., writerEmail=user55@aaa.com, writerName=USER55, regDate=2022-10-05T21:09:22.659477, modDate=2022-10-05T21:09:22.659477, replyCount=0)
BoardDTO(bno=102, title=Title...100, content=Content...100, writerEmail=user100@aaa.com, writerName=USER100, regDate=2022-10-05T21:01:48.365059, modDate=2022-10-05T21:01:48.365059, replyCount=0)
BoardDTO(bno=101, title=Title...99, content=Content...99, writerEmail=user99@aaa.com, writerName=USER99, regDate=2022-10-05T21:01:48.359115, modDate=2022-10-05T21:01:48.359115, replyCount=0)
BoardDTO(bno=100, title=Title...98, content=Content...98, writerEmail=user98@aaa.com, writerName=USER98, regDate=2022-10-05T21:01:48.353275, modDate=2022-10-05T21:01:48.353275, replyCount=1)
BoardDTO(bno=99, title=Title...97, content=Content...97, writerEmail=user97@aaa.com, writerName=USER97, regDate=2022-10-05T21:01:48.347276, modDate=2022-10-05T21:01:48.347276, replyCount=1)
BoardDTO(bno=98, title=Title...96, content=Content...96, writerEmail=user96@aaa.com, writerName=USER96, regDate=2022-10-05T21:01:48.340790, modDate=2022-10-05T21:01:48.340790, replyCount=1)
BoardDTO(bno=97, title=Title...95, content=Content...95, writerEmail=user95@aaa.com, writerName=USER95, regDate=2022-10-05T21:01:48.335785, modDate=2022-10-05T21:01:48.335785, replyCount=0)
BoardDTO(bno=96, title=Title...94, content=Content...94, writerEmail=user94@aaa.com, writerName=USER94, regDate=2022-10-05T21:01:48.329786, modDate=2022-10-05T21:01:48.329786, replyCount=0)
BoardDTO(bno=95, title=Title...93, content=Content...93, writerEmail=user93@aaa.com, writerName=USER93, regDate=2022-10-05T21:01:48.323767, modDate=2022-10-05T21:01:48.323767, replyCount=0)
BoardDTO(bno=94, title=Title...92, content=Content...92, writerEmail=user92@aaa.com, writerName=USER92, regDate=2022-10-05T21:01:48.316842, modDate=2022-10-05T21:01:48.316842, replyCount=0)
2022-10-06 22:57:02.692  INFO 27388 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-10-06 22:57:02.701  INFO 27388 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-10-06 22:57:02.713  INFO 27388 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 17s
4 actionable tasks: 3 executed, 1 up-to-date
오후 10:57:03: Task execution finished ':test --tests "com.example.bootboard.service.BoardServiceTests.testList"'.

 

 

 

 

 

 

 

 

 

 

 

 

 

Comments