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단계] 로지(윤가영) 미션 제출합니다. #281

Merged
merged 3 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) {
Comment on lines +25 to +26
Copy link
Member

Choose a reason for hiding this comment

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

try-with-resource 도 적용해주셨군요!👍

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());
Copy link
Member

Choose a reason for hiding this comment

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

예외처리 꼼꼼하게 하시네요👍

}
if (results.isEmpty()) {
throw new RuntimeException("no result");
}
return results.get(0);
}
Copy link
Member

Choose a reason for hiding this comment

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

변수명도 변경해주셨군요


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;

}
Loading