Spring MVC 프로젝트에서 Entity와 DTO(Data Transfer Object)를 분리하여 사용하는 이유를 학습하였다. 이전 JDBC 프로젝트에서는 Repository가 반환한 Entity를 그대로 사용하였지만, 이번 프로젝트에서는 사용 목적에 따라 AccountFormDTO, AccountUpdateDTO, AccountViewDTO를 각각 따로 만들어 사용하였다.
사용자의 입력을 받는 객체와 화면에 보여주는 객체, 그리고 실제 데이터베이스에 저장되는 객체는 서로 역할이 다르다는 점을 이해하게 되었다.
또한 Java Record를 이용하여 DTO를 작성하면서 불필요한 코드를 줄이고, Builder Pattern을 이용해 Entity를 생성하는 방법도 함께 익힐 수 있었다.
| 학습 내용 | 상세 내용 |
|---|---|
| Entity | 데이터베이스 테이블과 직접 연결되는 객체이다. |
| @Builder | 객체 생성 시 필요한 값만 선택하여 생성할 수 있도록 도와주는 Lombok 기능이다. |
| @Getter / @Setter | 필드에 직접 접근하지 않고 메서드를 통해 값을 읽고 수정한다. |
| 객체지향 캡슐화 | 데이터를 객체 내부에서 관리하도록 하여 유지보수성을 높인다. |
| DB와 매핑 | Account 객체 하나가 accounts 테이블의 한 행(Row)을 의미한다. |
@Getter
@Setter
@Builder
public class Account {
private long accountId;
private String accountName;
private String createdDate;
}
Account Entity는 데이터베이스의 accounts 테이블과 대응되는 객체이다.
예를 들어 데이터베이스에 다음과 같은 데이터가 있다고 가정해 보자.
| id | name | created_at |
|---|---|---|
| 1 | 홍길동 | 2026-07-20 |
Repository가 이 데이터를 조회하면 다음과 같은 객체가 만들어진다.
Account account = Account.builder()
.id(1L)
.name("홍길동")
.createdAt("2026-07-20")
.build();
Entity는 데이터베이스의 실제 데이터를 표현하는 객체이기 때문에 CRUD 과정에서 가장 많이 사용된다.
이번 프로젝트에서는 Lombok의 @Builder를 활용하여 객체를 생성하였다.
Builder Pattern을 사용하면 생성자의 매개변수 순서를 기억할 필요가 없으며, 필요한 값만 선택적으로 설정할 수 있어 코드의 가독성이 크게 향상된다.
Database
↓
Account(Entity)
↓
Repository
↓
Service
Entity는 항상 Repository와 Service 사이에서 실제 데이터를 전달하는 역할을 수행한다.
| 일반 생성자 | Builder Pattern |
|---|---|
| 매개변수 순서를 기억해야 함 | 이름으로 값을 지정할 수 있음 |
| 가독성이 떨어질 수 있음 | 읽기 쉽고 유지보수가 편리함 |
| 일부 값만 설정하기 어려움 | 필요한 값만 설정 가능 |
| 느낀 점 | 내용 |
|---|---|
| Entity의 역할 | 단순한 객체가 아니라 데이터베이스의 한 행(Row)을 표현하는 중요한 객체라는 점을 이해하였다. |
| Builder Pattern | 생성자보다 Builder를 사용하는 것이 훨씬 읽기 쉽고 유지보수하기 편리하다는 것을 느꼈다. |
| 객체지향 설계 | 데이터를 객체로 관리하는 것이 코드의 안정성과 가독성을 높여준다는 점을 배웠다. |
| 학습 내용 | 상세 내용 |
|---|---|
| DTO(Data Transfer Object) | 계층 간 데이터를 전달하기 위한 객체이다. |
| FormDTO | 사용자가 입력한 데이터를 저장하는 객체이다. |
| UpdateDTO | 수정 시 필요한 데이터(id, 변경 값)를 전달하는 객체이다. |
| Record | DTO를 간결하게 작성하기 위해 사용하였다. |
| 데이터 검증 | DTO를 이용하면 입력값 검증과 데이터 전달을 분리할 수 있다. |
public record AccountCreateDTO(
String accountName
) {
}
public record AccountModifyDTO(
long accountId,
String accountName
) {
}
DTO를 하나만 사용하지 않고 용도에 따라 분리하였다.
계좌를 생성할 때는 사용자가 입력하는 값이 이름뿐이다.
이름 입력
↓
AccountFormDTO
따라서 id는 필요하지 않다.
수정은 다르다.
어떤 계좌를 수정할지 알아야 하기 때문에
id
+
변경할 이름
두 정보가 모두 필요하다.
그래서
AccountUpdateDTO
를 별도로 만들었다.
이처럼 작업 목적에 따라 DTO를 분리하면 필요한 데이터만 전달할 수 있어 코드가 더욱 명확해진다.
| DTO | 사용하는 상황 |
|---|---|
| FormDTO | 생성(Create) |
| UpdateDTO | 수정(Update) |
| ViewDTO | 조회(Read) |
각 DTO는 하나의 목적만 수행하도록 설계하였다.
DTO는 데이터를 저장하고 전달하는 역할만 수행한다.
따라서
이 자동 생성되는 Record가 매우 적합하다.
코드도 훨씬 간결해진다.
실무에서도 요청(Request)마다 DTO를 분리하는 경우가 많다.
예를 들어 회원 기능이라면 다음과 같이 구성할 수 있다.
하나의 DTO를 재사용하기보다 기능별로 DTO를 분리하여 관리하는 것이 일반적이다.
| 느낀 점 | 내용 |
|---|---|
| DTO 분리 | 같은 Account라도 용도에 따라 다른 DTO를 사용하는 이유를 이해하였다. |
| Record 활용 | DTO 작성 시 코드가 훨씬 간결해지고 가독성이 좋아졌다. |
| 유지보수 | 목적에 맞게 DTO를 분리하면 변경 범위가 줄어든다는 점을 배웠다. |
| 학습 내용 | 상세 내용 |
|---|---|
| ViewDTO | 화면에 보여줄 데이터를 전달하는 DTO이다. |
| fromEntity() | Entity를 ViewDTO로 변환하는 정적 메서드이다. |
| Entity 보호 | Entity를 View에 직접 노출하지 않도록 한다. |
| EL(Expression Language) | JSP에서 객체의 값을 출력하기 위한 표현식이다. |
public static AccountViewDTO fromEntity(Account entity){
return new AccountViewDTO(
entity.getId(),
entity.getName(),
entity.getCreatedAt()
);
}
Service에서는 Repository가 반환한 Entity를 그대로 JSP에 전달하지 않는다.
대신
Entity
↓
ViewDTO
↓
JSP
순서로 변환한다.
이 과정을 담당하는 메서드가
fromEntity()
이다.
즉,
Account
객체를
AccountViewDTO
로 변환하는 역할을 수행한다.
예를 들어 Entity에 다음과 같은 정보가 추가되었다고 가정해 보자.
이 객체를 그대로 View에 전달하면 민감한 정보가 노출될 수 있다.
하지만 ViewDTO에는 필요한 데이터(id, name, createdAt)만 담기므로 불필요한 정보의 노출을 막을 수 있다.
Database
↓
Entity
↓
Repository
↓
Service
↓
ViewDTO
↓
Controller
↓
JSP
| 느낀 점 | 내용 |
|---|---|
| DTO의 중요성 | Entity와 화면을 분리하면 보안과 유지보수성이 모두 향상된다는 점을 이해하였다. |
| fromEntity() | 객체를 다른 객체로 변환하는 패턴을 익힐 수 있었다. |
| MVC 구조 | Controller와 View 사이에서도 DTO를 사용하는 이유를 명확하게 이해하게 되었다. |