본문 바로가기

Server Programming/Spring Boot Backend Programming

7장-4. 이전 프로젝트에 이미지 추가

반응형

이전 프로젝트에 이미지 추가

  1. DTO
  2. 목록 처리
  3. 검색 조건 처리
  4. 게시물 CRUD
  5. 컨트롤러와 화면 처리

 

1. 화면에 출력되는 최종 DTO 작성

: BoardDTO -> BoardListReplyDTO = BoardDTO + BoardListReplyCount
  -> BoardListAllDTO = BoardListReplyDTO + BoardImageDTO

 

(1) BoardImage 엔티티를 위한 BoardImageDTO 작성

package org.zerock.b01.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BoardImageDTO {
    private String uuid;
    private String filename;
    private int ord;
}

 

(2) 화면에 출력할 모든 정보를 처리하는 BoardListAllDTO 작성

package org.zerock.b01.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BoardListAllDTO {
    private Long bno;
    private String title;
    private String writer;
    private LocalDateTime regDate;

    private Long replyCount;
    
    private List<BoardImageDTO> boardImages;
    
}

 

2. 목록 처리를 위해 BoardSerivce 변경

-BoardListAllDTO를 이용하는 listWithAll() 목록출력 메서드 작성

(1) BoardService에 listWithAll() 선언

PageResponseDTO<BoardListAllDTO> listWithAll(PageRequestDTO pageRequestDTO);

 

(2) BoardServiceImpl에 listWithAll() 구현

@Override
public PageResponseDTO<BoardListAllDTO> listWithAll(PageRequestDTO pageRequestDTO) {
    //1. 리포지토리 호출
    String[] types = pageRequestDTO.getTypes();
    String keyword = pageRequestDTO.getKeyword();
    Pageable pageable = pageRequestDTO.getPageable("bno");


   Page<BoardListAllDTO> result = boardRepository.searchWithAll(types, keyword, pageable);


    //2. PageResponseDTO<BoardListAllDTO>를 생성자를 이용해 생성
    //: 생성자를 호출하기위해 PageRequestDTO , dtoList, total가 필요
    return PageResponseDTO.withAll()
            .pageRequestDTO(pageRequestDTO)
            .dtoList(result.getContent())
            .total((int)result.getTotalElements())
            .build();
}

-> BoardSearch/BoardSearchImpl의 searchWithAll()메서드의 리턴타입을 BoardListAllDTO로 변경

 

3. Querydsl를 이용해 검색 조건 처리

-Tuple를 이용해 엔티티 객체를 DTO로 변환

엔티티 객체를 DTO로 변환하는 방식

  1. ModelMapper : 엔티티를 DTO로 변환
  2. Projections : JPQL의 결과를 즉시 DTO로 처리하는 기능
  3. Tuple : 객체안에 중첩된 구조(HashSet)를 처리하는 경우 Tuple을 이용해 DTO로 변환

 

(1) BoardSearch의 searchWithAll()메서드의 리턴타입을 BoardListAllDTO로 변경

//BoardListReplyDTO -> BoardListAllDTO
Page<BoardListAllDTO> searchWithAll(String[] types, String keyword, Pageable pageable);

 

(2) BoardSearchImpl의 searchWithAll()메서드의 리턴타입을 BoardListAllDTO로 변경

-List<Tuple> -> List<BoardListAllDTO> 로 변경

 

변경 전, searchWithAll()

//검색 조건 없이 페이징 처리한 게시물과 댓글 외부 조인
@Override
public Page<BoardListReplyDTO> searchWithAll(String[] types, String keyword, Pageable pageable) {
    QBoard board=QBoard.board;
    QReply reply = QReply.reply;

    //from board
    JPQLQuery<Board> boardJPQLQuery=from(board);
    //left join reply on board=reply.board
    boardJPQLQuery.leftJoin(reply).on(reply.board.eq(board));

    //paging
    getQuerydsl().applyPagination(pageable,boardJPQLQuery);

    //쿼리 결과 저장
    List<Board> boardList=boardJPQLQuery.fetch();

    boardList.forEach(board1 ->{
        System.out.println(board1.getBno());
        System.out.println(board1.getImageSet());
        System.out.println("----------");
    });

    return null;
}

 

변경 후, searchWithAll()

  • 메서드 명 : searchWithAll()
  • 파라미터 : PageRequestDTO
  • 리턴 타입 : PageResponseDTO
  • 쿼리 방식 : Querydsl, JPQL

 

필요한 작업

  • 외부 조인
  • PageImpl()를 이용한 페이징 처리
  • 게시물당 처리하기 위해 groupBy 적용
  • 데이터를 튜플로 추출 처리

 

//BoardListReplyDTO -> BoardListAllDTO
@Override
public Page<BoardListAllDTO> searchWithAll(String[] types, String keyword, Pageable pageable) {
    QBoard board=QBoard.board;
    QReply reply = QReply.reply;

    //from board
    JPQLQuery<Board> boardJPQLQuery=from(board);
    //left join reply on board=reply.board
    boardJPQLQuery.leftJoin(reply).on(reply.board.eq(board));

    //게시물당 처리를 위한 groupBy
    boardJPQLQuery.groupBy(board);

    //paging
    getQuerydsl().applyPagination(pageable,boardJPQLQuery);

    //튜플로 처리
    //:SELECT로 가져오는 데이터는 하나의 엔티티가 아닌, 여러 엔티티의 데이터들이 섞인 Object
    JPQLQuery<Tuple> tupleJPQLQuery=boardJPQLQuery.select(board, reply.countDistinct());

    //쿼리 결과 저장
    List<Tuple> tupleList=tupleJPQLQuery.fetch();

    //스트림을 이용해 List<Tuple> -> List<BoardListAllDTO> 변환
    //: tuple ->BoardListAllDTO =  Board + replyCount + BoardImage
    List<BoardListAllDTO> dtoList=tupleList.stream().map(tuple->{
        //
        //SELECT한 board의 타입은 Expression <T>로 -> 객체 변수로 지정
       Board board1 = (Board) tuple.get(board);
        //SELECT한 댓글 개수를 변수로 지정 -> 인덱스로 검색 후, 두 번째 인수에 원하는 타입 설정
       Long replyCount =  tuple.get(1, Long.class);

       BoardListAllDTO dto = BoardListAllDTO.builder()
               .bno(board1.getBno())
               .title(board1.getTitle())
               .writer(board1.getWriter())
               .regDate(board1.getRegDate())
               .replyCount(replyCount)
               .build();

       //이미지 추가 : BoardImage -> BoardImageDTO 필요

      return dto;
    }).collect(Collectors.toList());

    //총 개수 추출
    Long totalCount = boardJPQLQuery.fetchCount();

    //PageImpl() : 한번에 페이징 처리
    //파라미터 : dtoList, pageable, totalCount
    return new PageImpl<>(dtoList, pageable, totalCount);
}

 

(3) 테스트 코드 작성

//BoardListAllDTO를 목록으로 가져오는 메서드 테스트
@Transactional
@Test
public void testSearchImageReplyCount(){
    Pageable pageable =PageRequest.of(0, 10, Sort.by("bno").descending());
    
    //검색조건 추가
    //boardRepository.searchWithAll(null, null, pageable);
    
    //페이징 처리 확인
    Page<BoardListAllDTO> result = boardRepository.searchWithAll(null, null, pageable);
    
    log.info("BoardListAllDTO 목록 출력 테스트");
    log.info("---");
    log.info(result.getTotalElements());
    
    result.getContent().forEach(boardListAllDTO -> {
        log.info(boardListAllDTO);
    });
}

 

Hibernate: 
    select
        board0_.bno as col_0_0_,
        count(distinct reply1_.rno) as col_1_0_,
        board0_.bno as bno1_0_,
        board0_.moddate as moddate2_0_,
        board0_.regdate as regdate3_0_,
        board0_.content as content4_0_,
        board0_.title as title5_0_,
        board0_.writer as writer6_0_ 
    from
        board board0_ 
    left outer join
        reply reply1_ 
            on (
                reply1_.board_bno=board0_.bno
            ) 
    group by
        board0_.bno 
    order by
        board0_.bno desc limit ?
Hibernate: 
    select
        count(distinct board0_.bno) as col_0_0_ 
    from
        board board0_ 
    left outer join
        reply reply1_ 
            on (
                reply1_.board_bno=board0_.bno
            )

 

 

(4) BoardImage 처리

-BoardSearchWithAll의 SearchWithAll()에 BoardImage -> BoardImageDTO를 이용해 이미지 추가

//BoardListReplyDTO -> BoardListAllDTO
@Override
public Page<BoardListAllDTO> searchWithAll(String[] types, String keyword, Pageable pageable) {
    QBoard board=QBoard.board;
    QReply reply = QReply.reply;

    //from board
    JPQLQuery<Board> boardJPQLQuery=from(board);
    //left join reply on board=reply.board
    boardJPQLQuery.leftJoin(reply).on(reply.board.eq(board));

    //게시물당 처리를 위한 groupBy
    boardJPQLQuery.groupBy(board);

    //paging
    getQuerydsl().applyPagination(pageable,boardJPQLQuery);

    //튜플로 처리
    //:SELECT로 가져오는 데이터는 하나의 엔티티가 아닌, 여러 엔티티의 데이터들이 섞인 Object
    JPQLQuery<Tuple> tupleJPQLQuery=boardJPQLQuery.select(board, reply.countDistinct());

    //쿼리 결과 저장
    List<Tuple> tupleList=tupleJPQLQuery.fetch();

    //스트림을 이용해 List<Tuple> -> List<BoardListAllDTO> 변환
    //: tuple ->BoardListAllDTO =  Board + replyCount + BoardImage
    List<BoardListAllDTO> dtoList=tupleList.stream().map(tuple->{
        //
        //SELECT한 board의 타입은 Expression <T>로 -> 객체 변수로 지정
       Board board1 = (Board) tuple.get(board);
        //SELECT한 댓글 개수를 변수로 지정 -> 인덱스로 검색 후, 두 번째 인수에 원하는 타입 설정
       Long replyCount =  tuple.get(1, Long.class);

       BoardListAllDTO dto = BoardListAllDTO.builder()
               .bno(board1.getBno())
               .title(board1.getTitle())
               .writer(board1.getWriter())
               .regDate(board1.getRegDate())
               .replyCount(replyCount)
               .build();
       //이미지 추가 : BoardImage -> BoardImageDTO 필요
        // 다중 이미지 처리를 위해 리스트처리
        List<BoardImageDTO> imageDTOS = board1.getImageSet().stream().sorted()
                .map(boardImage -> 
                    BoardImageDTO.builder()
                            .uuid(boardImage.getUuid())
                            .filename(boardImage.getFileName())
                            .ord(boardImage.getOrd())
                            .build()).collect(Collectors.toList());
        dto.setBoardImages(imageDTOS);


      return dto;
    }).collect(Collectors.toList());

    //총 개수 추출
    Long totalCount = boardJPQLQuery.fetchCount();

    //PageImpl() : 한번에 페이징 처리
    //파라미터 : dtoList, pageable, totalCount
    return new PageImpl<>(dtoList, pageable, totalCount);
}

 

(5) 테스트 코드를 이용해 이미지가져오는지 확인

Hibernate: 
    select
        board0_.bno as col_0_0_,
        count(distinct reply1_.rno) as col_1_0_,
        board0_.bno as bno1_0_,
        board0_.moddate as moddate2_0_,
        board0_.regdate as regdate3_0_,
        board0_.content as content4_0_,
        board0_.title as title5_0_,
        board0_.writer as writer6_0_ 
    from
        board board0_ 
    left outer join
        reply reply1_ 
            on (
                reply1_.board_bno=board0_.bno
            ) 
    group by
        board0_.bno 
    order by
        board0_.bno desc limit ?
Hibernate: 
    select
        imageset0_.board_bno as board_bn4_1_1_,
        imageset0_.uuid as uuid1_1_1_,
        imageset0_.uuid as uuid1_1_0_,
        imageset0_.board_bno as board_bn4_1_0_,
        imageset0_.file_name as file_nam2_1_0_,
        imageset0_.ord as ord3_1_0_ 
    from
        board_image imageset0_ 
    where
        imageset0_.board_bno in (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
Hibernate: 
    select
        count(distinct board0_.bno) as col_0_0_ 
    from
        board board0_ 
    left outer join
        reply reply1_ 
            on (
                reply1_.board_bno=board0_.bno
            )

 

(6) 페이징 이전 부분에 검색조건 추가

//BoardListReplyDTO -> BoardListAllDTO
@Override
public Page<BoardListAllDTO> searchWithAll(String[] types, String keyword, Pageable pageable) {
    QBoard board=QBoard.board;
    QReply reply = QReply.reply;
    //1. 조인

    //from board
    JPQLQuery<Board> boardJPQLQuery=from(board);
    //left join reply on board=reply.board
    boardJPQLQuery.leftJoin(reply).on(reply.board.eq(board));

    //2. 검색조건 추가
    if((types!=null) && types.length>0 && keyword!=null){
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        
        for(String type : types){
            switch (type){
                case "t":
                     booleanBuilder.or(board.title.contains(keyword));
                     break;
                case "c":
                    booleanBuilder.or(board.content.contains(keyword));
                    break;
                case "w":
                    booleanBuilder.or(board.writer.contains(keyword));
                    break;
            }
        }
        boardJPQLQuery.where(booleanBuilder);
    }
    
    //3. 게시물당 처리를 위한 groupBy
    boardJPQLQuery.groupBy(board);

    //4. paging
    getQuerydsl().applyPagination(pageable,boardJPQLQuery);

    //5. 튜플로 처리
    //:SELECT로 가져오는 데이터는 하나의 엔티티가 아닌, 여러 엔티티의 데이터들이 섞인 Object
    JPQLQuery<Tuple> tupleJPQLQuery=boardJPQLQuery.select(board, reply.countDistinct());

    //쿼리 결과 저장
    List<Tuple> tupleList=tupleJPQLQuery.fetch();

    //스트림을 이용해 List<Tuple> -> List<BoardListAllDTO> 변환
    //: tuple ->BoardListAllDTO =  Board + replyCount + BoardImage
    List<BoardListAllDTO> dtoList=tupleList.stream().map(tuple->{
        //
        //SELECT한 board의 타입은 Expression <T>로 -> 객체 변수로 지정
       Board board1 = (Board) tuple.get(board);
        //SELECT한 댓글 개수를 변수로 지정 -> 인덱스로 검색 후, 두 번째 인수에 원하는 타입 설정
       Long replyCount =  tuple.get(1, Long.class);

       BoardListAllDTO dto = BoardListAllDTO.builder()
               .bno(board1.getBno())
               .title(board1.getTitle())
               .writer(board1.getWriter())
               .regDate(board1.getRegDate())
               .replyCount(replyCount)
               .build();
       //이미지 추가 : BoardImage -> BoardImageDTO 필요
        // 다중 이미지 처리를 위해 리스트처리
        List<BoardImageDTO> imageDTOS = board1.getImageSet().stream().sorted()
                .map(boardImage ->
                    BoardImageDTO.builder()
                            .uuid(boardImage.getUuid())
                            .filename(boardImage.getFileName())
                            .ord(boardImage.getOrd())
                            .build()).collect(Collectors.toList());
        dto.setBoardImages(imageDTOS);


      return dto;
    }).collect(Collectors.toList());

    //총 개수 추출
    Long totalCount = boardJPQLQuery.fetchCount();

    //6. PageImpl() : 한번에 페이징 처리
    //파라미터 : dtoList, pageable, totalCount
    return new PageImpl<>(dtoList, pageable, totalCount);
}

 

 

검색 조건과 페이징 처리한 목록 처리 순서

  1. Querydsl을 이용해 조인
  2. 검색 조건
  3. 프로젝션 사용시, 쿼리 결과를 DTO로
  4. 필요하면 그룹바이
  5. 페이징 처리
  6. 튜플 사용시, 튜플로 처리 후 조합
  7. PageImpl에 파라미터 전달해 Page<DTO> 반환
    파라미터 : dtoList, count, pageable

4. 게시물 CRUD

  1. 게시물 등록
  2. 게시물 조회
  3. 게시물 수정
  4. 게시물 삭제
  5. 게시물 목록

 

게시물 등록 처리 : DTO -> Entity

첨부파일의 경우, 업로드 되어있는 파일 정보 사용자에게 제공해야하므로,

이전에 업로드된 파일 이름을 문자열로 받아서 리스트로 처리한다.

이미지 파일을 가리키는 이름 BoardImage fileNames ImageSet
종류 BoardImage List<String> Set<BoardImage>
위치 Entity(BoardImage) DTO(BoardDTO) Entity(Board)

 

(1) BoardDTO에서 업로드되어있는 이미지파일을 포함하기 위해,  파일이름을 담은 리스트 변수 추가

package org.zerock.b01.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.zerock.b01.domain.QBaseEntity;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BoardDTO {
    private Long bno;

    @NotEmpty
    @Size(min=3, max=100)
    private String title;
    @NotEmpty
    private String content;
    @NotEmpty
    private String writer;

    private LocalDateTime regDate;
    private LocalDateTime modDate;

    //첨부파일의 이름들
    private List<String> fileNames;
}

 

(2)  DTO를 엔티티로 변환하고 이미지를 문자열 배열로 처리하는 메서드 작성

-ModelMapper는 간단한 구조의 객체의 변환하기에 적합하고, 나머지의 경우 직접 변환

-BoardService에 DTO-> Entity로 변환하고, 이미지를 문자열 배열로 처리하는 default 메서드를 작성한다.

//DTO -> Entity 변환하는 디폴트 메서드
//: 이미지를 문자열 배열로 처리
default Board dtoToEntity(BoardDTO boardDTO){
    Board board = Board.builder()
            .bno(boardDTO.getBno())
            .title(boardDTO.getTitle())
            .content(boardDTO.getContent())
            .writer(boardDTO.getWriter())
            .build();
    //List<String> fileNames -> Set<BoardImage>

    if(boardDTO.getFileNames()!=null){
        //List<String>
        boardDTO.getFileNames().forEach(fileName->{
            String[] arr = fileName.split("_");
            // uuid, fileName
            board.addImage(arr[0], arr[1]);
         });
    }
    return board;
}

 

(3) 등록 처리

 

변경 전, ModelMapper로 변환하는 register()

@Override
public Long register(BoardDTO boardDTO) {
    //DTO -> Entity 변환
    Board board=modelMapper.map(boardDTO, Board.class);

    //리파지토리에 저장 (영속 컨텍스트에 저장) -> 데이터베이스와 동기화
    Long bno = boardRepository.save(board).getBno();

    return bno;
}

변경 후, dtoToEntity()로 변환하는 register()

//이미지 추가된 BoardDTO
@Override
public Long register(BoardDTO boardDTO) {
    //DTO -> Entity 변환
    Board board=dtoToEntity(boardDTO);

    //리파지토리에 저장 (영속 컨텍스트에 저장) -> 데이터베이스와 동기화
    Long bno = boardRepository.save(board).getBno();

    return bno;
}

 

(4) 테스트 코드 작성

 

변경 전, 테스트 코드

@Test
public void testRegister(){
    log.info(boardService.getClass().getName());
    //"org.zerock.b01.service.ServiceTests.testRegister"'.

    BoardDTO boardDTO= BoardDTO.builder()
            .title("Sample Title...")
            .content("Sample Content...")
            .writer("user00")
            .build();

    Long bno = boardService.register(boardDTO);

    log.info("bno : "+bno);
}

 

변경 후, 이미지 파일 포함된 ReplyDTO

@Test
public void testRegister(){
    log.info(boardService.getClass().getName());
    //"org.zerock.b01.service.ServiceTests.testRegister"'.

    BoardDTO boardDTO= BoardDTO.builder()
            .title("Sample Title...")
            .content("Sample Content...")
            .writer("user00")
            .build();
    //문자열 배열의 이미지 파일 이름 설정
    boardDTO.setFileNames(
            Arrays.asList(
                    UUID.randomUUID()+"_aaa.jpg",
                    UUID.randomUUID()+"_bbb.jpg",
                    UUID.randomUUID()+"_ccc.jpg"
            )
    );


    Long bno = boardService.register(boardDTO);

    log.info("bno : "+bno);
}

 

Hibernate: 
    insert 
    into
        board
        (moddate, regdate, content, title, writer) 
    values
        (?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        board_image
        (board_bno, file_name, ord, uuid) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        board_image
        (board_bno, file_name, ord, uuid) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        board_image
        (board_bno, file_name, ord, uuid) 
    values
        (?, ?, ?, ?)

게시물 조회 처리 : Entity -> DTO

이미지 파일을 가리키는 이름 BoardImage fileNames ImageSet
종류 BoardImage List<String> Set<BoardImage>
위치 Entity(BoardImage) DTO(BoardDTO) Entity(Board)

(1) BoardService에 entityToDTO 디폴트 메서드 작성

// Entity -> DTO 변환하는 디폴트 메서드
//: 이미지를 문자열 배열로 처리
default BoardDTO entityToDTO(Board board){
    BoardDTO boardDTO = BoardDTO.builder()
            .bno(board.getBno())
            .title(board.getTitle())
            .content(board.getContent())
            .writer(board.getWriter())
            .regDate(board.getRegDate())
            .modDate(board.getModDate())
            .build();
    //Set<BoardImage> -> List<String> fileNames
    // BoardImage 클래스에 구현한 Comparable<BoardImage>를 이용해 정렬처리
    List<String> fileNames=
            board.getImageSet().stream().sorted().map(boardImage ->
                    boardImage.getUuid()+"_"+boardImage.getFileName()).collect(Collectors.toList());

    //Setter로 List<String> fileNames 생성
    boardDTO.setFileNames(fileNames);

    return boardDTO;
}

 

(2) BoardServiceImpl의 readOne()에서 이미지까지 가져오는 메서드로 변경

-findById() -> findByIdWithImages()

- findByIdWithImages() : @EntitGraph를 이용해 다중 이미지까지 가져오는 쿼리

  @Override
    public BoardDTO readOne(Long bno) {

        //Null 처리를 위해 Optional 타입을 사용한다.
        //Optional<Board> result = boardRepository.findById(bno);
        //이미지까지 가져오는 리포지토리 메서드 호출
        Optional<Board> result=boardRepository.findByIdWithImages(bno);
        //Board board=result.orElseThrow(IllegalAccessError::new);
        Board board=result.orElseThrow();

        //이미지까지 가져오는 메서드 이용한 Board -> BoardDTO
        BoardDTO boardDTO = entityToDTO(board);

        return boardDTO;
    }

 

(3) 테스트 코드 작성

//이미지까지 가져오는 readOne() 테스트
@Test
public void testReadAll(){
    Long bno=101L;
    BoardDTO boardDTO=boardService.readOne(bno);
    
    log.info("이미지 포함 readOne 테스트");
    log.info("boardDTO");
    for(String fileName : boardDTO.getFileNames()){
        log.info(fileName);
    }
}

 

Hibernate: 
    select
        board0_.bno as bno1_0_0_,
        imageset1_.uuid as uuid1_1_1_,
        board0_.moddate as moddate2_0_0_,
        board0_.regdate as regdate3_0_0_,
        board0_.content as content4_0_0_,
        board0_.title as title5_0_0_,
        board0_.writer as writer6_0_0_,
        imageset1_.board_bno as board_bn4_1_1_,
        imageset1_.file_name as file_nam2_1_1_,
        imageset1_.ord as ord3_1_1_,
        imageset1_.board_bno as board_bn4_1_0__,
        imageset1_.uuid as uuid1_1_0__ 
    from
        board board0_ 
    left outer join
        board_image imageset1_ 
            on board0_.bno=imageset1_.board_bno 
    where
        board0_.bno=?

게시물 수정 처리

게시물 수정시에는 모든 첨부파일이 삭제 후에 추가된다.

 

수정 처리 동작 과정

  1. bno를 이용해 Board 객체 select
  2. 수정된 내용 처리
  3. 첨부파일 초기화
  4. 수정된 내용을 바탕으로 첨부파일 추가

(1) modify() 메서드  변경

-첨부파일 초기화

-수정된 내용을 바탕으로 첨부파일 추가

@Override
public void modify(BoardDTO boardDTO) {
    Optional<Board> result = boardRepository.findById(boardDTO.getBno());
    Board board = result.orElseThrow(IllegalAccessError::new);
    board.change(boardDTO.getTitle(), boardDTO.getContent());

    //1. 첨부파일 초기화
    //String title, String content
    board.change(boardDTO.getTitle(), boardDTO.getContent());
    //2. 수정된 내용을 바탕으로 첨부파일 추가
    if(boardDTO.getFileNames()!=null){
        //List<String>
        for(String fileName : boardDTO.getFileNames()){
            String[] arr = fileName.split("_");
            // uuid, fileName
            board.addImage(arr[0], arr[1]);
        }
    }

    boardRepository.save(board);
}

 

(2) 테스트 코드 작성

@Test
public void testModify(){
    //변경에 필요한 데이터만으로 객체 생성
    BoardDTO boardDTO = BoardDTO.builder()
            .bno(10L)
            .title("Updated...101")
            .content("Updated content 101...")
            .build();
    //이미지 추가
    boardDTO.setFileNames(Arrays.asList(UUID.randomUUID()+"_zzz.jpg"));
    boardService.modify(boardDTO);
}

-> Board객체의 이미지를 삭제하고 새로운첨부파일 하나만 가진다.

 


게시물 삭제 처리

FK 제약조건에 의해 댓글이 존재하지 않는 경우에만 PK가 삭제가능하다.

-> FK 제약조건을 만족하기위해서는 BoardService에서 모든 댓글을 삭제 후 게시물을 삭제하도록 변경한다.

cascade과 orphanRemoval 속성에 의해 상위 엔티티인 게시물 삭제시 하위 엔티티인 이미지도 함께 삭제된다.

//상위 엔티티 삭제시 하위 엔티티 삭제 테스트
//: Board 삭제시 BoardImage 삭제
@Test
public void testRemoveAll(){
    Long bno=1L;
    boardService.remove(bno);
}

게시물 목록 처리

검색과 페이징이 처리가 필요한 목록

 

(1) BoardServiceImpl에 listWithAll()메서드 구현

@Override
public PageResponseDTO<BoardListAllDTO> listWithAll(PageRequestDTO pageRequestDTO) {
    //1. 리포지토리 호출
    String[] types = pageRequestDTO.getTypes();
    String keyword = pageRequestDTO.getKeyword();
    Pageable pageable = pageRequestDTO.getPageable("bno");


    Page<BoardListAllDTO> result = boardRepository.searchWithAll(types, keyword, pageable);


    //2. PageResponseDTO<BoardListAllDTO>를 생성자를 이용해 생성
    //: 생성자를 호출하기위해 PageRequestDTO , dtoList, total가 필요
    return PageResponseDTO.<BoardListAllDTO>withAll()
            .pageRequestDTO(pageRequestDTO)
            .dtoList(result.getContent())
            .total((int)result.getTotalElements())
            .build();
}

 

(2) 테스트 코드 작성

//이미지 포함한 Board에 검색 조건+페이징 처리한 목록 출력 테스트
@Test
public void testListWithAll(){
    PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
            .type("tcw")
            .keyword("1")
            .page(1)
            .size(10)
            .build();
    PageResponseDTO<BoardListAllDTO> responseDTO = boardService.listWithAll(pageRequestDTO);

    List<BoardListAllDTO> dtoList=responseDTO.getDtoList();

    //반복문을 이용해 게시물 목록에서 각 게시물의 이미지 출력
    dtoList.forEach(boardListAllDTO -> {
        log.info(boardListAllDTO.getBno()+":"+ boardListAllDTO.getTitle());

        //이미지 존재시
        if(boardListAllDTO.getBoardImages()!=null){
            for(BoardImageDTO boardImageDTO : boardListAllDTO.getBoardImages()){
                log.info(boardImageDTO);
            }
        }
        log.info("----");
    });


}

-> 첨부파일 순서대로 출력


 

 

반응형