๐Ÿ’ป ๊ฐœ๋ฐœ/Spring

DTO์™€ Response๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ด์œ 

thrownewme 2026. 1. 13. 13:24

๐Ÿค” ๊ทธ๋ƒฅ DTO ํ•˜๋‚˜๋กœ ์“ฐ๋ฉด ์•ˆ ๋˜๋ƒ๋Š” ์งˆ๋ฌธ์„ ์ง„์งœ ๋งŽ์ด ๋ฐ›๋Š”๋‹ค.

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒŒ ๋งž๋‹ค.
๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ์ ํŠธ๊ฐ€ ๊ธ‰์ž‘์Šค๋Ÿฝ๊ฒŒ Mybatis ๋กœ ๋ณ€๊ฒฝ๋˜์–ด ์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ๊ฒƒ์ด์ง€, JPA ์˜ ๊ฒฝ์šฐ๋Š” Entity ๊นŒ์ง€ ๋ถ„๋ฆฌํ–ˆ์„ ๊ฒƒ์ด๋‹ค.

 

๋‚˜๋„ ์ฒจ์—๋Š” DTO๋งŒ ์“ฐ๋ ค๊ณ  ํ–ˆ์—ˆ๋Š”๋ฐ (Mapper์—์„œ DTO๋กœ ๋ฐ›์•„์„œ ๊ทธ๋Œ€๋กœ ๋ฆฌํ„ดํ•˜๋ฉด ํŽธํ•˜๋‹ˆ๊นŒ..)

๋ช‡ ๋ฒˆ ๊ฒช์–ด๋ณด๋‹ˆ๊นŒ ๋ถ„๋ฆฌํ•˜๋Š”๊ฒŒ ๋งž๋”๋ผ

 

๐Ÿ“ฆ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด

domain/
โ””โ”€โ”€ book/
โ”œโ”€โ”€ controller/
โ”‚ โ””โ”€โ”€ BookController.java
โ”œโ”€โ”€ dto/
โ”‚ โ””โ”€โ”€ BookDto.java ← DB ๋งคํ•‘์šฉ
โ”œโ”€โ”€ mapper/
โ”‚ โ””โ”€โ”€ BookMapper.java ← MyBatis ์ธํ„ฐํŽ˜์ด์Šค
โ”œโ”€โ”€ model/
โ”‚ โ””โ”€โ”€ BookListResponse.java ← API ์‘๋‹ต์šฉ
โ”‚ โ””โ”€โ”€ BookDetailResponse.java
โ””โ”€โ”€ service/
โ””โ”€โ”€ BookService.java

 

 

DTO - DB ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๋Š”๋‹ค

@Getter
@Setter
public class BookDto {
    private Long seq;
    private String title;
    private String author;
    private String isbn;
    private String publisherCode;    // DB์—๋Š” ์ฝ”๋“œ๋กœ ์ €์žฅ
    private String statusCode;       // 'A': ๋Œ€์ถœ๊ฐ€๋Šฅ, 'B': ๋Œ€์ถœ์ค‘, 'D': ํ๊ธฐ
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private String createdBy;        // ๋‚ด๋ถ€ ๊ด€๋ฆฌ์šฉ
    private String internalMemo;     // ๋ฏผ๊ฐ ์ •๋ณด (API ๋…ธ์ถœ X)
}

 

์ด๊ฑด ๋ง ๊ทธ๋Œ€๋กœ DB ๊ฒฐ๊ณผ ๋‹ด๋Š” ์šฉ๋„๋‹ค.
์ปฌ๋Ÿผ์ด ๋Š˜์–ด๋‚˜๋“ , ์ด๋ฆ„์ด ๋ฐ”๋€Œ๋“  ์ƒ๊ด€์—†๋‹ค.

 

Response - API๋กœ ๋‚ด๋ณด๋‚ผ ํ˜•ํƒœ๋ฅผ ์ •์˜ํ•œ๋‹ค

@Getter
@Builder
public class BookListResponse {
    private Long id;
    private String title;
    private String author;
    private String publisher;        // ์ฝ”๋“œ → ์ด๋ฆ„์œผ๋กœ ๋ณ€ํ™˜๋จ
    private String status;           // 'A' → "๋Œ€์ถœ ๊ฐ€๋Šฅ"
    private String registeredDate;   // yyyy-MM-dd ํฌ๋งท

    public static BookListResponse from(BookDto dto) {
        return BookListResponse.builder()
                .id(dto.getSeq())
                .title(dto.getTitle())
                .author(dto.getAuthor())
                .publisher(PublisherCode.toName(dto.getPublisherCode()))
                .status(BookStatus.fromCode(dto.getStatusCode()).getDisplayName())
                .registeredDate(formatDate(dto.getCreatedAt()))
                .build();
    }

    private static String formatDate(LocalDateTime dateTime) {
        if (dateTime == null) return null;
        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }
}

 

์—ฌ๊ธฐ์„œ๋Š” ํ”„๋ก ํŠธ๊ฐ€ ๋ฐ”๋กœ ์“ฐ๊ธฐ ์ข‹์€ ํ˜•ํƒœ๋กœ ๋งŒ๋“ ๋‹ค.
DB ๊ตฌ์กฐ๋Š” ์‹ ๊ฒฝ ์•ˆ ์จ๋„ ๋œ๋‹ค.

 

Service์—์„œ ๋ณ€ํ™˜

@Service
@RequiredArgsConstructor
public class BookService {
    private final BookMapper bookMapper;

    public List<BookListResponse> getBookList(BookSearchCondition condition) {
        List<BookDto> books = bookMapper.selectBookList(condition);
        
        return books.stream()
                .map(BookListResponse::from)   // DTO → Response ๋ณ€ํ™˜
                .collect(Collectors.toList());
    }
}

 

Service๊ฐ€ ๊น”๋”ํ•ด์ง„๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฝ๊ธฐ๋„ ํŽธํ•˜๋‹ค.


 

๐ŸŽฏ ์™œ ์ด๋ ‡๊ฒŒ ๋ถ„๋ฆฌํ• ๊นŒ?


1. DB ์Šคํ‚ค๋งˆ ๋ฐ”๋€Œ์–ด๋„ API๋Š” ์•ˆ ๊ฑด๋“œ๋ฆฌ๊ณ  ์‹ถ์–ด์„œ

DB ์ปฌ๋Ÿผ๋ช…์ด `pub_code`์—์„œ `publisher_cd`๋กœ ๋ฐ”๋€Œ์–ด๋„?
→ DTO๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋จ. Response๋Š” ๊ทธ๋Œ€๋กœ.

// DTO๋งŒ ๋ณ€๊ฒฝ
public class BookDto {
    // private String pubCode;     // before
    private String publisherCd;    // after - MyBatis ๋งคํ•‘๋งŒ ์ˆ˜์ •
}

// Response๋Š” ๋ณ€๊ฒฝ ์—†์Œ - API ์ŠคํŽ™ ์œ ์ง€
public class BookListResponse {
    private String publisher;  // ๊ทธ๋Œ€๋กœ
}



2. ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ์‹ค์ˆ˜๋กœ ๋…ธ์ถœ๋˜๋Š”๊ฑธ ๋ง‰๊ธฐ ์œ„ํ•ด

// DTO์—๋Š” ์žˆ์ง€๋งŒ
public class BookDto {
    private String internalMemo;    // ๋‚ด๋ถ€ ๊ด€๋ฆฌ์šฉ ๋ฉ”๋ชจ
    private String createdBy;       // ๋“ฑ๋ก์ž ์‚ฌ๋ฒˆ
    private BigDecimal purchasePrice; // ๊ตฌ๋งค๊ฐ€
}

// Response์—์„œ๋Š” ์˜๋„์ ์œผ๋กœ ์ œ์™ธ
public class BookListResponse {
    // ์œ„ ํ•„๋“œ๋“ค ์—†์Œ = API์— ์ ˆ๋Œ€ ๋…ธ์ถœ ์•ˆ ๋จ
}

 

>> `@JsonIgnore` ๋กœ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์•„์˜ˆ ํ•„๋“œ๊ฐ€ ์—†๋Š” ๊ฒŒ ๋” ์•ˆ์ „ํ•˜๋‹ค.

 


3. API ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›Œ์ง„๋‹ค

๐Ÿ”Ž V1, V2 API๋ฅผ ๋™์‹œ์— ์šด์˜ํ•ด์•ผ ํ•  ๋•Œ:

// V1 - ๊ธฐ์กด ์‘๋‹ต
public class BookListResponseV1 {
    private String status;  // "๋Œ€์ถœ ๊ฐ€๋Šฅ"
    
    public static BookListResponseV1 from(BookDto dto) { ... }
}

// V2 - ์ƒˆ๋กœ์šด ์‘๋‹ต ํฌ๋งท
public class BookListResponseV2 {
    private BookStatus status;  // { code: "A", name: "๋Œ€์ถœ ๊ฐ€๋Šฅ", available: true }
    
    public static BookListResponseV2 from(BookDto dto) { ... }
}

 

>> DTO๋Š” ํ•˜๋‚˜, Response๋งŒ ๋ฒ„์ „๋ณ„๋กœ ๊ด€๋ฆฌ.

 

 

4. ํ”„๋ก ํŠธ์—”๋“œ ์นœํ™”์ ์ธ ์‘๋‹ต์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค

// DB์—๋Š” ์ด๋ ‡๊ฒŒ ์ €์žฅ๋˜์–ด ์žˆ์ง€๋งŒ
BookDto {
    statusCode: "A",
    publisherCode: "PUB001",
    createdAt: "2024-01-15T14:30:00"
}

// API ์‘๋‹ต์€ ํ”„๋ก ํŠธ๊ฐ€ ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ
BookListResponse {
    status: "๋Œ€์ถœ ๊ฐ€๋Šฅ",
    publisher: "๋ฏผ์Œ์‚ฌ",
    registeredDate: "2024-01-15"
}

 



๐Ÿ’ก from() ๋ฉ”์„œ๋“œ๋ฅผ Response์— ๋‘๋Š” ์ด์œ 

๋ณ€ํ™˜ ๋กœ์ง์„ ์–ด๋””์— ๋‘˜๊นŒ? ์„ธ ๊ฐ€์ง€ ์„ ํƒ์ง€๊ฐ€ ์žˆ๋‹ค:

์œ„์น˜ ์˜ˆ์‹œ ํŠน์ง•
Service `toResponse(dto)` Service๊ฐ€ ๋น„๋Œ€ํ•ด์ง
๋ณ„๋„ Mapper `BookMapper.toResponse(dto)` ํŒŒ์ผ์ด ๋Š˜์–ด๋‚จ
Response ๋‚ด๋ถ€ `BookListResponse.from(dto)` ์‘์ง‘๋„ ๋†’์Œ

 

๋‚˜๋Š” Response ํด๋ž˜์Šค ๋‚ด๋ถ€์— `from()` ์ •์  ๋ฉ”์„œ๋“œ๋ฅผ ๋‘๋Š” ๋ฐฉ์‹์„ ์„ ํ˜ธํ•œ๋‹ค.

// Response๊ฐ€ "๋‚˜๋Š” ์ด DTO๋กœ๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด์งˆ ์ˆ˜ ์žˆ์–ด"๋ฅผ ์Šค์Šค๋กœ ์ •์˜
public static BookListResponse from(BookDto dto) {
    return BookListResponse.builder()
            .id(dto.getSeq())
            // ... ๋ณ€ํ™˜ ๋กœ์ง
            .build();
}

 

์žฅ์ :

  • ์‘์ง‘๋„: ๋ณ€ํ™˜ ๋กœ์ง์ด Response ํด๋ž˜์Šค ์•ˆ์— ์บก์Аํ™”๋จ
  • ๊ฐ€๋…์„ฑ: `BookListResponse.from(dto)` - ์ฝ๊ธฐ ์‰ฌ์›€
  • ํ…Œ์ŠคํŠธ: Response ํด๋ž˜์Šค๋งŒ ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ•˜๋ฉด ๋จ

๐Ÿท๏ธ ์ •๋ฆฌ

๊ตฌ๋ถ„ DTO Response
์—ญํ•  DB ↔ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ↔ ํด๋ผ์ด์–ธํŠธ
์œ„์น˜  `dto/` ํŒจํ‚ค์ง€ `model/` ํŒจํ‚ค์ง€
ํ•„๋“œ DB ์ปฌ๋Ÿผ๊ณผ 1:1 API ์ŠคํŽ™์— ๋งž๊ฒŒ ๊ฐ€๊ณต
๋ฏผ๊ฐ์ •๋ณด ํฌํ•จ ๊ฐ€๋Šฅ ๋…ธ์ถœํ•  ๊ฒƒ๋งŒ
๋ณ€๊ฒฝ ์ฃผ๊ธฐ DB ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ API ์ŠคํŽ™ ๋ณ€๊ฒฝ ์‹œ

 


์ฒ˜์Œ์— ์„ค๊ณ„ํ•  ๋• ํด๋ž˜์Šค๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์ง€๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€ ์ƒ๊ฐํ–ˆ์ง€๋งŒ ์ ์šฉํ•ด๋ณด๋‹ˆ ๋งค์šฐ ํ•ฉ๋ฆฌ์ ์ธ ์„ ํƒ์ด๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค ใ…‹


'๐Ÿ’ป ๊ฐœ๋ฐœ > Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Spring Cloud Gateway์—์„œ JWT ์ธ์ฆ ํ•„ํ„ฐ ๊ตฌํ˜„ํ•˜๊ธฐ  (0) 2026.01.13