-
Notifications
You must be signed in to change notification settings - Fork 300
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단계] 홍실(홍혁준) 미션 제출합니다. #270
Changes from 10 commits
dc70d1d
7182a49
06379e5
e1e638f
080f13c
3388deb
fd4fa81
1f7a746
f3c9df6
2602ce1
68029ad
7ffbb6f
db3936e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,17 @@ | ||
# JDBC 라이브러리 구현하기 | ||
|
||
- [x] UserDaoTest 통과시키기 | ||
- [x] user findAll 구현하기 | ||
- [x] user update 구현하기 | ||
- [x] user findByAccount 구현하기 | ||
- [x] UserDao 리팩터링 | ||
- [x] 공통부분 분리 | ||
- [ ] JdbcTemplate 구현하기 | ||
- [x] 조회기능 구현 | ||
- [x] 쓰기기능 구현 | ||
- [x] UserDao를 JdbcTemplate을 사용하도록 리팩터링 | ||
- [x] findByAccount | ||
- [x] findById | ||
- [x] findAll | ||
- [x] update | ||
- [x] insert |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,121 +1,66 @@ | ||
package com.techcourse.dao; | ||
|
||
import com.techcourse.domain.User; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import javax.sql.DataSource; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
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 org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.jdbc.core.Mapper; | ||
|
||
public class UserDao { | ||
|
||
private static final Mapper<User> USER_MAPPER = (rs) -> new User( | ||
rs.getLong(1), | ||
rs.getString(2), | ||
rs.getString(3), | ||
rs.getString(4) | ||
); | ||
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) {} | ||
} | ||
jdbcTemplate.executeUpdate(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.executeUpdate(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.executeQuery(sql, (rs) -> { | ||
final List<User> users = new ArrayList<>(); | ||
while (rs.next()) { | ||
users.add(USER_MAPPER.map(rs)); | ||
} | ||
return users; | ||
}); | ||
} | ||
|
||
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.executeQuery(sql, USER_MAPPER, id); | ||
} | ||
|
||
public User findByAccount(final String account) { | ||
// todo | ||
return null; | ||
final var sql = "select id, account, password, email from users where account = ?"; | ||
|
||
return jdbcTemplate.executeQuery(sql, USER_MAPPER, account); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
package org.springframework.jdbc.core; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import static java.sql.Statement.RETURN_GENERATED_KEYS; | ||
|
||
import java.sql.Connection; | ||
import java.sql.PreparedStatement; | ||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import javax.sql.DataSource; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class JdbcTemplate { | ||
|
||
|
@@ -14,4 +19,56 @@ public class JdbcTemplate { | |
public JdbcTemplate(final DataSource dataSource) { | ||
this.dataSource = dataSource; | ||
} | ||
|
||
public Long executeUpdate(final String sql, final Object... parameters) { | ||
try (final Connection connection = dataSource.getConnection(); | ||
final PreparedStatement pstmt = connection.prepareStatement(sql, | ||
RETURN_GENERATED_KEYS)) { | ||
log.debug("query : {}", sql); | ||
setPreparedStatement(pstmt, parameters); | ||
pstmt.executeUpdate(); | ||
return extractId(pstmt); | ||
} catch (final SQLException e) { | ||
log.error(e.getMessage(), e); | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private Long extractId(final PreparedStatement pstmt) throws SQLException { | ||
try (final ResultSet generatedKeys = pstmt.getGeneratedKeys()) { | ||
if (generatedKeys.next()) { | ||
return generatedKeys.getLong(1); | ||
} | ||
throw new SQLException("id를 찾을 수 없습니다."); | ||
} | ||
} | ||
|
||
private void setPreparedStatement( | ||
final PreparedStatement pstmt, | ||
final Object[] parameters | ||
) throws SQLException { | ||
for (int index = 1; index <= parameters.length; index++) { | ||
pstmt.setObject(index, parameters[index - 1]); | ||
} | ||
} | ||
|
||
public <T> T executeQuery( | ||
final String sql, | ||
final Mapper<T> mapper, | ||
final Object... objects) { | ||
|
||
try (final Connection connection = dataSource.getConnection(); | ||
final PreparedStatement pstmt = connection.prepareStatement(sql)) { | ||
setPreparedStatement(pstmt, objects); | ||
final ResultSet rs = pstmt.executeQuery(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ResultSet도 close를 해줘야하는 군요. 적용해볼게요! |
||
log.debug("query : {}", sql); | ||
if (rs.next()) { | ||
return mapper.map(rs); | ||
} | ||
return null; | ||
} catch (SQLException e) { | ||
log.error(e.getMessage(), e); | ||
throw new RuntimeException(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; | ||
|
||
public interface Mapper<T> { | ||
|
||
T map(final ResultSet resultSet) throws SQLException; | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.springframework.jdbc.core; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertAll; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.jdbc.core.test_supporter.DataSourceConfig; | ||
import org.springframework.jdbc.core.test_supporter.DatabasePopulatorUtils; | ||
import org.springframework.jdbc.core.test_supporter.User; | ||
import org.springframework.jdbc.core.test_supporter.UserDao; | ||
|
||
class JdbcTemplateTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 테스트까지! 꼼꼼하시군요 👍 |
||
|
||
private final JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); | ||
private final UserDao userDao = new UserDao(DataSourceConfig.getInstance()); | ||
|
||
@BeforeEach | ||
void setUp() { | ||
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); | ||
} | ||
|
||
@Test | ||
@DisplayName("execute Query로 읽는 쿼리를 실행할 수 있다.") | ||
void executeQuery() { | ||
final User user = new User("hong-sile", "hong", "[email protected]"); | ||
userDao.insert(user); | ||
final String sql = "select id, account, password, email from users where id = ?"; | ||
final Long id = 1L; | ||
|
||
final User actual = jdbcTemplate.executeQuery(sql, (rs) -> | ||
new User( | ||
rs.getLong(1), | ||
rs.getString(2), | ||
rs.getString(3), | ||
rs.getString(4) | ||
) | ||
, id); | ||
|
||
assertAll( | ||
() -> assertThat(id) | ||
.isEqualTo(actual.getId()), | ||
() -> assertThat(actual) | ||
.usingRecursiveComparison() | ||
.ignoringFields("id") | ||
.isEqualTo(user) | ||
); | ||
} | ||
|
||
@Test | ||
@DisplayName("execute로 쓰는 쿼리를 실행할 수 있다.") | ||
void execute() { | ||
final User user = new User("hong-sile", "hong", "[email protected]"); | ||
final String sql = "insert into users (account, password, email) values (?, ?, ?)"; | ||
|
||
final Long id = jdbcTemplate.executeUpdate( | ||
sql, | ||
user.getAccount(), user.getPassword(), user.getEmail() | ||
); | ||
|
||
final User actual = userDao.findById(id); | ||
|
||
assertAll( | ||
() -> assertThat(id) | ||
.isEqualTo(actual.getId()), | ||
() -> assertThat(actual) | ||
.usingRecursiveComparison() | ||
.ignoringFields("id") | ||
.isEqualTo(user) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package org.springframework.jdbc.core.test_supporter; | ||
|
||
import java.util.Objects; | ||
import org.h2.jdbcx.JdbcDataSource; | ||
|
||
public class DataSourceConfig { | ||
|
||
private static javax.sql.DataSource INSTANCE; | ||
|
||
public static javax.sql.DataSource getInstance() { | ||
if (Objects.isNull(INSTANCE)) { | ||
INSTANCE = createJdbcDataSource(); | ||
} | ||
return INSTANCE; | ||
} | ||
|
||
private static JdbcDataSource createJdbcDataSource() { | ||
final var jdbcDataSource = new JdbcDataSource(); | ||
jdbcDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;"); | ||
jdbcDataSource.setUser(""); | ||
jdbcDataSource.setPassword(""); | ||
return jdbcDataSource; | ||
} | ||
|
||
private DataSourceConfig() {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이전에 jdbcTempate을 사용했을 때를 생각하면 update 시 해당 쿼리의 영향을 받은 행의 개수가 반환되었던 걸로 기억하고 있습니다.
그런데 영향을 받은 행이 아닌 영향을 받은 행의 id를 반환하는 이유가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JdbcTemplate이 영향을 받은 행의 개수를 반환한다는 것은 알고 있었어요. 하지만 굳이 JdbcTemplate과 똑같이 할 필욘 없다고 생각했습니다.
사실 영향을 받은 행의 개수를 반환하게 해도 해당 값을 사용할 일이 적다고 생각했어요.
잘 쓰이지도 않는 영향을 받은 행의 개수를 반환할 바엔, id를 반환하게 하는 게 조금 더 유용하게 쓰일 메서드라고 생각했어요.
id는 보통 db에 의존하는 방식이고, 자바코드에서 별도의 쿼리를 날리지 않는 이상 알 수 있는 방법은 없으니까요.
insert를 하고 location을 반환할 때도 쓰일 수 있고요.
근데, 지금 다시 보니 여러개의 row를 업데이트 하는 상황이랑은 조금 안 맞는 것 같네요.
조금 더 고민해보고 적당한 방향으로 반영해보겠습니다.