📚 공부한 책 : 코드로배우는 스프링 부트 웹프로젝트
❤️ github 주소 : https://github.com/qkralswl689/LearnFromCode/tree/main/guestbook2022
DTO의 목적 : 데이터의 전달 => 읽고,쓰는 것이 모두 허용되는 점이 가능하고 일회성으로 사용되는 성격이 강하다
DTO사용의 장점 : 엔티티 객체의 범위를 한정 지을수 있기 때문에 좀 더 안전한 코드를 작성할 수 있으며 화면과 데이터를 분리하려는 취지에 부합한다
DTO사용의 단점 : Entity와 유사한 코드를 중복으로 개발, Entity객체를 DTO로 변환하거나 DTO객체를 Entity 객체로 변환하는 과정이 필요하다
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
// Entity클래스인 Guestbook과 거의 동일한 필드를 가지고 있으며 getter/setter를 통해 자유롭게 값을 변경할 수 있도록 구성한다
public class GuestbookDTO {
private Long gno;
private String title;
private String content;
private String writer;
private LocalDateTime regDate, modDate;
}
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.dto.PageResultDTO;
import com.example.guestbook2022.entity.Guestbook;
// service계층에서는 DTO를 이용해 필요한 내용을 전달받고 반환하도록 처리한다
public interface GuestbookService {
Long register(GuestbookDTO dto);
}
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.entity.Guestbook;
import com.example.guestbook2022.repository.GuestbookRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor // 의존성 자동 주입
public class GuestbookServiceImpl implements GuestbookService{
private final GuestbookRepository repository;
@Override
public Long register(GuestbookDTO dto) {
// service interface에 구현된 dtoToEntity 활용
Guestbook entity = dtoToEntity(dto);
repository.save(entity);
return entity.getGno();
}
service에서는 파라미터를 DTO타입으로 받기 때문에 JPA로 처리하기 위해서는 Entity타입의 객체로 변환해야 하는 작업이 반드시 필요하다
package com.example.guestbook2022.service;
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.dto.PageResultDTO;
import com.example.guestbook2022.entity.Guestbook;
// service계층에서는 DTO를 이용해 필요한 내용을 전달받고 반환하도록 처리한다
public interface GuestbookService {
Long register(GuestbookDTO dto);
// service 에서는 파라미터를 DTO타입으로 받기 때문에 JPA로 처리하기 위해서는 Entity 타입의 객체로 변환해야 하는 작업이 반드시 필요하다
// java 8 버전부터 인터페이스의 실제 내용을 가지는 코드는 default라는 키워드로 생성할 수 있다 -> 실제 코드를 인터페이스에 선언할 수 있다
// => 추상클래스를 생략하는것이 가능해 졌다
default Guestbook dtoToEntity(GuestbookDTO dto){
Guestbook entity = Guestbook.builder()
.gno(dto.getGno())
.title(dto.getTitle())
.content(dto.getContent())
.writer(dto.getWriter())
.build();
return entity;
}
}
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.entity.Guestbook;
import com.example.guestbook2022.repository.GuestbookRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.function.Function;
@Service
@RequiredArgsConstructor // 의존성 자동 주입
public class GuestbookServiceImpl implements GuestbookService{
private final GuestbookRepository repository;
@Override
public Long register(GuestbookDTO dto) {
// service interface에 구현된 dtoToEntity 활용
Guestbook entity = dtoToEntity(dto);
repository.save(entity);
return entity.getGno();
}
}
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.entity.Guestbook;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class GuestbookServiceTests {
@Autowired
private GuestbookService service;
@Test
public void testRegister(){
GuestbookDTO guestbookDTO = GuestbookDTO.builder()
.title("Sample Title...")
.content("Sample Content...")
.writer("user0")
.build();
System.out.println(service.register(guestbookDTO));
}
}
Hibernate:
insert
into
guestbook
(moddate, regdate, content, title, writer)
values
(?, ?, ?, ?, ?)
301
목록 처리시 고려사항
- 화면에서 필요한 목록 데이터에 대한 DTO 생성
- DTO 를 Pageable 타입으로 전환
- Page를 화면에서 사용하기 쉬운 DTO의 리스트 등으로 변환
- 화면에 필요한 페이지 번호 처리
재사용이 가능한 구조를 생성하기 위해 DTO 클래스를 작성한다
PageRequestDTO : 목록 페이지를 요청할 때 사용하는 데이터를 재사용하기 쉽게 만드는 클래스
=> 파라미터를 DTO로 선언하고 나중에 재사용하는 용도로 사용한다
목적 : JPA쪽에서 사용하는 Pageable 타입의 객체를 생성하는 것
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;
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);
}
}
PageResultDTO : JPA Repository에서 페이지 처리 결과를 Page 타입으로 반환해 주면 service에서 이를 처리하기 위해 만들어진 클래스 이다
- Page의 객체들을 DTO 객체로 변환해 자료구조로 담아준다
- 화면 출력에 필요한 페이지 정보들을 구성해 준다
import lombok.Data;
import org.springframework.data.domain.Page;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
// 화면에서 필요한 결과는 PageResultDTO라는 이름으로 생성한다
@Data
// 다양한 곳에서 사용할 수 있도록 제네릭 타입을 이용해 DTO 와 EN(entity) 이라는 타입을 지정한다
public class PageResultDTO <DTO,EN>{
private List<DTO> dtoList;
// Function<EN,DTO> : 엔티티 객체들을 DTO로 변환해주는 기능
public PageResultDTO(Page<EN> result, Function<EN,DTO> fn){
dtoList = result.stream().map(fn).collect(Collectors.toList());
}
}
- PageRequestDTO를 파라미터로, PageResultDTO를 리턴타입으로 사용하는getList()를 설계
- Entoty 객체를 DTO 객체로 변환하는 entityToDto()를 정의한다
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.dto.PageResultDTO;
import com.example.guestbook2022.entity.Guestbook;
// service계층에서는 DTO를 이용해 필요한 내용을 전달받고 반환하도록 처리한다
public interface GuestbookService {
Long register(GuestbookDTO dto);
PageResultDTO<GuestbookDTO,Guestbook> getList(PageRequestDTO requestDTO);
// service 에서는 파라미터를 DTO타입으로 받기 때문에 JPA로 처리하기 위해서는 Entity 타입의 객체로 변환해야 하는 작업이 반드시 필요하다
// java 8 버전부터 인터페이스의 실제 내용을 가지는 코드는 default라는 키워드로 생성할 수 있다 -> 실제 코드를 인터페이스에 선언할 수 있다
// => 추상클래스를 생략하는것이 가능해 졌다
default Guestbook dtoToEntity(GuestbookDTO dto){
Guestbook entity = Guestbook.builder()
.gno(dto.getGno())
.title(dto.getTitle())
.content(dto.getContent())
.writer(dto.getWriter())
.build();
return entity;
}
default GuestbookDTO entityToDto(Guestbook entity){
GuestbookDTO dto = GuestbookDTO.builder()
.gno(entity.getGno())
.title(entity.getTitle())
.content(entity.getContent())
.writer(entity.getWriter())
.regDate(entity.getRegDate())
.modDate(entity.getModDate())
.build();
return dto;
}
}
getList() 구현
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.dto.PageResultDTO;
import com.example.guestbook2022.entity.Guestbook;
import com.example.guestbook2022.repository.GuestbookRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.function.Function;
@Service
@RequiredArgsConstructor // 의존성 자동 주입
public class GuestbookServiceImpl implements GuestbookService{
private final GuestbookRepository repository;
@Override
public PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestDTO) {
Pageable pageable = requestDTO.getPageable(Sort.by("gno").descending());
Page<Guestbook> result = repository.findAll(pageable);
Function<Guestbook,GuestbookDTO> fn = (entity -> entityToDto(entity));
// JPA의 처리결과인 Page<Entity>와 Function을 전달해 엔티티 객체들을 DTO의 리스트로 변환하고 화면에 페이지 처리와 필요한 값들을 생성한다
return new PageResultDTO<>(result,fn);
}
}
Entity 객체들을 DTO 객체로 변환 테스트
package com.example.guestbook2022.service;
import com.example.guestbook2022.dto.GuestbookDTO;
import com.example.guestbook2022.dto.PageRequestDTO;
import com.example.guestbook2022.dto.PageResultDTO;
import com.example.guestbook2022.entity.Guestbook;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class GuestbookServiceTests {
@Autowired
private GuestbookService service;
@Test
public void testList(){
PageRequestDTO pageRequestDTO = PageRequestDTO.builder().page(1).size(10).build();
PageResultDTO<GuestbookDTO, Guestbook> resultDTO = service.getList(pageRequestDTO);
for(GuestbookDTO guestbookDTO : resultDTO.getDtoList()){
System.out.println(guestbookDTO);
}
}
}
Hibernate:
select
guestbook0_.gno as gno1_0_,
guestbook0_.moddate as moddate2_0_,
guestbook0_.regdate as regdate3_0_,
guestbook0_.content as content4_0_,
guestbook0_.title as title5_0_,
guestbook0_.writer as writer6_0_
from
guestbook guestbook0_
order by
guestbook0_.gno desc limit ?
Hibernate:
select
count(guestbook0_.gno) as col_0_0_
from
guestbook guestbook0_
// 결과
GuestbookDTO(gno=301, title=Sample Title..., content=Sample Content..., writer=user0, regDate=2022-01-31T19:43:17.140328, modDate=2022-01-31T19:43:17.140328)
GuestbookDTO(gno=300, title=Changed Title..., content=Changed Content..., writer=user0, regDate=2022-01-31T14:16:14.908530, modDate=2022-01-31T14:24:46.650907)
GuestbookDTO(gno=299, title=Title...299, content=Content...299, writer=user9, regDate=2022-01-31T14:16:14.904784, modDate=2022-01-31T14:16:14.904784)
GuestbookDTO(gno=298, title=Title...298, content=Content...298, writer=user8, regDate=2022-01-31T14:16:14.900567, modDate=2022-01-31T14:16:14.900567)
GuestbookDTO(gno=297, title=Title...297, content=Content...297, writer=user7, regDate=2022-01-31T14:16:14.892919, modDate=2022-01-31T14:16:14.892919)
GuestbookDTO(gno=296, title=Title...296, content=Content...296, writer=user6, regDate=2022-01-31T14:16:14.890396, modDate=2022-01-31T14:16:14.890396)
GuestbookDTO(gno=295, title=Title...295, content=Content...295, writer=user5, regDate=2022-01-31T14:16:14.887866, modDate=2022-01-31T14:16:14.887866)
GuestbookDTO(gno=294, title=Title...294, content=Content...294, writer=user4, regDate=2022-01-31T14:16:14.885068, modDate=2022-01-31T14:16:14.885068)
GuestbookDTO(gno=293, title=Title...293, content=Content...293, writer=user3, regDate=2022-01-31T14:16:14.882828, modDate=2022-01-31T14:16:14.882828)
GuestbookDTO(gno=292, title=Title...292, content=Content...292, writer=user2, regDate=2022-01-31T14:16:14.881012, modDate=2022-01-31T14:16:14.881012)