PengTory

[Spring] 오브젝트와 의존관계 (1) _ DAO의 분리 본문

Spring

[Spring] 오브젝트와 의존관계 (1) _ DAO의 분리

펭토리 2022. 10. 12. 00:12

스프링이란?

스프링 프레임워크는 자바 플랫폼을 위한 오픈소스 애플리케이션 프레임워크이다.

스프링이 자바에서 가장 중요하게 가치를 두는 것은 바로 객체지향 프로그래밍이다.

 

DAO

DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.

사용자 정보를 JDBC API를 통해 DB에 저장하고 조회할 수 있는 간단한 DAO 를 하나 만들어보자.

 

JDBC를 이용하는 작업의 일반적 순서는 다음과 같다.

1) DB 연결을 위한 Connection을 가져온다.

2) SQL을 담은 Statement를 만든다.

3) 만들어진 Statement를 실행한다.

4) 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.

5) 작업중 생성된 Connection,n Statement, ResultSet같은 리소스는 작업을 마친 후 반드시 닫아준다.

6) JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해 예외가 발생하면 메소드 밖으로 던지게한다

 

위에 적힌 방법을 참고해 사용자 정보를 관리하는 DAO이기 때문에 UserDao라는 클래스를 하나 만들어보았다.

import org.example.users.entity.User;

import java.sql.*;

public class UserDao {
    public void add(User user) throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/springbook","spring","book");
        
        PreparedStatement ps = c.prepareStatement(
                "insert into user(id, name, password) values (?,?,?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());
        
        ps.executeUpdate();
        
        ps.close();
        c.close();
    }
    
    public User get(String id) throws ClassNotFoundException,SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/springbook","spring","book");

        PreparedStatement ps = c.prepareStatement(
                "select * from users where id =?");
        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));
        
        rs.close();
        ps.close();
        c.close();
        
        return user;
    }
}

main()을 이용해 테스트코드로 돌려보면 정상적으로 작동하는 것을 알 수 있다.

그렇지만 위의 UserDAO의 구현된 메소드를 살펴보면 하나의 메소드에서 너무 많은 일이 일어나고 있는 것을 발견할 수 있다.

지금부터 위 코드를 객체지향에 맞게 수정해보겠다.

 

DAO의 분리

1) 관심사의 분리

개발자는 객체를 설계할 때 미래의 변화에 어떻게 대비할지를 염두해야한다. 객체지향은 변화에 효과적으로 대처할 수 있다는 기술적 특징을 가지고 있다. 따라서 이러한 특성을 이용해 가상의 추상세계를 자유롭고 편리하게 변경, 발전 확장시킬 수 있다.

 

관심사의 분리를 객체지향에 적용해보자.

관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리해야한다.

 

다음과 같은 개념을 알고 위의 UserDAO를 보면 add()메소드 하나에서만 적어도 세가지의 관심사항을 발견할 수 있다. 

첫째: DB와 연결을 위한 커넥션을 어떻게 가져올 것인가

둘째: 사용자 등록을 위해 DB에 보낼 SQL문자을 담을 Statement를 만들고 실행

셋째: 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아주기

 

커넥션을 가져오는 중복된 코드를 분리해보자.

    private Connection getConnection() throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost/springbook","spring","book");
        
        return c;
    }

다음과 같이 메소드를 추출하면 관심이 집중되는 부분의 코드만 수정할 수 있다.

관심이 다른 코드가 있는 메소드에는 영향을 주지 않고 관심 내용이 독립적으로 존재하므로 수정도 간단해진다.

공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는 메소드 추출 기법이라고 부른다.

 

2) DB 커넥션 만들기의 독립 (상속을 통한 확장)

상속을 사용하면 UserDao의 코드를 공개하지 않고 DB커넥션 생성 방식을 수정할 수 있다.

UserDao에서 메소드의 구현 코드를 제거하고 getConnection()을 추상 메소드로 만들어놓는다. 추상메소드라서 메소드 코드는 없지만 메소드 자체는 존재한다. 따라서 add(), get()메소드에서 getConnection()을 호출하는 코드는 그대로 유지할 수 있다. 

 

UserDao에 아래와 같이 코드를 수정해 구현 코드는 제거되고 추상메소드로 바꾼다. 메소드의 구현은 서브클래스가 담당한다.

public abstract Connection getConnection() throws ClassNotFoundException,SQLException;

상속을 통해 확장된 getConnection()메소드로 새로운 DB연결 방법을 적용해야 할때 다음과 같이 사용할 수 있다.

public class NUserDao extends UserDao {
    public Connection getConnection() throws ClassNotFoundException, SQLException{
        // DB 생성 코드
    }
}

 

 이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴 이라고 한다.

서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것은 팩토리 메소드 패턴이라 부른다.

 

이처럼 탬플릿 메소드 패턴과 팩토리 메소드 패턴으로 관심사항이 다른 코드를 분리해내고 서로 독립적으로 변경 또는 확장해 장점만 있는 것 처럼 보인다. 하지만 이 방법은 상속을 사용했다는 단점이 있다.

상속은 편리하고 간단해보이지만 여러 한계점이 있다. 대표적으로 자바는 클래스의 다중 상속을 허용하지 않는다. 또한 상속을 통한 상하위 클래스의 관계는 생각보다 밀접하다.

결국 상속틀 통해 관심사와 기능을 분리했지만 여전히 두 가지 다른 관심사에 대해 긴밀한 결합을 허용한 것이다.

또한 위의 코드처럼 사용하게 되면 확장된 기능인 DB 커넥션을 생성하는 코드를 다른 DAO 클래스에는 적용할 수 없다. 새로운 DAO가 생성될 때마다 또 다시 중복되는 코드를 작성해야하는 것이다.

 

다음 포스팅에서 위 문제를 해결할 수 있는 DAO의 확장에 대해 정리할 예정이다.

 

 

[참고자료] 토비의 스프링 3.1 Vol.1 스프링의 이해와 원리