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 라이브러리 구현하기 - 1단계] 도치(김동흠) 미션 제출합니다. #324

Merged
merged 8 commits into from
Oct 1, 2023
122 changes: 33 additions & 89 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,121 +1,65 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import org.springframework.jdbc.core.JdbcTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

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


public class UserDao {

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

private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;

public UserDao(final DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public UserDao(final JdbcTemplate jdbcTemplate) {
this.dataSource = null;
this.jdbcTemplate = jdbcTemplate;
}

public void insert(final User user) {
final var sql = "insert into users (account, password, email) values (?, ?, ?)";

Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);

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

pstmt.setString(1, user.getAccount());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getEmail());
pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {}
}
final String sql = "insert into users (account, password, email) values (?, ?, ?)";

jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final User user) {
// todo
final String sql = "update users set account = ?, password = ?, email = ? where users.id = ?";

jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public List<User> findAll() {
// todo
return null;
final String sql = "select id, account, password, email from users";

return jdbcTemplate.query(sql, rowMapper);
}

public User findById(final Long id) {
final var sql = "select id, account, password, email from users where id = ?";

Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();

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

if (rs.next()) {
return new User(
rs.getLong(1),
rs.getString(2),
rs.getString(3),
rs.getString(4));
}
return null;
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException ignored) {}

try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {}
}
private final RowMapper<User> rowMapper = rs -> (
new User(
rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")
)
);

public Optional<User> findById(final Long id) {
final String sql = "select id, account, password, email from users where id = ?";

return jdbcTemplate.queryForObject(sql, rowMapper, id);
}

public User findByAccount(final String account) {
// todo
return null;
public Optional<User> findByAccount(final String account) {
final String sql = "select id, account, password, email from users where users.account = ?";

return jdbcTemplate.queryForObject(sql, rowMapper, account);
}
}
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 IllegalArgumentException("해당 아이디의 회원이 존재하지 않습니다."));

Choose a reason for hiding this comment

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

:따봉

}

public void insert(final User user) {
Expand Down
11 changes: 6 additions & 5 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ void findAll() {

@Test
void findById() {
final var user = userDao.findById(1L);
final var user = userDao.findById(1L).get();

assertThat(user.getAccount()).isEqualTo("gugu");
}

@Test
void findByAccount() {
final var account = "gugu";
final var user = userDao.findByAccount(account);
final var user = userDao.findByAccount(account).get();

assertThat(user.getAccount()).isEqualTo(account);
}
Expand All @@ -49,20 +49,21 @@ void insert() {
final var user = new User(account, "password", "[email protected]");
userDao.insert(user);

final var actual = userDao.findById(2L);
final var actual = userDao.findById(2L).get();

assertThat(actual.getAccount()).isEqualTo(account);
assertThat(actual.getEmail()).isEqualTo("[email protected]");
}

@Test
void update() {
final var newPassword = "password99";
final var user = userDao.findById(1L);
final var user = userDao.findById(1L).get();
user.changePassword(newPassword);

userDao.update(user);

final var actual = userDao.findById(1L);
final var actual = userDao.findById(1L).get();

assertThat(actual.getPassword()).isEqualTo(newPassword);
}
Expand Down
1 change: 1 addition & 0 deletions jdbc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.springframework.jdbc;

import java.sql.SQLException;

public class BadGrammarJdbcException extends JdbcException {
public BadGrammarJdbcException(String msg) {
super(msg);
}

public BadGrammarJdbcException(String msg, SQLException ex) {
super(msg, ex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.sql.SQLException;

public class CannotGetJdbcConnectionException extends RuntimeException {
public class CannotGetJdbcConnectionException extends JdbcException {

public CannotGetJdbcConnectionException(String msg) {
super(msg);
Expand Down
11 changes: 11 additions & 0 deletions jdbc/src/main/java/org/springframework/jdbc/JdbcException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.springframework.jdbc;

public class JdbcException extends RuntimeException {
public JdbcException(String message) {
super(message);
}

public JdbcException(String message, Throwable cause) {
super(message);
}
}
98 changes: 98 additions & 0 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,114 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.BadGrammarJdbcException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.JdbcException;

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;
import java.util.Set;

public class JdbcTemplate {

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

private final DataSource dataSource;
private final SqlExceptionTranslator sqlExceptionTranslator = new SqlExceptionTranslator();

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

public int update(String sql, Object... arguments) {
try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = StatementCreator.createStatement(conn, sql, arguments)
) {
log.debug("query : {}", sql);

return pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw sqlExceptionTranslator.translateException(e);
}
}

public <T> Optional<T> queryForObject(String sql, RowMapper<T> rowMapper, Object... arguments) {
try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = StatementCreator.createStatement(conn, sql, arguments);
ResultSet rs = pstmt.executeQuery()
) {
log.debug("query : {}", sql);

if (rs.next()) {
return Optional.of(rowMapper.mapRow(rs));
}

return Optional.empty();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw sqlExceptionTranslator.translateException(e);
}
}


public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... arguments) {
try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = StatementCreator.createStatement(conn, sql, arguments);
ResultSet rs = pstmt.executeQuery()
) {
log.debug("query : {}", sql);

List<T> result = new ArrayList<>();
while (rs.next()) {
result.add(rowMapper.mapRow(rs));
}

return result;
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw sqlExceptionTranslator.translateException(e);
}
}

private class SqlExceptionTranslator {

Choose a reason for hiding this comment

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

👍

private final Set<String> BAD_SQL_GRAMMAR_CODES = Set.of(
"07", // Dynamic SQL error
"21", // Cardinality violation
"2A", // Syntax error direct SQL
"37", // Syntax error dynamic SQL
"42", // General SQL syntax error
"65" // Oracle: unknown identifier
);

private final Set<String> DATA_ACCESS_RESOURCE_FAILURE_CODES = Set.of(
"08", // Connection exception
"53", // PostgreSQL: insufficient resources (e.g. disk full)
"54", // PostgreSQL: program limit exceeded (e.g. statement too complex)
"57", // DB2: out-of-memory exception / database not started
"58" // DB2: unexpected system error
);

private JdbcException translateException(SQLException e) {
String sqlState = e.getSQLState();
String classCode = sqlState.substring(0, 2);
if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) {

Choose a reason for hiding this comment

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

사소하지만.. 저한테는 classCode.contains(DATA_ACCESS_RESOURCE_FAILURE_CODES) 같은 순서로 쓰는 게 더 잘 이해되는 것 같아서 커멘트 남겨봅니다!

Copy link
Author

Choose a reason for hiding this comment

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

이 부분 해보려했는데 charSequence로 바꾸기 위한 조작이 더 생길 것 같아서 현재대로 유지하겠습니다!

throw new CannotGetJdbcConnectionException(e.getMessage(), e);
}
if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) {
throw new BadGrammarJdbcException(e.getMessage(), e);
}

throw new JdbcException(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.springframework.jdbc.core;

import java.sql.ResultSet;
import java.sql.SQLException;

Choose a reason for hiding this comment

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

추상 메서드가 한 개이므로 @FunctionalInterface를 붙여주는 건 어떨까요?

@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
Loading
Loading