이전 프로젝트에 이미지 추가
- DTO
- 목록 처리
- 검색 조건 처리
- 게시물 CRUD
- 컨트롤러와 화면 처리
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로 변환하는 방식
- ModelMapper : 엔티티를 DTO로 변환
- Projections : JPQL의 결과를 즉시 DTO로 처리하는 기능
- 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);
}
검색 조건과 페이징 처리한 목록 처리 순서
- Querydsl을 이용해 조인
- 검색 조건
- 프로젝션 사용시, 쿼리 결과를 DTO로
- 필요하면 그룹바이
- 페이징 처리
- 튜플 사용시, 튜플로 처리 후 조합
- PageImpl에 파라미터 전달해 Page<DTO> 반환
파라미터 : dtoList, count, pageable
4. 게시물 CRUD
- 게시물 등록
- 게시물 조회
- 게시물 수정
- 게시물 삭제
- 게시물 목록
게시물 등록 처리 : 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=? |
게시물 수정 처리
게시물 수정시에는 모든 첨부파일이 삭제 후에 추가된다.
수정 처리 동작 과정
- bno를 이용해 Board 객체 select
- 수정된 내용 처리
- 첨부파일 초기화
- 수정된 내용을 바탕으로 첨부파일 추가
(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("----");
});
}
-> 첨부파일 순서대로 출력
'Server Programming > Spring Boot Backend Programming' 카테고리의 다른 글
8장-1. 스프링 시큐리티 (+ SecurityFilterChain, webSecurityCustomizer(), @EnableGlobalMethodSecurity, CSRF 토큰) (0) | 2022.12.13 |
---|---|
7장-5. 이미지 추가를 위한 컨트롤러와 화면 처리 (+ 파일명에 언더바가 들어간 경우 에러 발생) (0) | 2022.12.11 |
7장-3. 일대다 연관관계의 N+1 문제와 @BatchSize (0) | 2022.12.09 |
7장-2. 일대다 연관관계인 게시물과 첨부파일 (+ 매핑테이블, @Transactional, @EntityGraph) (0) | 2022.12.09 |
7장-1. 다중 파일 업로드 처리 (+MultipartFile) (0) | 2022.12.09 |