yehey's 공부 노트 \n ο(=•ω<=)ρ⌒☆

[BE/spring boot] JPA DTO 생성 (with. swagger request body) 본문

적어보자 에러 일지

[BE/spring boot] JPA DTO 생성 (with. swagger request body)

yehey 2023. 12. 9. 13:14

Background

우선 spring에서 아키텍처를 어떻게 가져가야할 지 잘 모르겠어서 잘 정리된 포스팅을 참조해서 진행하고 있었다. 그치만 문제는 포스팅은 정말 간단한 예제였고, 내가 원하는 모든 기능이 나타나있지 않다는 점! (항상 그렇지만)

DB 구조는 다음과 같다. 여기서 다룰거는 셀프 참조 혹은 관계에 관한 에러다.

Issue / Error

tag - type 관계에서 tag를 추가하고 싶은데, DTO 구성을 어떻게 해야하는지...

  1. tag 라는 테이블이 tag_id가 pk고 셀프 참조 해서 parent_id 필드가 있음
  2. tag 테이블이랑 type 테이블이랑 연결되어잇는데(?) 여러 tag가 하나의 type을 가질 수 있음! type > tag 이런식이어서 JPA로 tag가 N이고 type이 1이라고 생각해서 tag에 manyToOne 넣고 tag table에 join column 해줬음 (여기까지는 entity)
  3. 근데 이제 request body와 등등.. 을 입력 받으려고 DTO를 entity와 거의 동일하게 생성하고 swagger로 확인하니 어라.. 관련된 모든 값이 다 필요한 것처럼 떠있음..

왜 이러는 걸까요 정말.......

내가 원하는 방법:

  1. request body에서 type_id를 number(Long)으로 받아서 사용하기
  2. request body에서 필요 없는 값은 제외하고 받기
  3. 추가로 tag는 셀프 참조여서 parent_id 도 받을 수 있어야함

 

My Solution

DTO는 단순히 데이터를 한 시스템에서 다른 시스템으로 전달하는 역할을 하는 객체, 여기서 DTO를 Entity와 동일하게 구성했기 때문에 관련된 모든 값을 입력값으로 받도록 설정되어서 해당 문제가 발생했다. 

그래서 받기를 원하는 값을 DTO로 새로 구성하고, 해당 값을 request body로 사용하면 문제가 해결될 것이라는 힌트를 얻었다. (from 나으 친구)

더보기

자세하고 친절한 설명 ><

 

보통은 requestbody에서 필요한 필드만 선언해서 사용해!

controller <-> service <-> repository

보통 이렇게 레이어간 주고받을 데이터 클래스를 나누는데 controller 에서 DTO 클래스로 필요한 값만 받고 service 로 넘긴 다음에 service 비즈니스 로직에서 domain(여기서는 entity) 클래스로 변환해서 사용하면 좋을거 같아

그래서 request body로 받을 DTO를 새롭게 생성하고, Service 로직에서 입력 값에 해당하는 entity를 가져와서 로직을 진행하는 방식으로 수정했다! 

 

Before

기존 DTO (지금 보니 양심이 없네)

public static class TagWithParentDTO {

	@JsonProperty ("type_id")
	private ArchiveType archiveTypeID;
	@JsonProperty ("name")
	private String name;
	@JsonProperty"parent_id")
	private Tag parentID;
	
	public Tag toEntity(){
		return Tag.builder ()
			.archiveTypeID(this.archiveTypeID)
			.name(this.name)
			.parentID(this .parentID)
			.build();
    	}
}

After

//PostTagRequestDTO

@Getter
@RequiredArgsConstructor
public class PostTagRequestDTO {
    @JsonProperty("type_id")
    private Long archiveTypeID;

    @JsonProperty("name")
    private String name;

    @JsonProperty("parent_id")
    private Long parentID;
}


//TagController
@PostMapping("/tags")
public ResponseEntity<?> postTagWithParent(@RequestBody PostTagRequestDTO dto){
    if (dto.getParentID()!=null){
        service.saveTagWithParent(dto);
    }else{
        service.saveTagExceptParent(dto);
    }
    return ResponseEntity.status(201).body("success");
}


//TagService
public void saveTagWithParent(PostTagRequestDTO dto){
	Tag parentTag = tagRepository.findByTagID(dto.getParentID());
    ArchiveType type = archiveTypeRepository.findByArchiveTypeID(dto.getArchiveTypeID());

    Tag tag = Tag.builder()
            .parentID(parentTag)
            .archiveTypeID(type)
            .name(dto.getName())
            .build();

    tagRepository.save(tag);

}

public void saveTagExceptParent(PostTagRequestDTO dto){
    ArchiveType type = archiveTypeRepository.findByArchiveTypeID(dto.getArchiveTypeID());

    Tag tag = Tag.builder()
            .archiveTypeID(type)
            .name(dto.getName())
            .build();

    tagRepository.save(tag);
}

 

서비스는 총 2개, 사실 내부에서 나누는게 더 좋아보이지만 (코드가 반복되기 때문에..) 뭔가 명확히 분리하고 싶어서 분리함 (나중에 리팩토링 할 땐 합칠 수도) 

request body를 DTO로 생성해주고, 관련 로직은 service에서 처리해줬다.


Error Review 

당연한걸 당연하지 않게 사용하니 에러가 났다고 생각했다. 그치만 이번을 계기로 DTO가 정말 무엇인지에 대해 고민할 수 있었고, 그 역할을 직접 깨달을 수 있었던 계기였다고 생각한다. 근데 이제 Request와..Response 혹은 기타 등등으로 많아지는 DTO들을 어떻게 관리를 해야할지 고민을 할 차례... 적절히 상속받아 사용하도록 수정해보고 싶다. (반복되는 코드를 줄이고 싶음!!!!)

 

참조:

https://breezymind.com/spring-boot-project-architecture/

Comments