BackEnd/JDBC

[JDBC] DAO 리팩토링 1 - 개별 Template 만들기

샤아이인 2022. 3. 12.

이전 글의 코드를 리팩토링 한 과정입니다. 변경 전의 코드는 다음과 같습니다.

 

[JDBC] 순수 JDBC CRUD 코드

리팩토링 하기전의 중복 부분이 많은 코드. 이후의 글에서 점차 리팩토링 해가는 모습을 남기겠습니다. 1. DbUserRepository @Primary @Repository public class DbUserRepository implements UserRepository { p..

blogshine.tistory.com

위 링크의 순수 JDBC 코드를 개발자가 구현해야 하는 영역(변경이 많은 부분) 과 라이브러리가 담당해야 하는 부분구분하였다.

 

내가 작성하고 있는 코드는 약간 위 사진과 다르기는 한데, 여튼 중복되는 부분은 두고 개발자가 변경해야 하는 부분만 함수로 뽑았다.

@Override
public Long save(User user) {
    String SQL = createQuery();
    Connection connection = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        connection = getConnection();
        pstmt = connection.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS);
        setParameters(user, pstmt);
        pstmt.executeUpdate();

        rs = pstmt.getGeneratedKeys();
        if (rs.next()) {
            user.setId(rs.getLong(1));
            return user.getId();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        DbCleaner.close(connection, pstmt, rs, dataSource);;
    }
    return -1L;
}

private void setParameters(User user, PreparedStatement pstmt) throws SQLException {
    pstmt.setString(1, user.getUserId());
    pstmt.setString(2, user.getPassword());
    pstmt.setString(3, user.getName());
    pstmt.setString(4, user.getEmail());
}

private String createQuery() {
    return "INSERT INTO user_info (user_id, password, name, email) VALUES (?, ?, ?, ?)";
}

직전 사진에서 파란 영역에 해당하는 라이브러리 성 코드들을 Class 로 추출하자.

공통 Library에 해당하는 Class 로 만드는 것 이다

 

다음 코드는 JdbcTemplate로 추출한 것 이다.

다만 createQuery() 와 setParameters() 같은 경우 DbUserRepository에 있기 때문에 JdbcTemplate에서 DbUserRepository에 대한 의존성이 생겨버렸다.

public class JdbcTemplate {

    private final DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    public Long save(User user, DbUserRepository repository) { // 인자로 DbUserRepository를 받고있다.
        String SQL = repository.createQuery(); // DbUserRepository의 함수 사용
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            connection = getConnection();
            pstmt = connection.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS);
            repository.setParameters(user, pstmt); // DbUserRepository의 함수 사용
            pstmt.executeUpdate();

            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                user.setId(rs.getLong(1));
                return user.getId();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DbCleaner.close(connection, pstmt, rs, dataSource);;
        }
        return -1L;
    }
}

DbUserRepository의 의존성을 끊어기위해 abstract Class를 만들자.

기존에 DbUserRepository의 메서드들은 abstact method로 만들었다. 사용자가 직접 구현해야 한다.

public abstract class JdbcTemplate {

    private final DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    public Long save(User user) {
        String SQL = createQuery();
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            connection = getConnection();
            pstmt = connection.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS);
            setParameters(user, pstmt);
            pstmt.executeUpdate();

            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                user.setId(rs.getLong(1));
                return user.getId();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DbCleaner.close(connection, pstmt, rs, dataSource);;
        }
        return -1L;
    }

    public abstract void setParameters(User user, PreparedStatement pstmt) throws SQLException;
    public abstract String createQuery();
}

여기서 save의 인자로 User를 받고 있다.

1) User에 대한 의존성도 끊어봅시다!

2) 또한 save의 이름도 executeUpdate()로 이름을 변경 하였다.

3) SQL문은 함수의 인자로 전달받도록 변경하였다!

public abstract class DbTemplate {
    private final DataSource dataSource;

    public DbTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    public Long executeUpdate(String sql) {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            connection = getConnection();
            pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            setParameters(pstmt);
            pstmt.executeUpdate();

            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                return rs.getLong(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DbCleaner.close(connection, pstmt, rs, dataSource);;
        }
        return -1L;
    }

    public abstract void setParameters(PreparedStatement pstmt) throws SQLException;
}

 

결론적으로 각 메서드 별로 template method 패턴을 적용하여 중복을 제거하였습니다.

 

하지만 지금의 코드는 select, findAll, save 모두 template가 나뉘어 있다는점이 문제 입니다.

이러면 사용자는 모든 template를 적정시점에 알고 있어야 사용이 가능합니다.

 

이에 대한 해결은 다음 글에서 리팩토링할 예정입니다.

 

1. DbUserRepository

@Primary
@Repository
public class DbUserRepository implements UserRepository {
    private final DataSource dataSource;

    public DbUserRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Long save(User user) {
        DbTemplate template = new DbTemplate(dataSource) {
            @Override
            public void setParameters(PreparedStatement pstmt) throws SQLException {
                pstmt.setString(1, user.getUserId());
                pstmt.setString(2, user.getPassword());
                pstmt.setString(3, user.getName());
                pstmt.setString(4, user.getEmail());
            }
        };
        String SQL = "INSERT INTO user_info (user_id, password, name, email) VALUES (?, ?, ?, ?)";
        Long saveId = template.executeUpdate(SQL);
        user.setId(saveId);
        return saveId;
    }

    @Override
    public Optional<User> findByUserId(String userId) {
        DbSelectTemplate template = new DbSelectTemplate(dataSource) {
            @Override
            public void setParameters(PreparedStatement pstmt) throws SQLException {
                pstmt.setString(1, userId);
            }

            @Override
            public Object rowMapper(ResultSet rs) throws SQLException {
                while(rs.next()){
                    User user = new User(rs.getString("user_id"), rs.getString("password"), rs.getString("name"), rs.getString("email"));
                    user.setId(rs.getLong("id"));
                    return user;
                }
                return null;
            }
        };
        String SQL = "SELECT id, user_id, password, name, email FROM user_info WHERE user_id = (?)";
        return Optional.ofNullable((User)template.executeQuery(SQL));
    }

    @Override
    public List<User> findAll() {
        DbSelectAllTemplate template = new DbSelectAllTemplate(dataSource) {
            @Override
            public void rowMapper(ResultSet rs, List<User> list) throws SQLException {
                while(rs.next()){
                    User user = new User(rs.getString("user_id"), rs.getString("password"), rs.getString("name"), rs.getString("email"));
                    user.setId(rs.getLong("id"));
                    list.add(user);
                }
            }
        };
        String SQL = "SELECT id, user_id, password, name, email FROM user_info";
        return template.executeQuery(SQL);
    }

    @Override
    public boolean delete(String userId) {
        DbTemplate template = new DbTemplate(dataSource) {
            @Override
            public void setParameters(PreparedStatement pstmt) throws SQLException {
                pstmt.setString(1, userId);
            }
        };

        String SQL = "DELETE FROM user_info WHERE user_id = (?)";
        Long resultId = template.executeUpdate(SQL);

        if(resultId != -1){
            return true;
        }
        return false;
    }

    @Override
    public boolean update(String userId, User updateParam) {
        DbTemplate template = new DbTemplate(dataSource) {
            @Override
            public void setParameters(PreparedStatement pstmt) throws SQLException {
                pstmt.setString(1, updateParam.getUserId());
                pstmt.setString(2, updateParam.getPassword());
                pstmt.setString(3, updateParam.getName());
                pstmt.setString(4, updateParam.getEmail());
                pstmt.setString(5, updateParam.getUserId());
            }
        };

        String SQL = "UPDATE user_info SET user_id = (?), password = (?), name = (?), email = (?) WHERE user_id = (?)";
        Long resultId = template.executeUpdate(SQL);

        if(resultId != -1){
            return true;
        }
        return false;
    }
}

 

2. DbTemplate

public abstract class DbTemplate {
    private final DataSource dataSource;

    public DbTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    public Long executeUpdate(String sql) {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            connection = getConnection();
            pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            setParameters(pstmt);
            pstmt.executeUpdate();

            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                return rs.getLong(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DbCleaner.close(connection, pstmt, rs, dataSource);;
        }
        return -1L;
    }

    public abstract void setParameters(PreparedStatement pstmt) throws SQLException;
}

 

3. DbSelectTemplate

public abstract class DbSelectTemplate {

    private final DataSource dataSource;

    public DbSelectTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    public Object executeQuery(String sql) {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            connection = getConnection();
            pstmt = connection.prepareStatement(sql);
            setParameters(pstmt);
            rs = pstmt.executeQuery();

            Object value = rowMapper(rs);
            if (value != null) return value;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DbCleaner.close(connection, pstmt, rs, dataSource);
        }
        return null;
    }

    public abstract void setParameters(PreparedStatement pstmt) throws SQLException;
    public abstract Object rowMapper(ResultSet rs) throws SQLException;
}

 

4. DbSelectAllTemplate

public abstract class DbSelectAllTemplate {
    private final DataSource dataSource;

    public DbSelectAllTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    public List<Object> executeQuery(String sql) {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Object> list = null;

        try {
            connection = getConnection();
            pstmt = connection.prepareStatement(sql);
            rs = pstmt.executeQuery();

            list = rowMapper(rs);
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DbCleaner.close(connection, pstmt, rs, dataSource);
        }
        return list;
    }

    public abstract List<Object> rowMapper(ResultSet rs) throws SQLException;
}

댓글