Study/Java

[Java] Entity, DTO, VO 개념과 차이

jonghne 2023. 10. 31. 17:32

Entity 

Entity는 실제 DB 테이블과 매핑되는 클래스로, 테이블의 ID값을 기준으로 객체를 구분하고 비즈니스 로직을 포함한다.

 

실제 테이블과 매핑되기 때문에 테이블 컬럼이 추가되거나 변경될때마다, Entity 클래스 또한 변경된다. 

그렇기 때문에 Entity 클래스에는 가급적 Setter를 열어두지 말고, 생성자 메소드를 따로 생성하는 전략을 사용하는 것이 좋다. (Setter로 객체를 생성하게 하면 필드가 변경될 때 마다 Setter를 사용하는 곳을 수정해야 한다.)

또한, Entity 클래스에는 테이블의 민감한 정보를 다루는 필드도 포함되어 있기 때문에 (ex) password )

API 요청값 / 응답값에 Entity 객체를 사용하지 않는 것이 좋다.

다음은 User Entity의 예시 코드이다

import javax.persistence.*;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String username;
    private String password;
    private String email;
}

 

DTO (Data Transfer Object)

DTO는 계층 간 데이터를 전달하기 위한 객체로, 비즈니스 로직을 포함하지 않고 Getter / Setter 메서드만 존재한다.

public class UserDto {
    
    private String name;
    private String id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
    
}

 

만약 데이터가 전달 되는 과정에서 변경되지 않아야 하는 경우 Setter 대신 생성자 또는 편의 메서드를 사용해서 불변 객체로 활용할 수 있다.

public class UserDto {
    
    private final String name;
    private final String id;
    
    // 생성자를 통한 값 세팅
    public UserDto(String name, String id) {
    	this.name = name;
        this.id = id;
    }
   
    public String getName() {
        return name;
    }

    public String getId() {
        return id;
    }    
}

 

API 스펙을 설계할 때 요청/응답값으로 Entity 객체가 아닌 이 DTO 객체를 사용하는 것이 좋은데, 이유는 다음과 같다.

 

 

1. 테이블 컬럼 중 화면단에서 필요한 데이터만 DTO 객체로 관리 가능하다. 

2. Entity의 민감한 필드를 감출 수 있다. 

3. 테이블 컬럼 추가 등으로 Entity 클래스의 수정이 있어도, API 스펙이 변경되지 않는다.

 

 

VO (Value Object)

VO(Value Object)는 값 자체를 표현하는 객체로 한번 생성 된 이후에는 값이 변경되지 않는 불변 객체이다. 

 

VO 객체는 DTO와 다르게 비즈니스 로직을 포함할 수 있다는 특징을 있고 불변성을 보장하기 위해 final 키워드를 사용한다.

 

이 VO 객체는 ID 값이나 객체의 주소값으로 동일성을 판단하는 것이 아니라, 속성 값이 모두 같으면 동일한 객체로 판단한다는 특징을 가진다. 이러한 특징으로 인해 VO 객체를 사용하려면 equals()와 hashCode()를 재정의 해야 한다.

 

다음은 rgb 값을 멤버 변수로 가지는 ColorVO 클래스의 예제 코드로, rgb값이 같으면 서로 다른 2개의 인스턴스여도 같게 equals()와 hashCode()를 재정의 했다.

public class ColorVO {
    private final Integer red;
    private final Integer green;
    private final Integer blue;
    
    public ColorVO(Integer red, Integer green, Integer blue) {
        this.red = red;
        this.green = green;
        this.blue = blue;
    }

    public Integer getRed() {
        return red;
    }

    public Integer getGreen() {
        return green;
    }

    public Integer getBlue() {
        return blue;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ColorVO colorVO = (ColorVO) o;
        return Objects.equals(getRed(), colorVO.getRed()) && Objects.equals(getGreen(), colorVO.getGreen()) && Objects.equals(getBlue(), colorVO.getBlue());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getRed(), getGreen(), getBlue());
    }
}

 

아래의 테스트의 결과와 같이 서로 다른 주소를 가지는 2개의 ColorVO 객체를 eqauls() 비교했을 때 true를 반환하는 것을 확인할 수 있다.

class ColorVOTest {

    @DisplayName("RGB가 같은 ColorVO는 같은 객체이다.")
    @Test
    void ColorVoTest() {
        // given
        ColorVO colorA = new ColorVO(100, 255, 255);
        ColorVO colorB = new ColorVO(100, 255, 255);

        // when / then
        Assertions.assertThat(colorA.equals(colorB)).isTrue();
        Assertions.assertThat(Objects.equals(System.identityHashCode(colorA),System.identityHashCode(colorB)))
                        .isFalse();
    }
}

 

 

 

Entity vs DTO vs VO 

  Entity DTO VO
목적 DB 테이블 매핑 계층 간 데이터 운반 값 표현
비즈니스 로직 포함 여부 포함할 수 있다. 포함할 수 없다 포함할 수 있다.
불변 객체 여부 가변 또는 불변 객체 불변 또는 가변 객체 불변 객체