Entity & DTO

지우개·2024년 5월 6일
0

spring

목록 보기
2/2

Entity
: 데이터베이스에 저장되는 객체로, 데이터베이스와 직접 연결

DTO
: 클라이언트와 서버 간 데이터 전송을 위해 설계된 객체

Entity와 DTO를 분리해서 사용하는 이유

  • 필요한 데이터만 전송(서버 사용량↓)
    • Entity 범위에서 필요한 필드를 재정의하여 필요한 데이터만을 전송
    • 전송하는 데이터양과 네트워크 대역폭 사용량이 최적화되어 응답시간&전송시간↓
  • Entity 구조가 변경되어도 안전
    • Entity 구조가 변경되어도 변경사항이 Client에 직접적으로 영향을 미치지 않음
    • Entity와 Server 간의 결합도↓, 유지보수 용이
    • Entity 변경으로 인한 영향 최소화

(+) 웹 동작 과정 process

  • 정의 : Client(= Web browser), Server(회사)
  • 동장 : Client → request → Server → response → Client
  • 동작(심화) : View단(Client) → Controller단(main, router) → Service단 → Model단(DB)
    • View단 : 사용자에게 보여지는 화면
    • Controller단 : 사용자가 요청한 URL과 데이터(유효성 체크)를 전달받고 일을 분배하는 곳
    • Service단 : 실제 기능 구현
    • Model단 : DB관련된 기능 구현

  • View와 Model의 분리(MVC 패턴)
    • DTO는 View(사용자 인터페이스)와 Controller(서버)간의 인터페이스 역할을 하며, Entity는 Model(데이터베이스)의 역할을 함.
    • 이러한 분리를 통해 MVC 패턴을 적용하여, 코드 가독성과 유지보수를 용이하게 함
  • 순환 참조 예방
    • DTO는 Entity간의 양방향 참조가 포함되지 않은 간단한 구조임
    • Client와 Server간의 통신 과정에서 복잡한 객체 간 참조가 최소화, 순환 참조 문제 해결
  • Validation 코드와 모델링 코드의 분리(DTO, Entity)
    • validation 코드: @NotNull, @NotEmpty, @NotBlank...
    • 모델링 코드: @Column, @JoinColumn, @ManyToOne, @OneToOne...
    • Entity는 DB의 테이블과 매칭되는 필드가 선언되기 때문에 모델링을 위한 코드가 들어감.
    • 때문에 Entity에 validation 코드가 들어간다면 복잡↑, 가동성↓
    • 각각의 요청에 따른 DTO를 만들어 상황에 따라 필요한 validation을 추가한다면 Entity는 모델링에 집중
  • 보안 강화
    • DTO와 Entity를 분리함으로써 테이블 구조는 서버측에서만 알수있음. → 해커 데이터 조작 어려워짐
    • API 보안성 강화

DTO와 Entity 변환 위치?

=> Service단


Controller

  • Client의 요청을 받고, 응답을 반환함
  • Controller에서는 DTO의 형태로 데이터를 받아 Service에게 전달

Service

  • 비즈니스 로직을 수행하며 데이터 처리를 담당
  • Service에서는 Controller에서 받은 DTO를 Entity로 변환하고, 필요한 작업 수행후 Repository에 Entity를 전달

Entity

Post

@Table(name = "post")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@AllArgsConstructor
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column
    private String title;
    @Column
    private String nickname;
    @Column
    private String content;
    @Column
    private int view_;

    public void update(String title, String content) {
        if (title != null){
            this.title = title;
        }
        if (content != null){
            this.content = content;
        }
    }
}

DTO

PostDTO

@Getter
public class PostDTO {
    private Long id;
    private String title;
    private String nickname;
    private String content;
    private int view;

    public PostDTO(Post post){
        this.id = post.getId();
        this.title = post.getTitle();
        this.nickname = post.getNickname();
        this.content = post.getContent();
        this.view = post.getView_();
    }
}

PostCreateRequest

  • 게시글을 생성할때는 제목, 닉네임, 내용만 포함
@Builder
@Getter
public class PostCreateRequest {
    private String title;
    private String nickname;
    private String content;
}

PostReadResponse

  • 게시글을 조회할때는 제목, 닉네임, 내용, 조회수만 포함
@Builder
@Getter
public class PostReadResponse {
    private String title;
    private String nickname;
    private String content;
    private int view;
}

PostUpdateRequest

  • 게시글을 수정할때는 제목, 내용만 포함
@Builder
@Getter
public class PostUpdateRequest {
    private String title;
    private String content;
}

(테스트를 위한 더미 데이터)

create

PostController

  • 클라이언트는 POST 메소드와 함께 /posts 경로로 PostCreateRequest 데이터를 포함한 요청을 보냄
  • 서버는 이 요청을 처리하여 새로운 게시글을 생성함. (service단에서 처리)
  • 생성된 게시글의 정보를 담은 PostDTO 객체가 응답 본문으로 반환되며, 상태 코드 200 OK와 함께 클라이언트에게 전송됨.
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class PostController {

    private final PostService postService;

    // create
    @PostMapping("/posts")
    public ResponseEntity<PostDTO> create(@RequestBody PostCreateRequest request){
        PostDTO dto = postService.create(request);
        return ResponseEntity.ok(dto);
    }
}

PostService

  • 클라이언트로부터 게시글 생성을 위한 요청 데이터(PostCreateRequest 객체)를 받음.
  • 그 정보를 기반으로 데이터베이스에 새로운 게시글(Post 객체)를 저장.
  • 저장된 게시글의 정보를 PostDTO 객체로 변환하여 반환함.
@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;
    
    public PostDTO create(PostCreateRequest request) {
        Post post = Post.builder()
                .title(request.getTitle())
                .content(request.getContent())
                .nickname(request.getNickname())
                .build();

        return new PostDTO(postRepository.save(post));
    }
}


update

PostController

  • 클라이언트는 PUT 메소드와 함께 /posts/{id} 경로로 PostUpdateRequest 데이터, id를 포함한 요청을 보냄
  • 서버는 이 요청을 처리하여 게시글을 수정함. (service단에서 처리)
  • 수정된 게시글의 정보를 담은 PostDTO 객체가 응답 본문으로 반환되며, 상태 코드 200 OK와 함께 클라이언트에게 전송됨.
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class PostController {

    private final PostService postService;

    // update
    @PutMapping("/posts/{id}")
    public ResponseEntity<PostDTO> update(@PathVariable Long id, @RequestBody PostUpdateRequest request){
        PostDTO dto = postService.update(id, request);
        return ResponseEntity.ok(dto);
    }
}

PostService

  • 클라이언트로부터 게시글 수정을 위한 요청 데이터(PostCreateRequest 객체), id를 받음.
  • 그 정보를 기반으로 기존 게시글(Post 객체)의 정보를 업데이트하고 저장.
  • 수정된 게시글의 정보를 PostDTO 객체로 변환하여 반환함.

입력 데이터:

  • Long id: 수정하고자 하는 게시글의 식별자(ID)
  • PostUpdateRequest request: 게시글 수정을 위해 클라이언트로부터 받은 요청 데이터
@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;

    public PostDTO update(Long id, PostUpdateRequest request) {
        Post post = postRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 게시글x, id = " + id)
        );
        post.update(request.getTitle(), request.getContent());

        return new PostDTO(postRepository.save((post)));
    }
}


read

PostController

read()

  • 클라이언트는 특정 게시글의 상세 정보를 조회하기 위해 게시글의 id를 URL 경로 변수로 포함하여 GET 요청을 보냄. (예를 들어, /posts/1 경로는 id가 1인 게시글의 정보를 요청)
  • 서버는 요청 받은 게시글 id에 해당하는 게시글 정보를 조회하고, 그 결과를 PostReadResponse 객체에 담아 반환함. (service단에서 처리)
  • 조회된 게시글 정보를 담은 PostReadResponse 객체가 응답 본문으로 반환되며, HTTP 상태 코드 200 OK와 함께 클라이언트에게 전송됨.

readAll()

  • 클라이언트는 저장된 전체 게시글 목록을 조회하기 위해 /posts 경로로 GET 요청을 보냄.
  • 서버는 저장된 모든 게시글의 정보를 조회하고, 각 게시글의 정보를 PostReadResponse 객체 리스트에 담아 반환함. (service단에서 처리)
  • 조회된 전체 게시글 목록을 담은 List 객체가 응답 본문으로 반환되며, HTTP 상태 코드 200 OK와 함께 클라이언트에게 전송됨.
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class PostController {

    private final PostService postService;

    // read
    @GetMapping("/posts/{id}")
    public ResponseEntity<PostReadResponse> read(@PathVariable Long id){
        PostReadResponse dto = postService.read(id);
        return ResponseEntity.ok(dto);
    }

    // readAll
    @GetMapping("/posts")
    public ResponseEntity<List<PostReadResponse>> readAll(){
        List<PostReadResponse> dtoList = postService.readAll();
        return ResponseEntity.ok(dtoList);
    }
}

PostService

  • read(Long id) 메소드는 특정 게시글의 정보를 요청받아 해당 정보를 PostReadResponse 객체로 포맷하여 반환.
  • readAll() 메소드는 모든 게시글의 정보를 요청받아 각 게시글의 정보를 담은 PostReadResponse 객체 리스트로 반환
@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;

    public PostReadResponse read(Long id) {
        Post post = postRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 게시글x, id = " + id)
        );

        PostReadResponse response = PostReadResponse.builder()
                .title(post.getTitle())
                .content(post.getContent())
                .nickname(post.getNickname())
                .view(post.getView_())
                .build();

        return response;
    }

    public List<PostReadResponse> readAll() {
        List<Post> postList = postRepository.findAll();

        List<PostReadResponse> responseList = postList.stream().map(post -> PostReadResponse.builder()
                        .title(post.getTitle())
                        .content(post.getContent())
                        .nickname(post.getNickname())
                        .view(post.getView_())
                        .build())
                .collect(Collectors.toList());

        return responseList;
    }
}

read()

readAll()


delete

PostController

  • 클라이언트는 삭제하고자 하는 게시글의 id를 URL 경로에 포함시켜 DELETE 요청을 보냄. (예를 들어, /posts/3 URL은 id가 3인 게시글을 삭제하고자 하는 요청)
  • 서버는 요청 받은 게시글 id에 해당하는 게시글을 삭제하고, 그 결과를 PostDTO 객체에 담아 반환함.(service단에서 처리)
  • 삭제된 게시글의 정보를 담은 PostDTO 객체가 응답 본문으로 반환되며, HTTP 상태 코드 200 OK와 함께 클라이언트에게 전송됨
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class PostController {

    private final PostService postService;

    // delete
    @DeleteMapping("/posts/{id}")
    public ResponseEntity<PostDTO> delete(@PathVariable Long id){
        PostDTO dto = postService.delete(id);
        return ResponseEntity.ok(dto);
    }
}

PostService

  • 클라이언트로부터 받은 게시글 id를 기반으로 해당 게시글을 데이터베이스에서 찾아 삭제
  • 삭제된 게시글의 정보를 담은 PostDTO 객체를 반환
@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;

    public PostDTO delete(Long id) {
        Post post = postRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 게시글x, id = " + id)
        );
        postRepository.delete(post);

        return new PostDTO(post);
    }
}

0개의 댓글

관련 채용 정보