다음과 같이 하나의 Template 안에 기존의 코드들을 전부 모아두었다.
기존 코드는 직전 글에서 설명했었다.
1. 하나의 Template 안에 모인 코드들
- 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 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 List<Object> findAllQuery(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 = rowsMapper(rs);
return list;
} catch (SQLException e) {
e.printStackTrace();
}finally {
DbCleaner.close(connection, pstmt, rs, dataSource);
}
return list;
}
public abstract void setParameters(PreparedStatement pstmt) throws SQLException;
public abstract Object rowMapper(ResultSet rs) throws SQLException;
public abstract List<Object> rowsMapper(ResultSet rs) throws SQLException;
}
하지만 이렇게 되면 모든 경우에 3가지 abstract 메서드를 항상 구현해주어야 한다는 단점이 발생한다.
다음 코드를 살펴보면 abstract 메서드 들을 전부 구현하고 있는것를 볼 수 있다.
심지어 필요도 없는데 말이다! 따라서 Repository 코드는 다음과 같아진다.
- 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());
}
@Override
public Object rowMapper(ResultSet rs) throws SQLException {
return null;
}
@Override
public List<Object> rowsMapper(ResultSet rs) throws SQLException {
return null;
}
};
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) {
DbTemplate template = new DbTemplate(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;
}
@Override
public List<Object> rowsMapper(ResultSet rs) throws SQLException {
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 List<Object> rowMapper(ResultSet rs) throws SQLException {
List<Object> list = new ArrayList<>();
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);
}
return list;
}
};
String SQL = "SELECT id, user_id, password, name, email FROM user_info";
return template.executeQuery(SQL).stream()
.map(o -> (User)o)
.collect(Collectors.toList());
}
@Override
public boolean delete(String userId) {
DbTemplate template = new DbTemplate(dataSource) {
@Override
public void setParameters(PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, userId);
}
@Override
public Object rowMapper(ResultSet rs) throws SQLException {
return null;
}
@Override
public List<Object> rowsMapper(ResultSet rs) throws SQLException {
return null;
}
};
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());
}
@Override
public Object rowMapper(ResultSet rs) throws SQLException {
return null;
}
@Override
public List<Object> rowsMapper(ResultSet rs) throws SQLException {
return null;
}
};
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. 메서드를 인터페이스로 분리하기
우선 추출할 메서드의 인터페이스를 다음과 같이 만들자.
나같은 경우 select, update, findAll 기능에 해당되는 인터페이스를 만들어 주었다.
- PreparedStatementSetter
public interface PreparedStatementSetter {
void setParameters(PreparedStatement pstmt) throws SQLException;
}
- RowMapper
public interface RowMapper<T> {
T rowMapper(ResultSet rs) throws SQLException;
}
- RowsMapper
public interface RowsMapper<T> {
List<T> rowsMapper(ResultSet rs) throws SQLException;
}
이제 위 3개의 인터페이스를 사용하는 DbTemplate 의 변경된 코드는 다음과 같다.
- 변경된 DbTemplate
public 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, PreparedStatementSetter pss) {
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
connection = getConnection();
pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pss.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 <T> T executeQuery(String sql, RowMapper<T> mapper, PreparedStatementSetter pss) {
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
connection = getConnection();
pstmt = connection.prepareStatement(sql);
pss.setParameters(pstmt);
rs = pstmt.executeQuery();
T value = mapper.rowMapper(rs);
if (value != null) return value;
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbCleaner.close(connection, pstmt, rs, dataSource);
}
return null;
}
public <T> List<T> executeQuery(String sql, RowsMapper<T> mapper) {
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<T> list = null;
try {
connection = getConnection();
pstmt = connection.prepareStatement(sql);
rs = pstmt.executeQuery();
list = mapper.rowsMapper(rs);
return list;
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbCleaner.close(connection, pstmt, rs, dataSource);
}
return list;
}
}
기존의 abtract 메서드가 전부 사라졌다. 따라서 Class 에서도 abstract 를 제거해 주었다.
위 template는 더이상 자식 class에서 메서드를 구현하여 사용하지 않는다.
변경 된 후의 코드는 인터페이스를 통해 외부로 부터 함수 자체를 전달받아 사용한다.
인터페이스 분리 전의 코드는 필요없는 abstract 메서드 전부를 구현해야 했지만, 변경된 template는 필요한 메서드만 함수의 인자로 전달받아서 사용하면 된다.
- 변경된 DbUserRepository
대표적으로 findByUserId 메서드를 살펴보자.
메서드 내부에서 Interface를 익명 class로 바로 구현하여 이를 template.executeQuery()의 인자로 전달하고 있다.
다음 코드를 살펴보자.
public Optional<User> findByUserId(String userId) {
PreparedStatementSetter pss = new PreparedStatementSetter() {
@Override
public void setParameters(PreparedStatement pstmt) throws SQLException {
pstmt.setString(1, userId);
}
};
RowMapper<User> mapper = new RowMapper<User>() {
@Override
public User 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;
}
};
DbTemplate template = new DbTemplate(dataSource);
String SQL = "SELECT id, user_id, password, name, email FROM user_info WHERE user_id = (?)";
return Optional.ofNullable(template.executeQuery(SQL, mapper, pss)); // 함수를 전달
}
나머지 코드들도 위와 거의 유사하다!!
'BackEnd > JDBC' 카테고리의 다른 글
[JDBC] DAO 리팩토링 3 - DbTemplate 리팩토링 (0) | 2022.03.13 |
---|---|
[JDBC] DAO 리팩토링 1 - 개별 Template 만들기 (0) | 2022.03.12 |
[JDBC] 순수 JDBC CRUD 코드 (0) | 2022.03.12 |
[JDBC] PrepareStatement에서 TimeStamp, LocalDateTime 사용하기 (0) | 2022.03.06 |
[JDBC] INSERT에 대한 자동 생성 키 값 검색하기 (0) | 2022.03.04 |
댓글