거북이-https://velog.io/@violet_evgadn 이전완료

Spring의 데이터 처리 방법 본문

웹 개발/Spring(이론)

Spring의 데이터 처리 방법

VioletEvgadn 2022. 9. 19. 18:03

DAO

Data Access Object의 약자이다.

말 그대로 Data에 접근하는 역할을 하는 객체이며, 곧 DB에 직접적으로 CRUD 작업을 시행하는 클래스라고 말할 수 있을 것이다.

 

DAO는 클래스 내부에서 직접적으로 CRUD 과정을 실행시킬 수 있어야 한다.

Spring Data JPA에서 직접적으로 CRUD 과정을 실행하는 주체가 무엇이었는지 생각해보면 바로 "JpaRepository"였다.

엄밀하게 따지면 Repository와 DAO의 차이점은 존재하지만, 기본적으로 Spring Data JPA에서는 Repository가 DAO를 대체하기 때문에 개발자가 직접 구현할 일이 없는 데이터라고 볼 수 있다.

 

그렇다면 DAO와 Repository의 차이점이 무엇일까? 이는 DAO를 직접 구현한 클래스를 보면 바로 확인 가능하다.

import java.sql.*;

public class DaoEx {
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://localhost:3306/dao_db";
    private static final String USER = "root";
    private static final String PASSWORD = "********";

    private Connection con;
    private Statement stmt;
    private ResultSet res;

    public DaoEx() throws ClassNotFoundException {
        Class.forName(DRIVER);
    }

    public void select() {
        String sql = "SELECT * FROM vouchers";


        try {
            con = DriverManager.getConnection(URL, USER, PASSWORD);
            stmt = con.createStatement();
            res = stmt.executeQuery(sql);
            while (res.next()) {
                System.out.println(res.getString("id") + " ");
                System.out.println(res.getString("value") + " ");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    public void close(Connection con, Statement stmt, ResultSet res) throws SQLException {
        if (res != null)
            res.close();
        if (stmt != null)
            stmt.close();
        if (con != null)
            con.close();
    }
}
출처 : https://today-retrospect.tistory.com/142

위에서 보면 DAO 예시 클래스는 직접적으로 EntityManager를 불러와 로직을 실행시키는 것을 볼 수 있다. 이때 결괏값을 객체에 저장할 수도 있고, 결괏값을 반환할 수도 있을 것이다.

 

위 예시에선 나오지 않았지만 결과 값을 내부에 List나 객체로 담아 저장해 놓아서 if(A!=null) return A;처럼 활용하여 Entity의 역할을 수행할 수도 있을 것이다.

 

하지만 Repository 같은 경우 이전에 봤듯 Entity라는 DB 접속을 위한 새로운 클래스가 필요하게 된다. 또한 DB에 접속하기 위한 로직을 직접적으로 구현하고 있지는 않다.

 

즉, 간단히 정리하자면 DAO는 "Entity 클래스와 Repository 로직을 동시에 포함하고 있는" 실제 DB에 접속하여 처리하기 위한 클래스(객체)라면, Repository는 Entity 클래스를 활용하여 DB에 접속하고 데이터를 처리하는, 개발자에게 해당 Repository가 DB를 활용한다는 것을 숨겨 DB 접속에 관련된 로직 고민을 하지 않아도 되게 한다는 차이점이 존재하는 것이다.


DTO & VO

◎ 서두

사실 DAO는 실제로 많이 활용되지는 않는다.

위 코드에서 볼 수 있듯이 DAO에서는 실제 EntityManager를 통한 DB 접속 과정에 대한 코드까지 짜줘야 하는데, Spring Data JPA의 장점이 이런 DB 연결 과정을 굳이 설정하지 않아도 된다는 점이기에 장점을 굳이 DAO 활용을 위해 없애버릴 필요는 없을 것이다.

이런 면에서 DAO란 결국 "DB에 접속하여 로직을 수행하는 개체"라는 특징을 가질 뿐 Repository와 Entity를 활용하는 JPA 측에서는 큰 의미가 없는 개체라고 볼 수 있겠다.

 

하지만 DTO와 VO는 다르다.

DTO와 VO는 매우 큰 활용도가 존재하며, 둘의 차이점도 잘 알고 있어야 한다. 또한, 굳이 Entity가 아닌 DTO와 VO에 대한 클래스를 새로 만들어서 활용하는 이유도 꼭 알고 있어야 한다

 

DTO

출처 : https://velog.io/@ohzzi/Entity-DAO-DTO%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C

DTO는 계층 간 Data 교환을 하기 위한 객체이다.

DTO는 계층 간 데이터 교환을 하기 위한 객체이기 때문에 DB에 접속할 수 없으며, 로직을 가지지 않는 순수한 POJO 객체이다.

다시 한번 강조할 점은 DTO에는 로직이 존재해서는 안된다는 것이다. 따라서 DTO는 꼭 필요한 경우가 아니라면 Getter, Setter, 생성자 이외의 메서드가 웬만한 경우 추가되지 않는 것이 좋다...라는 것이 일반적인 이론이다.

 

하지만 실제 개발을 할 때 DTO에 로직을 구현하는 것이 더 쉬워지는 경우도 존재하고, 만약 View 측에서 데이터를 DTO 형태로 받아 DB에 적용시키고 싶을 경우 DTO를 VO로 변환시키는 과정이 필요할 것이다.

이 경우 Entity에 dtoToEntity 함수를 지정하는 경우도 있지만 개인적으로는 DTO에 toEntity()라는 메서드를 만드는 것이 더 보기 깔끔해져 개인적으로는 이 방법을 많이 활용한다.

 

 VO

VO는 DTO와 매우 유사한 개념이다. 단지 DTO는 Getter와 Setter를 가지고 있어 수정이 가능하지만 VO는 Getter만 가지고 있어 수정이 불가능하다는 점이 특징이다

 

그렇다면 굳이 VO와 DTO를 나눠놓을 필요가 있을까?

VO와 DTO 사이에는 목적성의 차이가 존재한다.

 

DTO는 "계층 간 데이터의 전송"을 주목적으로 한다.

즉 HTML에서 Controller, Service 사이에서 데이터를 주고받을 때 DTO를 활용함으로써 전달되는 데이터를 보존하는데 더욱 많은 신경을 쓴다.

하지만 VO의 특징은 "Read-Only"이기 때문에 필드의 값들이 변경될 수 없기 때문에 데이터 자체에 의미를 두는 객체인 것이다. 따라서 VO는 equals()와 hashCode()를 오버라이딩하여 두 객체의 모든 필드 값들이 같으면 같은 객체임을 알리는 것이 매우 중요하다.

 

VO는 getter 이외에도 여러 가지 메서드를 가질 수 있다.(Setter처럼 VO에 저장되는 값을 직접 변경시키는 로직만 아니라면)

 

하지만, 개인적으로는 VO를 굳이 활용해야 할까 의문이긴 하다. 결국 Spring Data JPA를 활용할 시 Entity끼리의 일치 여부는 식별자 필드 값의 비교로써만 수행 가능할 것이고, 계층 간 데이터를 전달하기 위해선 VO가 아닌 DTO를 활용해야 할 것이기 때문에 DTO -> VO -> DTO -> VO -> Entity라는 엄청나게 복잡한 과정을 거쳐야 할 것이다.

그렇다고 VO를 활용하지 않을 시 보안 상 문제가 생기는 것은 아니기 때문에 나는 DTO에 최소한의 로직을 추가시키는 방식으로 VO와 DTO를 동시에 활용하지 않고 DTO만 활용하며 개발을 수행할 것 같다.

 

 Entity와 DTO를 분리하는 이유

그렇다면 Entity와 DTO를 왜 굳이 분리할까? 어차피 DTO와 Entity를 서로 변환해가며 활용해야 한다면 그냥 처음부터 Entity로만 활용하면 되지 않을까?

 

둘을 분리하는 이유는 "DB와 View 사이의 역할 분리"를 목적으로 한다.

조금 더 자세히 설명해보자.

 

Entity의 목적은 "DB와의 통신"을 위한 객체이고, DTO의 목적은 "Layer 간의 통신"을 위한 객체이다.

이렇게 목적이 다른 두 객체는 당연히 활용 방식이 달라질 수밖에 없다.

 

먼저, DTO는 한 번 사용되고 사라지는 휘발성 객체이다. 이 DTO는 Layer를 뛰어넘어 데이터를 사용하는 순간 자신의 역할을 모두 수행한 것이다. 따라서 일회성으로 활용되는 성격이 강하다.

하지만 Entity는 DB와 통신하는 객체이며 데이터를 담고 있기에 영속성이라는 특성이 강하다. 즉 Transaction이 종료되기 전까지는 데이터를 계속 보존하고 있어야 하며 일회성이라기보다는 트랜잭션이 수행되는 순간 계속 살아 있어 데이터를 처리하는 재활용 성격이 강해야 한다.

 

두 번째로 관리하는 주체에도 차이가 존재한다.

DTO는 POJO 객체이므로 GC나 Spring 쪽의 관리를 받는다. 따라서 DTO는 데이터를 주고받은 뒤 사용성이 끝나면 GC에 의해 처리된다.

하지만 Entity는 DB와 연동되는 객체이므로 EntityManager의 관리를 받는다. 따라서 이 EntityManager가 Entity 객체의 수명(Life Cycle)을 정하게 된다.

이렇게 관리 주체가 다르고 Life Cycle도 다르기 때문에 동시에 활용하여 관리 주체를 모호하게 하는 것보다는 분리하여 관리 주체를 확실하게 하고 객체의 Life Cycle을 조금 더 명확하게 하는 것이 재사용성 및 유지보수성을 고려했을 때 뛰어날 것이다.

 

세 번째로 Table에 Mapping 되는 정보와 실제 View에서 전달받은 정보가 다를 경우 Table에 필요한 형태로 데이터를 변환해줄 필요가 있다.

이때 @Transient를 활용해서 Entity에 특수한 객체를 만드는 것보다는 DTO에서 정보를 받아 메서드를 통해 View에서 받은 정보를 Table에 Mapping 될 정보로 변환하여 Entity를 만드는 것이 깔끔한 코딩이 될 수 있다.

 

가장 중요한 점은 바로 보안 문제이다.

Entity로 데이터를 주고받을 경우 (Entity를 용도별로 여러 개 만들 것이 아니라면) Table에 저장된 모든 데이터를 가지고 온다. 그런데 만약 Entity 데이터를 Layer마다 전송하다 View 단까지 데이터를 보낸다면, 일반 User에게 보여주면 안 되는 정보를 User가 확인할 수도 있다는 점이다.

 

예를 들어보자.

이전에 우리는 Super Type과 Sub Type을 구분하기 위해서 DTYPE이라는 Column을 만들고, 여기에 구분 문자를 넣어서 데이터를 추출함을 알 수 있었다

그런데 이 구분 문자는 굳이 User가 몰라도 되는 문자이며, 사실 몰라야 한다. 예를 들어 Starcraft의 구분자가 "S"이고 LOL의 구분자가 "L"인데 이를 User가 알 경우, 만약 Update 로직을 수행하는 View단에서 Starcraft 정보에 구분자만 "L"로 바꿔서 쿼리를 넘길 경우 LOL 정보가 저장되어야 하는 Table에 Starcraft 정보가 저장되어 심각할 경우 서버에 심각한 에러를 가지고 올 수 있다.

따라서 유저는 이 구분자 값을 몰라야 하는데, Entity를 활용하다 보면 이 구분자 정보도 User단에 전달이 될 것이다.

물론 개발자는 해당 Entity를 안 보여주려고 노력하겠지만, DTYPE처럼 Default Column 이름이 너무 유명한 것은 공격자가 쉽게 정보를 탈취할 수 있을 것이다.

 

따라서 Table 전체와 연동되는 Entity는 1개만 만들고, 용도나 데이터를 보여줄 대상에 따라 DTO를 여러 개 만들어줌으로써 User에게 너무 많은 정보를 보내지 않도록 만드는 것이다.

'웹 개발 > Spring(이론)' 카테고리의 다른 글

Spring Web 계층  (0) 2022.09.19
DTO 클래스  (0) 2022.09.19
AOP  (0) 2022.08.04
IoC와 DI  (0) 2022.08.03
스프링 컨테이너와 스프링 빈  (0) 2022.08.03
Comments