diff --git a/README.md b/README.md index 22473b0082..198b0f45fb 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,11 @@ | 트랜잭션 관리 | ✅ | | | Connection, Statement, ResultSet 객체 close | ✅ | | +## 3단계 + +- [ ] User 비밀번호 변경 기능 + - [ ] 비밀번호 변경 기능을 구현한다. (UserDao.changePassword()) + - [ ] 누가, 언제, 어떤 비밀번호로 바꿨는지 이력을 남겨야한다. + - [ ] changePassword 원자성을 보장한다 + - 트랜잭션을 설정한다. + - userDao와 userHistoryDao를 한 트랜잭션으로 묶으려면 동일한 Connection 객체를 사용하도록 변경 diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index b5c6b06907..bcae20e661 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -1,11 +1,19 @@ package com.techcourse.dao; import com.techcourse.domain.User; +import java.sql.Connection; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; public class UserDao { + public static final RowMapper USER_ROW_MAPPER = (resultSet, rowNum) -> new User(resultSet.getLong("id"), + resultSet.getString("account"), + resultSet.getString("password"), + resultSet.getString("email")); + + private final JdbcTemplate jdbcTemplate; @@ -23,28 +31,29 @@ public void update(final User user) { jdbcTemplate.update(sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId()); } + public void update(final Connection connection, final User user) { + final var sql = "update users set password = ?, email = ?, account = ? where id = ?"; + jdbcTemplate.update(connection, sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId()); + } + public List findAll() { final var sql = "select id, account, password, email from users"; - return jdbcTemplate.query(sql, (resultSet, rowNum) -> new User(resultSet.getLong("id"), - resultSet.getString("account"), - resultSet.getString("password"), - resultSet.getString("email"))); + return jdbcTemplate.query(sql, USER_ROW_MAPPER); } public User findById(final Long id) { final var sql = "select id, account, password, email from users where id = ?"; - return jdbcTemplate.queryForObject(sql, (resultSet, rowNum) -> new User(resultSet.getLong("id"), - resultSet.getString("account"), - resultSet.getString("password"), - resultSet.getString("email")), id); + return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id); + } + + public User findById(final Connection connection, final Long id) { + final var sql = "select id, account, password, email from users where id = ?"; + return jdbcTemplate.queryForObject(connection, sql, USER_ROW_MAPPER, id); } public User findByAccount(final String account) { final var sql = "select id, account, password, email from users where account = ?"; - return jdbcTemplate.queryForObject(sql, (resultSet, rowNum) -> new User(resultSet.getLong("id"), - resultSet.getString("account"), - resultSet.getString("password"), - resultSet.getString("email")), account); + return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, account); } } diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index 6c7fa4f339..9554b1adf9 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -1,6 +1,7 @@ package com.techcourse.dao; import com.techcourse.domain.UserHistory; +import java.sql.Connection; import org.springframework.jdbc.core.JdbcTemplate; public class UserHistoryDao { @@ -11,6 +12,12 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } + public void log(final Connection connection, final UserHistory userHistory) { + final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; + jdbcTemplate.update(connection, sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(), + userHistory.getEmail(), userHistory.getCreatedAt(), userHistory.getCreateBy()); + } + public void log(final UserHistory userHistory) { final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(), diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index fcf2159dc8..83958e086b 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,9 +1,13 @@ package com.techcourse.service; +import com.techcourse.config.DataSourceConfig; import com.techcourse.dao.UserDao; import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; +import java.sql.Connection; +import java.sql.SQLException; +import org.springframework.dao.DataAccessException; public class UserService { @@ -19,14 +23,30 @@ public User findById(final long id) { return userDao.findById(id); } + public User findById(final Connection connection, final long id) { + return userDao.findById(connection, id); + } + public void insert(final User user) { userDao.insert(user); } - public void changePassword(final long id, final String newPassword, final String createBy) { - final var user = findById(id); - user.changePassword(newPassword); - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); + public void changePassword(final long id, final String newPassword, final String createBy) throws SQLException { + final var connection = DataSourceConfig.getInstance().getConnection(); + try { + connection.setAutoCommit(false); + final var user = findById(connection, id); + user.changePassword(newPassword); + userDao.update(connection, user); + userHistoryDao.log(connection, new UserHistory(user, createBy)); + + connection.commit(); + } catch (final Exception e) { + connection.rollback(); + throw new DataAccessException(e); + } finally { + connection.close(); + } } + } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 2ee12b195f..4f6f9aba26 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -2,6 +2,7 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.UserHistory; +import java.sql.Connection; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -12,7 +13,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { } @Override - public void log(final UserHistory userHistory) { + public void log(final Connection connection, final UserHistory userHistory) { throw new DataAccessException(); } } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index 255a0ebfe7..934318af84 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -5,16 +5,16 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; +import java.sql.SQLException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; -@Disabled class UserServiceTest { private JdbcTemplate jdbcTemplate; @@ -31,7 +31,7 @@ void setUp() { } @Test - void testChangePassword() { + void testChangePassword() throws SQLException { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); final var userService = new UserService(userDao, userHistoryDao); @@ -46,18 +46,26 @@ void testChangePassword() { @Test void testTransactionRollback() { - // 트랜잭션 롤백 테스트를 위해 mock으로 교체 - final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + // given + final var mockUserHistoryDao = new MockUserHistoryDao(jdbcTemplate); + final var userService = new UserService(userDao, mockUserHistoryDao); final var newPassword = "newPassword"; final var createBy = "gugu"; - // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. - assertThrows(DataAccessException.class, - () -> userService.changePassword(1L, newPassword, createBy)); - final var actual = userService.findById(1L); + // when + try { + userService.changePassword(1L, newPassword, createBy); + } catch (final Exception e) { + // do nothing + } - assertThat(actual.getPassword()).isNotEqualTo(newPassword); + // then + final var actual = userService.findById(1L); + assertAll( + () -> assertThrows(DataAccessException.class, + () -> userService.changePassword(1L, newPassword, createBy)), + () -> assertThat(actual.getPassword()).isNotEqualTo(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 5d801fdfa0..da9c4a0beb 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -22,7 +22,7 @@ public JdbcTemplate(final DataSource dataSource) { this.dataSource = dataSource; } - public T queryForObject(String sql, RowMapper rowMapper, Object... params) { + public T queryForObject(final String sql, final RowMapper rowMapper, final Object... params) { List results = query(sql, rowMapper, params); if (results.size() > 1) { throw new DataAccessException("too many result. expected 1 but was " + results.size()); @@ -33,7 +33,18 @@ public T queryForObject(String sql, RowMapper rowMapper, Object... params return results.get(0); } - public List query(String sql, RowMapper rowMapper, Object... parameters) { + public T queryForObject(final Connection connection, final String sql, final RowMapper rowMapper, final Object... params) { + List results = query(connection, sql, rowMapper, params); + if (results.size() > 1) { + throw new DataAccessException("too many result. expected 1 but was " + results.size()); + } + if (results.isEmpty()) { + throw new DataAccessException("no result"); + } + return results.get(0); + } + + public List query(final String sql, final RowMapper rowMapper, final Object... parameters) { try (final Connection conn = dataSource.getConnection(); final PreparedStatement preparedStatement = conn.prepareStatement(sql); final ResultSet resultSet = executeQuery(preparedStatement, parameters)) { @@ -45,6 +56,17 @@ public List query(String sql, RowMapper rowMapper, Object... parameter } } + public List query(final Connection conn, final String sql, final RowMapper rowMapper, final Object... parameters) { + try (final PreparedStatement preparedStatement = conn.prepareStatement(sql); + final ResultSet resultSet = executeQuery(preparedStatement, parameters)) { + log.debug("query : {}", sql); + return mapResults(rowMapper, resultSet); + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new DataAccessException(e); + } + } + private ResultSet executeQuery(final PreparedStatement preparedStatement, final Object[] parameters) throws SQLException { setParameters(preparedStatement, parameters); @@ -66,7 +88,7 @@ private List mapResults(final RowMapper rowMapper, final ResultSet res return results; } - public void update(String sql, Object... parameters) { + public void update(final String sql, final Object... parameters) { try (final Connection conn = dataSource.getConnection(); final PreparedStatement preparedStatement = conn.prepareStatement(sql)) { log.debug("query : {}", sql); @@ -79,4 +101,16 @@ public void update(String sql, Object... parameters) { } } + public void update(final Connection conn, final String sql, final Object... parameters) { + try (final PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + log.debug("query : {}", sql); + + setParameters(preparedStatement, parameters); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new DataAccessException(e); + } + } + }