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단계] 마코(이규성) 미션 제출합니다. #310

Merged
merged 3 commits into from
Oct 1, 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
105 changes: 21 additions & 84 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,121 +1,58 @@
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;

public class UserDao {

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

private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;

private RowMapper<User> rowMapper = rs -> {
Long id = rs.getLong("id");
String account = rs.getString("account");
String password = rs.getString("password");
String email = rs.getString("email");
return new User(id, account, password, email);
};

public UserDao(final DataSource dataSource) {
this.dataSource = dataSource;
this(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) {}
}
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

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

public List<User> findAll() {
// todo
return null;
final var 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) {}
}
return jdbcTemplate.queryForObject(sql, rowMapper, 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, rowMapper, account);
}
}
1 change: 1 addition & 0 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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 static org.assertj.core.api.Assertions.assertThat;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.springframework.dao;

public class EmptyResultAccessException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.springframework.jdbc.core;

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

public class ArgumentPreparedStatementSetter implements PreparedStatementSetter{

Choose a reason for hiding this comment

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

책임 분리 좋네요!!👍

PreparedStatement 의 코드에 설명에서 Parameter 라는 단어로 해당 값들을 표현하더라구요 Parameter 를 이용한 네이밍을 사용해보면 더 좋을 것 같아요~😁

사소하지만 제가 이해한 PreparedStatement의 Argument를 setting을 하는 책임을 가진 객체가 맞다면 PreparedStatmentArgumentSetter 라는 네이밍이 저는 좀 더 와닿는데 클래스명만으로 PreparedStatementSetter 인터페이스를 구현한 클래스임을 충분히 알수있는 현재 네이밍에도 장점이 있어서 마코의 생각이 궁금합니다!🫡

Copy link
Author

Choose a reason for hiding this comment

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

저는 argument 기반의 PreparedStatementSetter라고 생각을 해서 ArgumentPreparedStatementSetter라고 지었습니다.
확실히 스플릿이 말씀해주신 PreparedStatementArgumentSetter가 의미 전달이 잘 되는 것 같습니다👍
하지만 나중에 PreparedStatementSetter 뿐만 아니라 PreparedStatementCreator와 같이 비슷한 이름의 여러 인터페이스가 생기는 경우 헷갈릴 것 같아 가능하면 인터페이스의 이름을 사용하는 것이 좋다고 생각합니다~


private final Object[] args;

public ArgumentPreparedStatementSetter(Object[] args) {
this.args = args;
}

@Override
public void setValues(PreparedStatement pstmt) throws SQLException {
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);

Choose a reason for hiding this comment

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

setObject 👍

}
}
}
48 changes: 48 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,8 +2,16 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultAccessException;

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;

public class JdbcTemplate {

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

public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
List<T> results = query(sql, rowMapper, args);
if (results.isEmpty()) {
throw new EmptyResultAccessException();
}
return results.get(0);
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... args) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
Comment on lines +35 to +36

Choose a reason for hiding this comment

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

try-with-resources 👍
finally 코드들 없애보니 가독성이 진짜 많이 올라가네요오!!

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

PreparedStatementSetter pstSetter = new ArgumentPreparedStatementSetter(args);
pstSetter.setValues(pstmt);
ResultSet resultSet = pstmt.executeQuery();

List<T> results = new ArrayList<>();
while (resultSet.next()) {
results.add(rowMapper.mapRow(resultSet));
}
return results;
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

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

PreparedStatementSetter pstSetter = new ArgumentPreparedStatementSetter(args);
pstSetter.setValues(pstmt);
pstmt.executeUpdate();
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
}
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 PreparedStatementSetter {

Choose a reason for hiding this comment

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

확장성을 위한 추상화 👍


void setValues(PreparedStatement preparedStatement) throws SQLException;
}
10 changes: 10 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,10 @@
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