Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 1단계] 도치(김동흠) 미션 제출합니다. (#324)
Browse files Browse the repository at this point in the history
* 패키지 위치 변경 및 코드 정리

* feat:jdbcTemplate으로 userDao 구현

* refactor:예외처리 구체화

* refactor(UserDao):rowmapper와 타입 수정해 가독성 개선

* fix:유저의 이메일 정보 일부기호를 sql문 생성시 누락시키는 오류 수정

* refactor:sql문의 인자 필터링 메서드 분리

* feat:SqlException의 예외를 구체화한 jdbcException으로 변환하는 기능 구현

* refactor:jdbcExcpetion 계층화

---------

Co-authored-by: kang-hyungu <[email protected]>
  • Loading branch information
hum02 and kang-hyungu authored Oct 1, 2023
1 parent 7182a49 commit 0e42841
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 96 deletions.
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("해당 아이디의 회원이 존재하지 않습니다."));
}

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 {
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)) {
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;

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

0 comments on commit 0e42841

Please sign in to comment.