From 2a605431e13ad70fb2c44fb2068979da2b8434c2 Mon Sep 17 00:00:00 2001 From: Rosie Date: Wed, 27 Sep 2023 14:01:47 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20UserDao=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++ .../main/java/com/techcourse/dao/UserDao.java | 59 ++++++++++++++----- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 721b81d472..83b92f3e3f 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # JDBC 라이브러리 구현하기 + +개발자는 SQL 쿼리 작성, 쿼리에 전달할 인자, SELECT 구문일 경우 +조회 결과를 추출하는 것만 집중할 수 있도록 라이브러리를 만들자. + +## 힌트 + +- 리팩터링은 UserDaoTest를 활용해 진행한다. +- 중복을 제거하기 위한 라이브러리는 JdbcTemplate 클래스에 구현한다. +- DataSource는 DataSourceConfig 클래스의 getInstance() 메서드를 호출하면 된다. diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index d14c545f34..2c1bbe33a0 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -1,6 +1,7 @@ package com.techcourse.dao; import com.techcourse.domain.User; +import java.util.ArrayList; import org.springframework.jdbc.core.JdbcTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,6 +12,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +import org.springframework.jdbc.core.RowMapper; public class UserDao { @@ -28,7 +30,10 @@ public UserDao(final JdbcTemplate jdbcTemplate) { public void insert(final User user) { final var sql = "insert into users (account, password, email) values (?, ?, ?)"; + execute(sql, user.getAccount(), user.getPassword(), user.getEmail()); + } + private void execute(String sql, Object... parameters) { Connection conn = null; PreparedStatement pstmt = null; try { @@ -37,9 +42,10 @@ public void insert(final User user) { log.debug("query : {}", sql); - pstmt.setString(1, user.getAccount()); - pstmt.setString(2, user.getPassword()); - pstmt.setString(3, user.getEmail()); + for (int i = 0; i < parameters.length; i++) { + pstmt.setObject(i + 1, parameters[i]); + } + pstmt.executeUpdate(); } catch (SQLException e) { log.error(e.getMessage(), e); @@ -60,36 +66,56 @@ public void insert(final User user) { } public void update(final User user) { - // todo + final var sql = "update users set password = ?, email = ?, account = ? where id = ?"; + execute(sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId()); } public List findAll() { - // todo - return null; + final var sql = "select id, account, password, email from users"; + return 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 = ?"; + return queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), + rs.getString("account"), + rs.getString("password"), + rs.getString("email")), id); + } + private T queryForObject(String sql, RowMapper rowMapper, Object... params) { + List query = query(sql, rowMapper, params); + if (query.size() > 1) { + throw new RuntimeException("too many result"); + } + if (query.isEmpty()) { + return null; + } + return query.get(0); + } + + private List query(String sql, RowMapper rowMapper, Object... parameters) { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = dataSource.getConnection(); pstmt = conn.prepareStatement(sql); - pstmt.setLong(1, id); + for (int i = 0; i < parameters.length; i++) { + pstmt.setObject(i + 1, parameters[i]); + } rs = pstmt.executeQuery(); log.debug("query : {}", sql); + final List results = new ArrayList<>(); if (rs.next()) { - return new User( - rs.getLong(1), - rs.getString(2), - rs.getString(3), - rs.getString(4)); + results.add(rowMapper.mapRow(rs, 0)); } - return null; + return results; } catch (SQLException e) { log.error(e.getMessage(), e); throw new RuntimeException(e); @@ -115,7 +141,10 @@ public User findById(final Long id) { } public User findByAccount(final String account) { - // todo - return null; + final var sql = "select id, account, password, email from users where account = ?"; + return queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), + rs.getString("account"), + rs.getString("password"), + rs.getString("email")), account); } } From 780c3934eba3b9cb89d16c0875b5518aaaf3b60b Mon Sep 17 00:00:00 2001 From: Rosie Date: Wed, 27 Sep 2023 14:09:02 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20JdbcTemplate=20=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=98=AE=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 104 ++---------------- .../java/com/techcourse/dao/UserDaoTest.java | 3 +- .../jdbc/core/JdbcTemplate.java | 92 ++++++++++++++++ .../springframework/jdbc/core/RowMapper.java | 60 ++++++++++ 4 files changed, 162 insertions(+), 97 deletions(-) create mode 100644 jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 2c1bbe33a0..b27e0f6c74 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -1,5 +1,6 @@ package com.techcourse.dao; +import com.techcourse.config.DataSourceConfig; import com.techcourse.domain.User; import java.util.ArrayList; import org.springframework.jdbc.core.JdbcTemplate; @@ -18,61 +19,26 @@ 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 (?, ?, ?)"; - execute(sql, user.getAccount(), user.getPassword(), user.getEmail()); - } - - private void execute(String sql, Object... parameters) { - Connection conn = null; - PreparedStatement pstmt = null; - try { - conn = dataSource.getConnection(); - 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); - } 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) { final var sql = "update users set password = ?, email = ?, account = ? where id = ?"; - execute(sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId()); + jdbcTemplate.execute(sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId()); } public List findAll() { final var sql = "select id, account, password, email from users"; - return query(sql, (rs, rowNum) -> new User(rs.getLong("id"), + return jdbcTemplate.query(sql, (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("account"), rs.getString("password"), rs.getString("email"))); @@ -80,69 +46,15 @@ public List findAll() { public User findById(final Long id) { final var sql = "select id, account, password, email from users where id = ?"; - return queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("account"), rs.getString("password"), rs.getString("email")), id); } - private T queryForObject(String sql, RowMapper rowMapper, Object... params) { - List query = query(sql, rowMapper, params); - if (query.size() > 1) { - throw new RuntimeException("too many result"); - } - if (query.isEmpty()) { - return null; - } - return query.get(0); - } - - private List query(String sql, RowMapper rowMapper, Object... parameters) { - Connection conn = null; - PreparedStatement pstmt = null; - ResultSet rs = null; - try { - conn = dataSource.getConnection(); - pstmt = conn.prepareStatement(sql); - for (int i = 0; i < parameters.length; i++) { - pstmt.setObject(i + 1, parameters[i]); - } - rs = pstmt.executeQuery(); - - log.debug("query : {}", sql); - - final List results = new ArrayList<>(); - if (rs.next()) { - results.add(rowMapper.mapRow(rs, 0)); - } - return results; - } 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) {} - } - } - public User findByAccount(final String account) { final var sql = "select id, account, password, email from users where account = ?"; - return queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("account"), rs.getString("password"), rs.getString("email")), account); diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index 773d7faf82..7e9cc2b01a 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -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; @@ -16,7 +17,7 @@ class UserDaoTest { void setup() { DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); - userDao = new UserDao(DataSourceConfig.getInstance()); + userDao = new UserDao(new JdbcTemplate(DataSourceConfig.getInstance())); final var user = new User("gugu", "password", "hkkang@woowahan.com"); userDao.insert(user); } diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 52a0d30a17..7baee7f2a2 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -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; @@ -14,4 +20,90 @@ public class JdbcTemplate { public JdbcTemplate(final DataSource dataSource) { this.dataSource = dataSource; } + + public void execute(String sql, Object... parameters) { + Connection conn = null; + PreparedStatement pstmt = null; + try { + conn = dataSource.getConnection(); + 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); + } finally { + try { + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException ignored) {} + + try { + if (conn != null) { + conn.close(); + } + } catch (SQLException ignored) {} + } + } + + public T queryForObject(String sql, RowMapper rowMapper, Object... params) { + List query = query(sql, rowMapper, params); + if (query.size() > 1) { + throw new RuntimeException("too many result"); + } + if (query.isEmpty()) { + return null; + } + return query.get(0); + } + + public List query(String sql, RowMapper rowMapper, Object... parameters) { + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + conn = dataSource.getConnection(); + pstmt = conn.prepareStatement(sql); + for (int i = 0; i < parameters.length; i++) { + pstmt.setObject(i + 1, parameters[i]); + } + rs = pstmt.executeQuery(); + + log.debug("query : {}", sql); + + final List results = new ArrayList<>(); + if (rs.next()) { + results.add(rowMapper.mapRow(rs, 0)); + } + return results; + } 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) {} + } + } } diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java b/jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java new file mode 100644 index 0000000000..7fee249381 --- /dev/null +++ b/jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java @@ -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. + * + *

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. + * + *

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 the result type + * @see JdbcTemplate + */ +@FunctionalInterface +public interface RowMapper { + + /** + * 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; + +} From 53f810d4c0c49d9b167cb23831b1e1af348e8675 Mon Sep 17 00:00:00 2001 From: Rosie Date: Wed, 27 Sep 2023 17:52:42 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=97=AC=EB=9F=AC=20=ED=96=89?= =?UTF-8?q?=EC=9D=B4=20=EB=82=98=EC=98=AC=EB=95=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/dao/UserDaoTest.java | 12 +++- .../jdbc/core/JdbcTemplate.java | 65 ++++--------------- 2 files changed, 24 insertions(+), 53 deletions(-) diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index 7e9cc2b01a..f692842434 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -3,6 +3,7 @@ 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; @@ -12,16 +13,22 @@ class UserDaoTest { private UserDao userDao; + private JdbcTemplate jdbcTemplate; @BeforeEach void setup() { DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); - - userDao = new UserDao(new JdbcTemplate(DataSourceConfig.getInstance())); + jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + userDao = new UserDao(jdbcTemplate); final var user = new User("gugu", "password", "hkkang@woowahan.com"); userDao.insert(user); } + @AfterEach + void tearDown() { + jdbcTemplate.execute("truncate table users restart identity"); + } + @Test void findAll() { final var users = userDao.findAll(); @@ -67,4 +74,5 @@ void update() { assertThat(actual.getPassword()).isEqualTo(newPassword); } + } diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 7baee7f2a2..4da1f3afc9 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -22,12 +22,8 @@ public JdbcTemplate(final DataSource dataSource) { } public void execute(String sql, Object... parameters) { - Connection conn = null; - PreparedStatement pstmt = null; - try { - conn = dataSource.getConnection(); - pstmt = conn.prepareStatement(sql); - + try (final Connection conn = dataSource.getConnection(); + final PreparedStatement pstmt = conn.prepareStatement(sql)) { log.debug("query : {}", sql); for (int i = 0; i < parameters.length; i++) { @@ -38,72 +34,39 @@ public void execute(String sql, Object... parameters) { } 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) {} } } public T queryForObject(String sql, RowMapper rowMapper, Object... params) { - List query = query(sql, rowMapper, params); - if (query.size() > 1) { - throw new RuntimeException("too many result"); + List results = query(sql, rowMapper, params); + if (results.size() > 1) { + throw new RuntimeException("too many result. expected 1 but was " + results.size()); } - if (query.isEmpty()) { - return null; + if (results.isEmpty()) { + throw new RuntimeException("no result"); } - return query.get(0); + return results.get(0); } public List query(String sql, RowMapper rowMapper, Object... parameters) { - Connection conn = null; - PreparedStatement pstmt = null; - ResultSet rs = null; - try { - conn = dataSource.getConnection(); - pstmt = conn.prepareStatement(sql); + 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]); } - rs = pstmt.executeQuery(); + ResultSet rs = pstmt.executeQuery(); log.debug("query : {}", sql); final List results = new ArrayList<>(); - if (rs.next()) { - results.add(rowMapper.mapRow(rs, 0)); + while (rs.next()) { + results.add(rowMapper.mapRow(rs, rs.getRow())); } return results; } 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) {} } } + }