Back-end/Spring

[Spring/스프링] - DAO, DTO, VO 정리

poopooreum 2025. 5. 24. 19:24
반응형

안녕하세요!
오늘은 백엔드 개발자가 꼭 알아야 할 3가지 객체인 DAO, DTO, VO에 대해서 정리를 해보겠습니다.


DAO, DTO, VO는 백엔드 개발에서 자주 사용되는 개념이며, 서로의 역할이 명확히 나뉘어 있습니다.

- DAO: 데이터베이스 접근 담당 (Persistence Layer)
- DTO: 계층 간 데이터 전달을 위한 객체
- VO: 불변 객체로, 값 자체에 의미가 있는 객체


DAO(Data Access Object)

개념

DAO는 Data Access Object의 약자로, DB의 데이터에 접근하기 위한 객체를 가리키며 DB에 접근하기 로직을 분리하기 위해 사용합니다. 직접 DB에 접근하여 data를 삽입, 삭제, 조회 등 조작할 수 있는 기능을 수행한다. 주로 MVC패턴의 Model에서 이러한 일을 수행합니다.

책임

DB의 연결 및 쿼리 실행, CRUD(SELECT, INSERT, UPDATE, DELETE)작업을 처리합니다.

@Repository
interface UserDao : JpaRepository<UserEntity, Long> {
    fun findByUsername(username: String): UserEntity?
}

DTO(Data Transfer Object)

개념

DTO는 Data Transfer Object의 약자로, 계층 간(Controller, View, Business Layer) 데이터 교환을 위한 Java Bean을 의미합니다. 또한, 로직 없이 데이터만 담는 객체로, 주로 getter와 setter로 구성되며 계층 간 데이터 전달에 사용됩니다.

책임

하나 이상의 속성들을 묶어서 의미 있는 값으로 표현한다. 주로 클라이언트 → 서버로 요청을 보내는 형식을 정의하거나, 계층 간의 데이터 교환 시에 사용합니다.

@Builder
@Data
public class NotificationRequestDto {

    @NotBlank
    private String title;
		
	  @NotBlank
    private String subTitle;
	
	  @NotBlank
    private String content;
	  
	  @NotBlank
    private String imageUrl;

    @NotNull
    private Boolean isActive;

    public NotificationParamDto toNotificationParamDto(Integer userId) {
        return NotificationParamDto.builder()
                .title(this.title)
                .subTitle(this.subTitle)
                .content(this.content)
                .imageUrl(this.imageUrl)
                .isActive(this.isActive)
                .userId(userId)
                .build();
    }
}

VO(Value Object)

개념

VO는 Value Object의 약자로, Read-Only 속성을 가진 값 오브젝트로,  자바에서 단순히 값 타입을 표현하기 위하여 불변 클래스(Read-Only)를 만들어 사용한다. 따라서 getter기능만 존재합니다.

책임

주로 도메인 모델 내부에서 의미 있는 값을 표현할 때 사용되며 동등성 비교, 의미 부여, 값 객체 자체로의 로직 집합에 적합합니다.
VO는 원래 도메인 내부에서 불변의 값 객체로 사용되지만, 의미 있는 값을 표현한다는 특성 덕분에 서버 응답에서도 직접 사용할 수 있습니다. 다만, 이 경우 프론트엔드와의 데이터 구조 협의가 필요하며, 필요한 경우 DTO로 변환해주는 것이 더 명확하고 유연할 수 있습니다.

data class Money(val amount: Int, val currency: String)

DTO vs VO

DTO는 가변의 성격을 가진 클래스이며 데이터 전송을 위해 존재하므로 getter와 setter의 기능을 모두 가지고 있습니다.

그러나 VO는 값 그 자체 의 의미를 가진 불변 클래스(Read-Only)를 의미하므로 getter 기능만 존재합니다.

DTO는 인스턴스 개념이라면, VO는 리터럴 개념으로 이해하면 좋습니다.

 

목적 DB 접근 값 표현 데이터 전달
위치 Repository/DB Layer Domain Layer Controller ↔ Service 간
특징 DB 연산 수행 불변성, equals/hashCode 중요 유연성, 가공된 필드도 가능
예시 UserRepository Money, Address UserResponseDto, LoginRequestDto

예시 : 회원 정보 조회 API

DAO

interface UserDao : JpaRepository<UserEntity, Long> {
    fun findByEmail(email: String): UserEntity?
}

DTO

data class UserResponseDto(
    val id: Long,
    val name: String,
    val email: String
)

VO

data class Email(val value: String) {
    init {
        require(value.contains("@")) { "유효한 이메일 형식이 아닙니다." }
    }
}

Controller

@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): UserResponseDto {
    val user = userDao.findById(id).orElseThrow()
    return UserResponseDto(user.id, user.name, user.email)
}

정리하자면, DAO는 데이터 접근을 담당하며 DB와 직접 연결되는 계층입니다.
DTO는 데이터 전달을 위해 존재하며 유연성과 가공성이 장점이고, VO는 값 그 자체에 의미를 부여하는 불변 객체로써 도메인 모데을 보다 견고하게 만들어줍니다.

 

DAO DB와의 연결, 쿼리 실행 Repository, Mapper
VO 의미 있는 값을 표현 (불변) 도메인 로직, 비교 대상
DTO 데이터 전달 API 요청/응답, Controller ↔ Service
반응형