-
Notifications
You must be signed in to change notification settings - Fork 300
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단계] 도이(유도영) 미션 제출합니다. #421
Changes from all commits
0b21b4c
1d72705
527f3f8
cd532d9
0dc122f
d45188f
f93fd1b
36fa3f7
100f07f
00e9d96
73b9b2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,8 @@ | |
<root level="INFO"> | ||
<appender-ref ref="STDOUT" /> | ||
</root> | ||
|
||
<logger name="com.zaxxer.hikari" level="DEBUG"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logback 설정 좋네요! 혹시 로그 설정으로 어떤 정보를 얻으셨을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
해당 로그 설정으로, app 모듈의 Application을 실행했을 때 위 이미지와 같이 총 10개의 Connection이 생성되는 것을 확인할 수 있었어요. (그런데 왜 각 로그가 두번씩 출력되는지는 모르겠어요..)
그리고 주기적으로 아래와 같은 로그가 출력되는데, 계속해서 풀을 채워야 하는지 확인하는 것 같아요.
그런데 connection을 가져가고 반납하는 내용은 해당 설정에서는 확인이 어려웠네요..! |
||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
</configuration> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
|
||
import com.techcourse.config.DataSourceConfig; | ||
import com.techcourse.domain.User; | ||
import com.techcourse.support.jdbc.init.DataSourceConnectionManager; | ||
import com.techcourse.support.jdbc.init.PooledDataSourceConnectionManager; | ||
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
@@ -18,7 +18,7 @@ class UserDaoTest { | |
void setup() { | ||
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); | ||
|
||
userDao = new UserDao(new JdbcTemplate(new DataSourceConnectionManager())); | ||
userDao = new UserDao(new JdbcTemplate(new PooledDataSourceConnectionManager())); | ||
final var user = new User("gugu", "password", "[email protected]"); | ||
userDao.insert(user); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.springframework.jdbc.core; | ||
|
||
import java.sql.Connection; | ||
import java.sql.PreparedStatement; | ||
import java.sql.SQLException; | ||
|
||
@FunctionalInterface | ||
public interface ConnectionCallback<T> { | ||
|
||
T doInConnection(Connection connection, PreparedStatement preparedStatement) throws SQLException; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,64 +8,82 @@ | |
import java.util.List; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.SqlQueryException; | ||
import org.springframework.jdbc.SqlQueryException; | ||
|
||
public class JdbcTemplate { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class); | ||
|
||
private final ConnectionManager connectionManager; | ||
|
||
public JdbcTemplate(ConnectionManager connectionManager) { | ||
public JdbcTemplate(final ConnectionManager connectionManager) { | ||
this.connectionManager = connectionManager; | ||
} | ||
|
||
public void executeUpdate(String query, Object... parameters) { | ||
try (final Connection connection = connectionManager.getConnection(); | ||
final PreparedStatement preparedStatement = connection.prepareStatement(query)) { | ||
log.info("query: {}", query); | ||
setParameters(preparedStatement, parameters); | ||
preparedStatement.executeUpdate(); | ||
} catch (SQLException exception) { | ||
throw new SqlQueryException(exception.getMessage(), query); | ||
} | ||
public int executeUpdate(final String query, final Object... parameters) { | ||
return execute(query, (connection, preparedStatement) -> preparedStatement.executeUpdate(), parameters); | ||
} | ||
|
||
public <T> T executeQueryForObject(String query, RowMapper<T> rowMapper, Object... parameters) { | ||
try (final Connection connection = connectionManager.getConnection(); | ||
final PreparedStatement preparedStatement = connection.prepareStatement(query); | ||
final ResultSet resultSet = executePreparedStatementQuery(preparedStatement, parameters)) { | ||
log.info("query: {}", query); | ||
public <T> T executeQueryForObject( | ||
final String query, | ||
final RowMapper<T> rowMapper, | ||
final Object... parameters | ||
) { | ||
final ResultSetExtractor<T> resultSetExtractor = resultSet -> { | ||
if (resultSet.next()) { | ||
return rowMapper.mapRow(resultSet); | ||
} | ||
return null; | ||
} catch (SQLException exception) { | ||
throw new SqlQueryException(exception.getMessage(), query); | ||
} | ||
}; | ||
|
||
return executeQuery(query, resultSetExtractor, parameters); | ||
} | ||
|
||
public <T> List<T> executeQueryForList(String query, RowMapper<T> rowMapper, Object... parameters) { | ||
try (final Connection connection = connectionManager.getConnection(); | ||
final PreparedStatement preparedStatement = connection.prepareStatement(query); | ||
final ResultSet resultSet = executePreparedStatementQuery(preparedStatement, parameters)) { | ||
log.info("query: {}", query); | ||
public <T> List<T> executeQueryForList( | ||
final String query, | ||
final RowMapper<T> rowMapper, | ||
final Object... parameters | ||
) { | ||
final ResultSetExtractor<List<T>> resultSetExtractor = resultSet -> { | ||
final List<T> results = new ArrayList<>(); | ||
while (resultSet.next()) { | ||
results.add(rowMapper.mapRow(resultSet)); | ||
} | ||
return results; | ||
}; | ||
|
||
return executeQuery(query, resultSetExtractor, parameters); | ||
} | ||
|
||
public <T> T executeQuery( | ||
final String query, | ||
final ResultSetExtractor<T> resultSetExtractor, | ||
final Object... parameters | ||
) { | ||
return execute(query, (connection, preparedStatement) -> { | ||
try (final ResultSet resultSet = preparedStatement.executeQuery()) { | ||
return resultSetExtractor.extract(resultSet); | ||
} | ||
}, parameters); | ||
} | ||
|
||
private <T> T execute( | ||
final String query, | ||
final ConnectionCallback<T> callback, | ||
final Object... parameters | ||
) { | ||
try (final Connection connection = connectionManager.getConnection(); | ||
final PreparedStatement preparedStatement = connection.prepareStatement(query)) { | ||
log.info("query: {}", query); | ||
setParameters(preparedStatement, parameters); | ||
return callback.doInConnection(connection, preparedStatement); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어마어마한 리팩토링이네요! 힌트를 안보고 진행하셨다고 하셨는데 그러면 직접 코드를 보신건가요? 아니면 공식문서를 보신건가요? 각 메서드별로 기능들이 확실하게 나눠져있고, 그 나눠진 로직을 람다를 이용해 깔끔하게 리팩토링하신게 인상적입니다.. 특히 callback.doInConnection은 아직도 이해를 잘 못한거 같은데 이 메서드를 통해 반환 값을 사용하는쪽에서 정의하도록 한 거죠? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉 감사합니다 !
네 맞습니다! 사실 헷갈리셨을 것 같은게, |
||
} catch (SQLException exception) { | ||
throw new SqlQueryException(exception.getMessage(), query); | ||
} | ||
} | ||
|
||
private ResultSet executePreparedStatementQuery(PreparedStatement preparedStatement, Object... parameters) throws SQLException { | ||
setParameters(preparedStatement, parameters); | ||
return preparedStatement.executeQuery(); | ||
} | ||
|
||
private void setParameters(PreparedStatement preparedStatement, Object... parameters) throws SQLException { | ||
private void setParameters(final PreparedStatement preparedStatement, final Object... parameters) | ||
throws SQLException { | ||
for (int index = 1; index <= parameters.length; index++) { | ||
preparedStatement.setString(index, String.valueOf(parameters[index - 1])); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.springframework.jdbc.core; | ||
|
||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
|
||
|
||
public interface ResultSetExtractor<T> { | ||
|
||
T extract(ResultSet resultSet) throws SQLException; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,120 @@ | ||
package nextstep.jdbc; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import java.sql.Connection; | ||
import java.sql.SQLException; | ||
import java.sql.Statement; | ||
import java.util.List; | ||
import org.h2.jdbcx.JdbcDataSource; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.jdbc.CannotGetJdbcConnectionException; | ||
import org.springframework.jdbc.core.ConnectionManager; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
|
||
class JdbcTemplateTest { | ||
|
||
private JdbcTemplate jdbcTemplate; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
TestConnectionManager connectionManager = new TestConnectionManager(); | ||
this.jdbcTemplate = new JdbcTemplate(connectionManager); | ||
try (Connection conn = connectionManager.getConnection()) { | ||
conn.setAutoCommit(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 AutoCommit을 true로 했다가 false로 하신 이유가 있을까요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 사실 이 부분은 테스트 환경 설정을 빠르게 하려고 |
||
try (Statement stmt = conn.createStatement()) { | ||
stmt.execute("DROP TABLE IF EXISTS users;"); | ||
stmt.execute( | ||
"CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(100) NOT NULL)"); | ||
stmt.executeUpdate("INSERT INTO users (email) VALUES ('[email protected]')"); | ||
conn.setAutoCommit(false); | ||
} | ||
} catch (SQLException e) { | ||
throw new AssertionError(e); | ||
} | ||
} | ||
|
||
@Test | ||
@DisplayName("쓰기 작업(생성, 수정, 삭제)을 하는 쿼리를 실행한다.") | ||
void executeUpdate() { | ||
// given | ||
failIfExceptionThrownBy(() -> { | ||
// when | ||
final var updated = jdbcTemplate.executeUpdate("insert into users (email) values (?)", "[email protected]"); | ||
|
||
// then | ||
assertThat(updated).isOne(); | ||
}); | ||
} | ||
|
||
@Test | ||
@DisplayName("단건 레코드를 조회하는 쿼리를 실행한다.") | ||
void executeQueryForObject() { | ||
// given | ||
failIfExceptionThrownBy(() -> { | ||
// when | ||
User user = jdbcTemplate.executeQueryForObject( | ||
"select email from users where id = ?", | ||
resultSet -> new User(resultSet.getString(1)), 1L | ||
); | ||
|
||
// then | ||
assertThat(user).usingRecursiveComparison() | ||
.comparingOnlyFields("[email protected]"); | ||
}); | ||
} | ||
|
||
@Test | ||
@DisplayName("다건 레코드를 조회하는 쿼리를 실행한다.") | ||
void executeQueryForList() { | ||
// given | ||
jdbcTemplate.executeUpdate("insert into users (email) values (?)", "[email protected]"); | ||
|
||
failIfExceptionThrownBy(() -> { | ||
// when | ||
List<User> users = jdbcTemplate.executeQueryForList( | ||
"select email from users", | ||
resultSet -> new User(resultSet.getString(1)) | ||
); | ||
|
||
// then | ||
assertThat(users).extracting("email") | ||
.containsExactlyInAnyOrder("[email protected]", "[email protected]"); | ||
}); | ||
} | ||
|
||
private void failIfExceptionThrownBy(Runnable test) { | ||
try { | ||
test.run(); | ||
} catch (Exception exception) { | ||
Assertions.fail(exception); | ||
} | ||
} | ||
|
||
private static class User { | ||
|
||
private final String email; | ||
|
||
public User(final String email) { | ||
this.email = email; | ||
} | ||
|
||
} | ||
|
||
private static class TestConnectionManager implements ConnectionManager { | ||
|
||
@Override | ||
public Connection getConnection() throws CannotGetJdbcConnectionException { | ||
try { | ||
final var jdbcDataSource = new JdbcDataSource(); | ||
jdbcDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;"); | ||
return jdbcDataSource.getConnection(); | ||
} catch (SQLException sqlException) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍어떻게 보면 무리한 요구일 수 도 있었는데 직접 해보시고 좋은 경험을 하신 것 같아서 다행입니다 ㅎㅎ