Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 1단계] 로지(윤가영) 미션 제출합니다. (#281)
Browse files Browse the repository at this point in the history
* feat: UserDao 구현

* refactor: JdbcTemplate 으로 옮기기

* fix: 여러 행이 나올때 처리
  • Loading branch information
kyY00n authored Sep 30, 2023
1 parent 06379e5 commit 61c94c3
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 83 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# JDBC 라이브러리 구현하기

개발자는 SQL 쿼리 작성, 쿼리에 전달할 인자, SELECT 구문일 경우
조회 결과를 추출하는 것만 집중할 수 있도록 라이브러리를 만들자.

## 힌트

- 리팩터링은 UserDaoTest를 활용해 진행한다.
- 중복을 제거하기 위한 라이브러리는 JdbcTemplate 클래스에 구현한다.
- DataSource는 DataSourceConfig 클래스의 getInstance() 메서드를 호출하면 된다.
103 changes: 22 additions & 81 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.techcourse.dao;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import java.util.ArrayList;
import org.springframework.jdbc.core.JdbcTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -11,111 +13,50 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;

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;
}

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

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) {}
}
jdbcTemplate.execute(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final User user) {
// todo
final var sql = "update users set password = ?, email = ?, account = ? where id = ?";
jdbcTemplate.execute(sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId());
}

public List<User> findAll() {
// todo
return null;
final var sql = "select id, account, password, email from users";
return jdbcTemplate.query(sql, (rs, rowNum) -> new User(rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")));
}

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) {}
}
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")), id);
}

public User findByAccount(final String account) {
// todo
return null;
final var sql = "select id, account, password, email from users where account = ?";
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"),
rs.getString("account"),
rs.getString("password"),
rs.getString("email")), account);
}
}
13 changes: 11 additions & 2 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,32 @@
import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;

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

class UserDaoTest {

private UserDao userDao;
private JdbcTemplate jdbcTemplate;

@BeforeEach
void setup() {
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());

userDao = new UserDao(DataSourceConfig.getInstance());
jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
userDao = new UserDao(jdbcTemplate);
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
}

@AfterEach
void tearDown() {
jdbcTemplate.execute("truncate table users restart identity");
}

@Test
void findAll() {
final var users = userDao.findAll();
Expand Down Expand Up @@ -66,4 +74,5 @@ void update() {

assertThat(actual.getPassword()).isEqualTo(newPassword);
}

}
55 changes: 55 additions & 0 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package org.springframework.jdbc.core;

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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -14,4 +20,53 @@ public class JdbcTemplate {
public JdbcTemplate(final DataSource dataSource) {
this.dataSource = dataSource;
}

public void execute(String sql, Object... parameters) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)) {
log.debug("query : {}", sql);

for (int i = 0; i < parameters.length; i++) {
pstmt.setObject(i + 1, parameters[i]);
}

pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... params) {
List<T> results = query(sql, rowMapper, params);
if (results.size() > 1) {
throw new RuntimeException("too many result. expected 1 but was " + results.size());
}
if (results.isEmpty()) {
throw new RuntimeException("no result");
}
return results.get(0);
}

public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... parameters) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)) {
for (int i = 0; i < parameters.length; i++) {
pstmt.setObject(i + 1, parameters[i]);
}
ResultSet rs = pstmt.executeQuery();

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

final List<T> results = new ArrayList<>();
while (rs.next()) {
results.add(rowMapper.mapRow(rs, rs.getRow()));
}
return results;
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}

}
60 changes: 60 additions & 0 deletions jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.jdbc.core;

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

/**
* An interface used by {@link JdbcTemplate} for mapping rows of a
* {@link ResultSet} on a per-row basis. Implementations of this
* interface perform the actual work of mapping each row to a result object,
* but don't need to worry about exception handling.
* {@link SQLException SQLExceptions} will be caught and handled
* by the calling JdbcTemplate.
*
* <p>Typically used either for {@link JdbcTemplate}'s query methods
* or for out parameters of stored procedures. RowMapper objects are
* typically stateless and thus reusable; they are an ideal choice for
* implementing row-mapping logic in a single place.
*
* <p>Alternatively, consider subclassing
* {@code jdbc.object} package: Instead of working with separate
* JdbcTemplate and RowMapper objects, you can build executable query
* objects (containing row-mapping logic) in that style.
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @param <T> the result type
* @see JdbcTemplate
*/
@FunctionalInterface
public interface RowMapper<T> {

/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
* @param rs the ResultSet to map (pre-initialized for the current row)
* @param rowNum the number of the current row
* @return the result object for the current row (maybe {@code null})
* @throws SQLException if an SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
T mapRow(ResultSet rs, int rowNum) throws SQLException;

}

0 comments on commit 61c94c3

Please sign in to comment.