diff --git a/app/build.gradle b/app/build.gradle
index 00444e6f91..514e7fd4f3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,6 +27,7 @@ dependencies {
implementation "org.apache.commons:commons-lang3:3.13.0"
implementation "com.fasterxml.jackson.core:jackson-databind:2.15.2"
implementation "com.h2database:h2:2.2.220"
+ implementation 'com.zaxxer:HikariCP:5.0.1'
testImplementation "org.assertj:assertj-core:3.24.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2"
diff --git a/app/src/main/java/com/techcourse/config/DataSourceConfig.java b/app/src/main/java/com/techcourse/config/DataSourceConfig.java
index 183b03b4ce..7aa79113d7 100644
--- a/app/src/main/java/com/techcourse/config/DataSourceConfig.java
+++ b/app/src/main/java/com/techcourse/config/DataSourceConfig.java
@@ -1,7 +1,8 @@
package com.techcourse.config;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
import java.util.Objects;
-import org.h2.jdbcx.JdbcDataSource;
public class DataSourceConfig {
@@ -9,17 +10,18 @@ public class DataSourceConfig {
public static javax.sql.DataSource getInstance() {
if (Objects.isNull(INSTANCE)) {
- INSTANCE = createJdbcDataSource();
+ INSTANCE = createHikariDataSource();
}
return INSTANCE;
}
- private static JdbcDataSource createJdbcDataSource() {
- final var jdbcDataSource = new JdbcDataSource();
- jdbcDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;");
- jdbcDataSource.setUser("");
- jdbcDataSource.setPassword("");
- return jdbcDataSource;
+ private static HikariDataSource createHikariDataSource() {
+ final var hikariConfig = new HikariConfig();
+ hikariConfig.setJdbcUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;");
+ hikariConfig.setUsername("");
+ hikariConfig.setPassword("");
+
+ return new HikariDataSource(hikariConfig);
}
private DataSourceConfig() {}
diff --git a/app/src/main/java/com/techcourse/support/jdbc/init/DataSourceConnectionManager.java b/app/src/main/java/com/techcourse/support/jdbc/init/PooledDataSourceConnectionManager.java
similarity index 57%
rename from app/src/main/java/com/techcourse/support/jdbc/init/DataSourceConnectionManager.java
rename to app/src/main/java/com/techcourse/support/jdbc/init/PooledDataSourceConnectionManager.java
index bfffc10cc9..b12b952c82 100644
--- a/app/src/main/java/com/techcourse/support/jdbc/init/DataSourceConnectionManager.java
+++ b/app/src/main/java/com/techcourse/support/jdbc/init/PooledDataSourceConnectionManager.java
@@ -1,18 +1,25 @@
package com.techcourse.support.jdbc.init;
import com.techcourse.config.DataSourceConfig;
+import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
-import javax.sql.DataSource;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.ConnectionManager;
-public class DataSourceConnectionManager implements ConnectionManager {
+public class PooledDataSourceConnectionManager implements ConnectionManager {
+ private final HikariDataSource dataSource;
+
+ public PooledDataSourceConnectionManager() {
+ this.dataSource = (HikariDataSource) DataSourceConfig.getInstance();
+ }
+
+ @Override
public Connection getConnection() throws CannotGetJdbcConnectionException {
try {
- DataSource dataSource = DataSourceConfig.getInstance();
- return dataSource.getConnection();
+ final var connection = dataSource.getConnection();
+ return connection;
} catch (final SQLException exception) {
throw new CannotGetJdbcConnectionException("Datasource connection error:" + exception.getMessage());
}
diff --git a/app/src/main/resources/logback.xml b/app/src/main/resources/logback.xml
index c127b484d4..b909459e6e 100644
--- a/app/src/main/resources/logback.xml
+++ b/app/src/main/resources/logback.xml
@@ -12,4 +12,8 @@
+
+
+
+
diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java
index d7dee66a75..70f4645a68 100644
--- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java
+++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java
@@ -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", "hkkang@woowahan.com");
userDao.insert(user);
}
diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java
index bbbddecdc1..f320478955 100644
--- a/app/src/test/java/com/techcourse/service/UserServiceTest.java
+++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java
@@ -7,7 +7,7 @@
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
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.Disabled;
@@ -23,7 +23,7 @@ class UserServiceTest {
@BeforeEach
void setUp() {
- this.jdbcTemplate = new JdbcTemplate(new DataSourceConnectionManager());
+ this.jdbcTemplate = new JdbcTemplate(new PooledDataSourceConnectionManager());
this.userDao = new UserDao(jdbcTemplate);
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
diff --git a/jdbc/build.gradle b/jdbc/build.gradle
index 83f293f626..e1432935a9 100644
--- a/jdbc/build.gradle
+++ b/jdbc/build.gradle
@@ -14,6 +14,7 @@ dependencies {
implementation "org.reflections:reflections:0.10.2"
implementation "org.apache.commons:commons-lang3:3.13.0"
implementation "ch.qos.logback:logback-classic:1.2.12"
+ implementation "com.h2database:h2:2.2.220"
testImplementation "org.assertj:assertj-core:3.24.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2"
diff --git a/jdbc/src/main/java/org/springframework/SqlQueryException.java b/jdbc/src/main/java/org/springframework/jdbc/SqlQueryException.java
similarity index 90%
rename from jdbc/src/main/java/org/springframework/SqlQueryException.java
rename to jdbc/src/main/java/org/springframework/jdbc/SqlQueryException.java
index e315b0ce00..585e4c704b 100644
--- a/jdbc/src/main/java/org/springframework/SqlQueryException.java
+++ b/jdbc/src/main/java/org/springframework/jdbc/SqlQueryException.java
@@ -1,4 +1,4 @@
-package org.springframework;
+package org.springframework.jdbc;
public class SqlQueryException extends RuntimeException {
diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java b/jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java
new file mode 100644
index 0000000000..bac8375457
--- /dev/null
+++ b/jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java
@@ -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 doInConnection(Connection connection, PreparedStatement preparedStatement) throws SQLException;
+
+}
diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
index 4db10da224..b6f5bc8cf2 100644
--- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
+++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
@@ -8,7 +8,7 @@
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.SqlQueryException;
+import org.springframework.jdbc.SqlQueryException;
public class JdbcTemplate {
@@ -16,56 +16,74 @@ public class JdbcTemplate {
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 executeQueryForObject(String query, RowMapper 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 executeQueryForObject(
+ final String query,
+ final RowMapper rowMapper,
+ final Object... parameters
+ ) {
+ final ResultSetExtractor 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 List executeQueryForList(String query, RowMapper 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 List executeQueryForList(
+ final String query,
+ final RowMapper rowMapper,
+ final Object... parameters
+ ) {
+ final ResultSetExtractor> resultSetExtractor = resultSet -> {
final List results = new ArrayList<>();
while (resultSet.next()) {
results.add(rowMapper.mapRow(resultSet));
}
return results;
+ };
+
+ return executeQuery(query, resultSetExtractor, parameters);
+ }
+
+ public T executeQuery(
+ final String query,
+ final ResultSetExtractor resultSetExtractor,
+ final Object... parameters
+ ) {
+ return execute(query, (connection, preparedStatement) -> {
+ try (final ResultSet resultSet = preparedStatement.executeQuery()) {
+ return resultSetExtractor.extract(resultSet);
+ }
+ }, parameters);
+ }
+
+ private T execute(
+ final String query,
+ final ConnectionCallback 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);
} 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]));
}
diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java b/jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java
new file mode 100644
index 0000000000..e95e71b393
--- /dev/null
+++ b/jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java
@@ -0,0 +1,11 @@
+package org.springframework.jdbc.core;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public interface ResultSetExtractor {
+
+ T extract(ResultSet resultSet) throws SQLException;
+
+}
diff --git a/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java b/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java
index d777c8becf..d7ecce16c1 100644
--- a/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java
+++ b/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java
@@ -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);
+ 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 ('hkkang@woowahan.com')");
+ 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 (?)", "doy@gmail.com");
+
+ // 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("hkkang@woowahan.com");
+ });
+ }
+
+ @Test
+ @DisplayName("다건 레코드를 조회하는 쿼리를 실행한다.")
+ void executeQueryForList() {
+ // given
+ jdbcTemplate.executeUpdate("insert into users (email) values (?)", "doy@gmail.com");
+
+ failIfExceptionThrownBy(() -> {
+ // when
+ List users = jdbcTemplate.executeQueryForList(
+ "select email from users",
+ resultSet -> new User(resultSet.getString(1))
+ );
+
+ // then
+ assertThat(users).extracting("email")
+ .containsExactlyInAnyOrder("hkkang@woowahan.com", "doy@gmail.com");
+ });
+ }
+
+ 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();
+ }
+ }
+ }
}