📚 공부한 책 : 코드로배우는 스프링 부트 웹프로젝트
❤️ github 주소 : https://github.com/qkralswl689/LearnFromCode/tree/main/board2022
DTO를 구성하는 기준은 화면에 전달하는 데이터이거나 화면쪽에서 전달되는 데이터를 기준으로 하기때문에 Entity클래스의 구성과 일치하지 않는 경우가 많다.
BoardDTO의 경우 Member에 대한 참조는 구성하지 않고 작성한다
- BoardDTO 클래스와 Board 엔티티 클래스와 다른점 : Member를 참조하는 대신 화면에서 필요한 작성자의 이메일과 이름으로 처리하고 있는 점
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BoardDTO {
private Long bno;
private String titel;
private String content;
private String writerEmail; // 작성자의 이메일(id)
private String writerName; // 작성자의 이름
private LocalDateTime regDate;
private LocalDateTime modDate;
private int replyCount; // 해당 게시글의 댓글 수
}
BoardDTO타입을 파라미터로 전달받고 생성된 게시물의 버호를 반환하도록 작성한다
BoardDTO를 Board 엔티티 타입으로 변환을 위해 dtoToEntity()를 작성한다
- dtoToEntity()는 DTO가 연관관계를 가진 Board 엔티티 객체와 Member 엔티티 객체를 구성해야 하므로 내부적으로 Member 엔티티를 처리하는 과정을 거쳐야 한다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.entity.Board;
import com.example.board2022.entity.Member;
public interface BoardService {
Long register(BoardDTO dto);
default Board dtoToEntity(BoardDTO dto){
Member member = Member.builder().email(dto.getWriterEmail()).build();
Board board = Board.builder()
.bno(dto.getBno())
.title(dto.getTitel())
.content(dto.getContent())
.writer(member)
.build();
return board;
}
}
import com.example.board2022.dto.BoardDTO
import com.example.board2022.entity.Board;
import com.example.board2022.entity.Member;
import com.example.board2022.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
@Autowired
private final BoardRepository repository; //자동주입 final
@Override
public Long register(BoardDTO dto) {
Board board = dtoToEntity(dto);
repository.save(board);
return board.getBno();
}
}
import com.example.board2022.dto.BoardDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BoardServiceTests {
@Autowired
private BoardService boardService;
@Test
public void testRegister(){
BoardDTO dto = BoardDTO.builder()
.titel("Test...")
.content("Test....")
.writerEmail("user55@aaa.com") // 현재 DB에 존재하는 회원이메일
.build();
Long bno = boardService.register(dto);
}
}
Hibernate:
select
member_.email,
member_.moddate as moddate2_1_,
member_.name as name4_1_,
member_.password as password5_1_
from
member member_
where
member_.email=?
Hibernate:
insert
into
board
(moddate, regdate, content, title, writer_email)
values
(?, ?, ?, ?, ?)
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;
// 화면에서 전달되는 목록 관련된 데이터에 대한 DTO를 PageRequestDTO 라는 이름으로 생성
// 목록 페이지를 요청할 때 사용하는 데이터를 재사용하기 쉽게 만든다
// -> 파라미터를 DTO로 선언하고 나중에 재사용하는 용도
@Builder
@AllArgsConstructor
@Data
public class PageRequestDTO { // 목적 : JPA쪽에서 사용하는 Pageable 타입의 객체를 생성하는 것
// 화면에서 절달되는 page,size 라는 파라미터를 수집한다
private int page;
private int size;
// 검색 처리를 위해 추가
private String type;
private String keyword;
public PageRequestDTO(){
// 페이비 번호 등은 기본값을 가지는것이 좋기때문에 1과10이라는 값을 지정한다
this.page = 1;
this.size = 10;
}
public Pageable getPageable(Sort sort){
// 페이지 번호가 0부터 시작한다는 점을 감안해 1페이지의 경우 0이 될수 있도록 page -1로 작성해준다
// 정렬은 다양한 상황에서 쓰기위해 별도의 파라미터로 받도록 설계
return PageRequest.of(page -1, size,sort);
}
}
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;
// 화면에서 필요한 결과는 PageResultDTO라는 이름으로 생성한다
@Data
// 다양한 곳에서 사용할 수 있도록 제네릭 타입을 이용해 DTO 와 EN(entity) 이라는 타입을 지정한다
public class PageResultDTO<DTO,EN>{
// 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;
// Function<EN,DTO> : 엔티티 객체들을 DTO로 변환해주는 기능
public PageResultDTO(Page<EN> result, Function<EN,DTO> fn){
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의 핵심은 JPQL의 결과로 나오는 Object[]를 DTO 타입으로 변화하는 기능이다.
- Object[]의 내용은 Board와 Member, 댓글수는 Long 타입으로 나오게 되므로 이것을 파라미터로 전달받아 BoardDTO를 구성하도록 작성해야 한다.
entityToDTO()는 총 3개의 파라미터를 처리할 수 있도록 구성한다
-> Board 엔티티 객체와 Member 엔티티 객체, 댓글의 수를 파라미터로 전달받도록 구성하고, 이것들을 이용해 BoardDTO 객체를 생성할 수 있도록 처리한다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.dto.PageRequestDTO;
import com.example.board2022.dto.PageResultDTO;
import com.example.board2022.entity.Board;
import com.example.board2022.entity.Member;
public interface BoardService {
PageResultDTO<BoardDTO, Object[]> getList(PageRequestDTO pageRequestDTO); // 목록처리
default BoardDTO entityToDTO(Board board,Member member,Long replyCount){
BoardDTO boardDTO = BoardDTO.builder()
.bno(board.getBno())
.titel(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;
}
}
getList()의 핵심 : entityToDTO()를 이용해 PageResultDTO 객체를 구성하는 부분이다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.dto.PageRequestDTO;
import com.example.board2022.dto.PageResultDTO;
import com.example.board2022.entity.Board;
import com.example.board2022.entity.Member;
import com.example.board2022.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.function.Function;
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
@Autowired
private final BoardRepository repository; //자동주입 final
@Override
public PageResultDTO<BoardDTO, Object[]> getList(PageRequestDTO pageRequestDTO) {
Function<Object[],BoardDTO> fn = (en -> entityToDTO((Board)en[0],(Member)en[1],(Long)en[2]));
Page<Object[]> result = repository.getBoardWithReplyCount(pageRequestDTO.getPageable(Sort.by("bno").descending()));
return new PageResultDTO<>(result,fn);
}
}
BoardDTO 객체 내에 목록 화면에 필요한 10개의 BoardDTO 객체가 만들어지고 필요한 모든 내용이 담겨져 있는것을 확인할 수 있다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.dto.PageRequestDTO;
import com.example.board2022.dto.PageResultDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BoardServiceTests {
@Autowired
private BoardService boardService;
@Test
public void testList(){
PageRequestDTO pageRequestDTO = new PageRequestDTO();
PageResultDTO<BoardDTO, Object[]> result = boardService.getList(pageRequestDTO);
for (BoardDTO boardDTO : result.getDtoList()) {
System.out.println(boardDTO);
}
}
}
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_.regdate as regdate3_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_.regdate as regdate3_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=101, titel=Test..., content=Test...., writerEmail=user55@aaa.com, writerName=USER55, regDate=2022-02-15T19:17:31.876659, modDate=2022-02-15T19:17:31.876659, replyCount=0)
BoardDTO(bno=100, titel=Title...100, content=Content...100, writerEmail=user100@aaa.com, writerName=USER100, regDate=2022-02-03T22:59:09.783053, modDate=2022-02-03T22:59:09.783053, replyCount=4)
BoardDTO(bno=99, titel=Title...99, content=Content...99, writerEmail=user99@aaa.com, writerName=USER99, regDate=2022-02-03T22:59:09.767433, modDate=2022-02-03T22:59:09.767433, replyCount=3)
BoardDTO(bno=98, titel=Title...98, content=Content...98, writerEmail=user98@aaa.com, writerName=USER98, regDate=2022-02-03T22:59:09.761206, modDate=2022-02-03T22:59:09.761206, replyCount=3)
BoardDTO(bno=97, titel=Title...97, content=Content...97, writerEmail=user97@aaa.com, writerName=USER97, regDate=2022-02-03T22:59:09.754006, modDate=2022-02-03T22:59:09.754006, replyCount=5)
BoardDTO(bno=96, titel=Title...96, content=Content...96, writerEmail=user96@aaa.com, writerName=USER96, regDate=2022-02-03T22:59:09.749046, modDate=2022-02-03T22:59:09.749046, replyCount=2)
BoardDTO(bno=95, titel=Title...95, content=Content...95, writerEmail=user95@aaa.com, writerName=USER95, regDate=2022-02-03T22:59:09.740988, modDate=2022-02-03T22:59:09.740988, replyCount=2)
BoardDTO(bno=94, titel=Title...94, content=Content...94, writerEmail=user94@aaa.com, writerName=USER94, regDate=2022-02-03T22:59:09.733527, modDate=2022-02-03T22:59:09.733527, replyCount=4)
BoardDTO(bno=93, titel=Title...93, content=Content...93, writerEmail=user93@aaa.com, writerName=USER93, regDate=2022-02-03T22:59:09.728620, modDate=2022-02-03T22:59:09.728620, replyCount=3)
BoardDTO(bno=92, titel=Title...92, content=Content...92, writerEmail=user92@aaa.com, writerName=USER92, regDate=2022-02-03T22:59:09.725467, modDate=2022-02-03T22:59:09.725467, replyCount=3)
게시물 조회는 파라미터로 게시물의 번호(bno)를 파라미터로 받아 처리한다
import com.example.board2022.dto.BoardDTO;
public interface BoardService {
// 생략..
BoardDTO get(Long bno);
// 생략..
}
BoardRepository의 getBoardByBno()를 이용해 처리한다
- getBoardByBno() : Board , Member 엔티티, 댓글의 수를 가져온다
import com.example.board2022.dto.BoardDTO;
import com.example.board2022.entity.Board;
import com.example.board2022.entity.Member;
import com.example.board2022.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
@Autowired
private final BoardRepository repository; //자동주입 final
@Override
public BoardDTO get(Long bno) {
Object result = repository.getBoardByBno(bno);
Object[] arr = (Object[]) result;
return entityToDTO((Board)arr[0],(Member)arr[1],(Long)arr[2]);
}
}
import com.example.board2022.dto.BoardDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BoardServiceTests {
@Autowired
private BoardService boardService;
@Test
public void testGet(){
Long bno = 100L;
BoardDTO boardDTO = boardService.get(bno);
System.out.println(boardDTO);
}
}
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_.regdate as regdate3_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_.regdate as regdate3_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
)
where
board0_.bno=?
BoardDTO(bno=100, titel=Title...100, content=Content...100, writerEmail=user100@aaa.com, writerName=USER100, regDate=2022-02-03T22:59:09.783053, modDate=2022-02-03T22:59:09.783053, replyCount=4)