Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDBC 라이브러리 구현하기 - 2단계] 채채(신채원) 미션 제출합니다. #467

Merged
merged 6 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;

public class UserDao {

Expand Down Expand Up @@ -42,12 +43,12 @@ public List<User> findAll() {
return jdbcTemplate.queryForList(sql, USER_MAPPER);
}

public User findById(final Long id) {
public Optional<User> findById(final Long id) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app단의 코드까지 신경써주셨네요 👍

final String sql = "select id, account, password, email from users where id = ?";
return jdbcTemplate.queryForObject(sql, USER_MAPPER, id);
}

public User findByAccount(final String account) {
public Optional<User> findByAccount(final String account) {
final String sql = "select id, account, password, email from users where account = ?";
return jdbcTemplate.queryForObject(sql, USER_MAPPER, account);
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
}

public User findById(final long id) {
return userDao.findById(id);
return userDao.findById(id)
.orElseThrow(() -> new RuntimeException("존재하지 않는 회원입니다."));
}

public void insert(final User user) {
Expand Down
83 changes: 36 additions & 47 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,72 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcTemplate {

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);

private final DataSource dataSource;
private final StatementExecutor statementExecutor;

public JdbcTemplate(final DataSource dataSource) {
this(dataSource, new StatementExecutor());
}

public JdbcTemplate(final DataSource dataSource,
final StatementExecutor statementExecutor) {
this.dataSource = dataSource;
this.statementExecutor = statementExecutor;
}

public void update(final String sql,
final Object... parameters) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement preparedStatement = getPreparedStatement(sql, connection, parameters)) {
log.debug("query : {}", sql);
for (int i = 0; i < parameters.length; i++) {
preparedStatement.setObject(i + 1, parameters[i]);
}
preparedStatement.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
public void update(final String sql, final Object... parameters) {
query(sql, PreparedStatement::executeUpdate, parameters);
}

public <T> Optional<T> queryForObject(final String sql, final ResultSetMapper<T> rowMapper, final Object... parameters) {
final List<T> results = query(sql, statement -> statementExecutor.execute(statement, rowMapper), parameters);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

채채가 작성해주신 queryForObject 에는 결과의 수를 검증하는 부분이 있으니, 이 메서드에서 queryForList를 재사용해서 중복을 줄여도 괜찮을 것 같다는 생각이 드네요.

if (results.size() > 1) {
throw new DataAccessException("2개 이상의 결과를 반환할 수 없습니다.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유저에게 상세한 unchckedException으로 변환해 던져 주셨네요 👍

그런데 JDBC Template 같은 라이브러리의 코드를 보면 메서드들이 UnchckedException 인데도 불구하고 이러런식으로 메서드 시그니처에 throws 가 붙어있는 것을 확인할 수 있습니다.
image

저도 왜 굳이 던져주는지 궁금해서 찾아봤는데 스택 오버플로우 에서 이런 의견이 있더라고요.

The (ostensible) reason for declaring an unchecked exception in a throws clause is to document the fact that the exception may be thrown by the method. This will translate into text in the method's javadocs that the programmer can (should) read to understand the method and how to use it.

대충 명시적으로 사용자가 어떤 예외가 던져지는지 알게 하고, 처리를 어떻게 할 것인지는 자유에 맡긴다는 뜻 같아요!
채채는 혹시 이와 같이 명시적으로 unchckedException을 던져주는 것에 대해 어떻게 생각하시나요?

}
if (results.isEmpty()) {
return Optional.empty();
}

return Optional.ofNullable(results.iterator().next());
Comment on lines +40 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에서 Empty를 검증하는데도Optional.of가 아닌 Optional.ofNullable 로 반환해주셨네요
혹시 어떤 이유일까요!?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional은 항상 ofNullable를 사용하였어서 습관처럼 사용하였네요 말씀처럼 위에서 체크를 해주었기 때문에 of로 하는 것이 더 좋을 것 같습니다!

}

public <T> T queryForObject(final String sql,
final ResultSetMapper<T> rowMapper,
final Object... parameters) {
public <T> List<T> queryForList(final String sql, final ResultSetMapper<T> rowMapper, final Object... parameters) {
return query(sql, statement -> statementExecutor.execute(statement, rowMapper), parameters);
}

private <T> T query(final String sql,
final PreparedStatementCallback<T> preparedStatementCallback,
final Object... parameters) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement preparedStatement = getPreparedStatement(sql, connection, parameters);
final ResultSet resultSet = preparedStatement.executeQuery()) {
log.debug("query : {}", sql);
if (resultSet.next()) {
return rowMapper.apply(resultSet);
}
return null;
} catch (SQLException e) {
final PreparedStatement preparedStatement = statementCreate(connection, sql, parameters)) {
return preparedStatementCallback.execute(preparedStatement);
} catch (final SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
throw new DataAccessException(e);
}
}

private PreparedStatement getPreparedStatement(final String sql,
final Connection connection,
final Object[] parameters) throws SQLException {
private PreparedStatement statementCreate(final Connection connection,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 createStatement 처럼 동사가 앞에 오는게 조금 더 자연스러울 것 같네요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

꼼꼼하시군여

final String sql,
final Object[] parameters) throws SQLException {
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < parameters.length; i++) {
preparedStatement.setObject(i + 1, parameters[i]);
}
return preparedStatement;
}

public <T> List<T> queryForList(final String sql,
final ResultSetMapper<T> rowMapper,
final Object... parameters) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement preparedStatement = getPreparedStatement(sql, connection, parameters);
final ResultSet resultSet = preparedStatement.executeQuery()) {
log.debug("query : {}", sql);
final List<T> result = new ArrayList<>();
while (resultSet.next()) {
result.add(rowMapper.apply(resultSet));
}
return result;
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.springframework.jdbc.core;

import java.sql.PreparedStatement;
import java.sql.SQLException;

@FunctionalInterface
public interface PreparedStatementCallback<T> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

템플릿 콜백 패턴으로 중복 코드 제거해주셨군요 👍

T execute(final PreparedStatement preparedStatement) throws SQLException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.springframework.jdbc.core;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class StatementExecutor {

public <T> List<T> execute(final PreparedStatement preparedStatement,
final ResultSetMapper<T> rowMapper) throws SQLException {
final ResultSet resultSet = preparedStatement.executeQuery();
final List<T> results = new ArrayList<>();
while (resultSet.next()) {
results.add(rowMapper.apply(resultSet));
}
return results;
}
}
Loading