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단계] 제이미(임정수) 미션 제출합니다. #432

Merged
merged 10 commits into from
Oct 7, 2023
10 changes: 5 additions & 5 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

public class UserDao {

private static final RowMapper<User> rowMapper = (rs, rowNum) -> new User(
rs.getLong(1),
rs.getString(2),
rs.getString(3),
rs.getString(4)
private static final RowMapper<User> rowMapper = (rs) -> new User(
rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")
Comment on lines +12 to +16
Copy link

Choose a reason for hiding this comment

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

👍

);

private final JdbcTemplate jdbcTemplate;
Expand Down
29 changes: 29 additions & 0 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.exception.EmptyResultDataAccessException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class UserDaoTest {

private static final String TRUNCATE_USER_TABLE_SQL = "DROP TABLE IF EXISTS users;" +
"create table if not exists users ( " +
" id bigint auto_increment," +
" account varchar(100) not null," +
" password varchar(100) not null," +
" email varchar(100) not null," +
" primary key(id)" +
");";

private UserDao userDao;

@BeforeEach
void setup() {
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
final JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
jdbcTemplate.execute(TRUNCATE_USER_TABLE_SQL);

userDao = new UserDao(DataSourceConfig.getInstance());
final var user = new User("gugu", "password", "[email protected]");
Expand All @@ -35,6 +49,13 @@ void findById() {
assertThat(user.getAccount()).isEqualTo("gugu");
}

@Test
void findByIdException() {
assertThatThrownBy(() -> userDao.findById(999L))
.isInstanceOf(EmptyResultDataAccessException.class)
.hasMessage("조회된 결과가 존재하지 않습니다.");
}

@Test
void findByAccount() {
final var account = "gugu";
Expand All @@ -43,6 +64,14 @@ void findByAccount() {
assertThat(user.getAccount()).isEqualTo(account);
}

@Test
void findByAccountException() {
final var account = "jamie";
assertThatThrownBy(() -> userDao.findByAccount(account))
.isInstanceOf(EmptyResultDataAccessException.class)
.hasMessage("조회된 결과가 존재하지 않습니다.");
}

@Test
void insert() {
final var account = "insert-gugu";
Expand Down
99 changes: 39 additions & 60 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package org.springframework.jdbc.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.exception.EmptyResultDataAccessException;
import org.springframework.jdbc.core.exception.IncorrectResultSizeDataAccessException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand All @@ -13,83 +12,63 @@

public class JdbcTemplate {

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);
public static final int MAX_RESULT_SIZE = 1;
public static final int FIRST_INDEX = 0;

private final DataSource dataSource;
private final PreparedStatementExecutor preparedStatementExecutor;

public JdbcTemplate(final DataSource dataSource) {
this.dataSource = dataSource;
this.preparedStatementExecutor = new PreparedStatementExecutor(dataSource);
}

public int update(final String sql, final Object... args) {
try (
final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)
) {
setArguments(pstmt, args);

log.debug("query : {}", sql);

return pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
return preparedStatementExecutor.execute(PreparedStatement::executeUpdate, sql, args);
}

private static void setArguments(final PreparedStatement pstmt, final Object[] args) throws SQLException {
for (int i = 0; i < args.length; i++) {
final int parameterIndex = i + 1;
pstmt.setObject(parameterIndex, args[i]);
}
public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
return preparedStatementExecutor.execute(
pstmt -> {
final ResultSet rs = pstmt.executeQuery();
final List<T> results = getResults(rowMapper, rs);

return getSingleResult(results);
}, sql, args
Comment on lines +30 to +35
Copy link

Choose a reason for hiding this comment

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

익명 함수를 잘 사용해주셨네요 !

);
}

public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
try (
final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)
) {
setArguments(pstmt, args);
ResultSet rs = pstmt.executeQuery();

log.debug("query : {}", sql);

return calculateResult(rowMapper, rs);
} catch (SQLException e) {
throw new RuntimeException(e);
private <T> List<T> getResults(final RowMapper<T> rowMapper, final ResultSet rs) throws SQLException {
final List<T> results = new ArrayList<>();

while (rs.next()) {
final T result = rowMapper.mapRow(rs);
results.add(result);
}

return results;
}

private static <T> T calculateResult(final RowMapper<T> rowMapper, final ResultSet rs) throws SQLException {
if (rs.next()) {
return rowMapper.mapRow(rs, rs.getRow());
private <T> T getSingleResult(final List<T> results) {
if (results.isEmpty()) {
throw new EmptyResultDataAccessException("조회된 결과가 존재하지 않습니다.");
}
if (results.size() > MAX_RESULT_SIZE) {
throw new IncorrectResultSizeDataAccessException("조회된 결과의 개수가 적절하지 않습니다.");
}

return null;
return results.get(FIRST_INDEX);
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper) {
try (
final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)
) {
ResultSet rs = pstmt.executeQuery();

log.debug("query : {}", sql);
return preparedStatementExecutor.execute(
pstmt -> {
final ResultSet rs = pstmt.executeQuery();

return getResults(rowMapper, rs);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return getResults(rowMapper, rs);
}, sql
);
}

private static <T> List<T> getResults(final RowMapper<T> rowMapper, final ResultSet rs) throws SQLException {
List<T> results = new ArrayList<>();

while (rs.next()) {
final T result = calculateResult(rowMapper, rs);
results.add(result);
}

return results;
public boolean execute(final String sql) {
return preparedStatementExecutor.execute(PreparedStatement::execute, sql);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.springframework.jdbc.core;

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

@FunctionalInterface
public interface PreparedStatementExecuteStrategy<T> {

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class PreparedStatementExecutor {

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

private final DataSource dataSource;

public PreparedStatementExecutor(final DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> T execute(final PreparedStatementExecuteStrategy<T> executeStrategy, final String sql, final Object... args) {
try (
final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)
) {
setArguments(pstmt, args);

log.debug("query : {}", sql);

return executeStrategy.strategy(pstmt);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

private void setArguments(final PreparedStatement pstmt, final Object[] args) throws SQLException {
for (int i = 0; i < args.length; i++) {
final int parameterIndex = i + 1;
pstmt.setObject(parameterIndex, args[i]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
@FunctionalInterface
public interface RowMapper<T> {

T mapRow(final ResultSet rs, final int rowNum) throws SQLException;
T mapRow(final ResultSet rs) throws SQLException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.springframework.jdbc.core.exception;

import org.springframework.dao.DataAccessException;

public class EmptyResultDataAccessException extends DataAccessException {

public EmptyResultDataAccessException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.springframework.jdbc.core.exception;

import org.springframework.dao.DataAccessException;

public class IncorrectResultSizeDataAccessException extends DataAccessException {

public IncorrectResultSizeDataAccessException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.springframework.jdbc.core.exception;

public class ResultSetOverflowException extends IllegalArgumentException {

public ResultSetOverflowException(final String message) {
super(message);
}
}
Loading