diff --git a/README.md b/README.md index 71eba26ccd..aa37b938f5 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,4 @@ - [x] findById() - [x] update() - [x] JdbcTemplate 으로 중복 제거 +- [x] 트랜잭션 경계 설정하기 diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 88e6c56780..29089dce2d 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -4,6 +4,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import java.sql.Connection; import java.util.List; import java.util.Optional; @@ -32,7 +33,13 @@ public void update(final User user) { jdbcTemplate.execute(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); } - public List findAll() { + public void update(final Connection connection, final User user) { + final var sql = "update users set (account, password, email) = (?, ?, ?) where id = ?"; + + jdbcTemplate.executeWithConnection(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); + } + + List findAll() { final var sql = "select id, account, password, email from users"; return jdbcTemplate.query(sql, USER_ROW_MAPPER); @@ -44,7 +51,7 @@ public Optional findById(final Long id) { return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id); } - public Optional findByAccount(final String account) { + Optional findByAccount(final String account) { final var sql = "select id, account, password, email from users where 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 3afdb57016..0d59666b25 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -3,6 +3,8 @@ import com.techcourse.domain.UserHistory; import org.springframework.jdbc.core.JdbcTemplate; +import java.sql.Connection; + public class UserHistoryDao { private final JdbcTemplate jdbcTemplate; @@ -23,4 +25,18 @@ public void log(final UserHistory userHistory) { userHistory.getCreateBy() ); } + + 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.executeWithConnection(connection, + sql, + userHistory.getUserId(), + userHistory.getAccount(), + userHistory.getPassword(), + userHistory.getEmail(), + userHistory.getCreatedAt(), + userHistory.getCreateBy() + ); + } } diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index fd6e595087..08737eb6d9 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,23 +1,32 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; import java.util.NoSuchElementException; public class UserService { + private static final Logger log = LoggerFactory.getLogger(UserService.class); + private final UserDao userDao; private final UserHistoryDao userHistoryDao; - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { + UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { this.userDao = userDao; this.userHistoryDao = userHistoryDao; } - public User findById(final long id) { + User findById(final long id) { return userDao.findById(id) .orElseThrow(() -> new NoSuchElementException("id 값으로 해당하는 User 를 찾을 수 없습니다.")); } @@ -26,10 +35,49 @@ public void insert(final User user) { userDao.insert(user); } - public void changePassword(final long id, final String newPassword, final String createBy) { + 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)); + + Connection connection = null; + try { + final DataSource dataSource = DataSourceConfig.getInstance(); + connection = dataSource.getConnection(); + connection.setAutoCommit(false); + + userDao.update(connection, user); + userHistoryDao.log(connection, new UserHistory(user, createBy)); + + connection.commit(); + } catch (final SQLException | DataAccessException e) { + rollback(connection); + log.error("SQLException occurred"); + throw new DataAccessException(e); + } finally { + release(connection); + } + } + + private void rollback(final Connection connection) { + if (connection != null) { + try { + connection.rollback(); + } catch (final SQLException ex) { + log.error("rollback callback"); + throw new DataAccessException(ex); + } + } + } + + private void release(final Connection connection) { + if (connection != null) { + try { + connection.setAutoCommit(true); + connection.close(); + } catch (final SQLException e) { + log.error("Cannot close Connection"); + throw new DataAccessException(e); + } + } } } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 2ee12b195f..bec1932c88 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -5,14 +5,16 @@ import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; +import java.sql.Connection; + public class MockUserHistoryDao extends UserHistoryDao { - public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { + MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { super(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 2391b5c631..1438b90b4c 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -6,7 +6,6 @@ import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; 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; @@ -14,15 +13,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -@Disabled class UserServiceTest { - private JdbcTemplate jdbcTemplate; private UserDao userDao; + private UserHistoryDao userHistoryDao; + private MockUserHistoryDao mockUserHistoryDao; @BeforeEach void setUp() { - this.userDao = new UserDao(new JdbcTemplate(DataSourceConfig.getInstance())); + final JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + userDao = new UserDao(jdbcTemplate); + userHistoryDao = new UserHistoryDao(jdbcTemplate); + mockUserHistoryDao = new MockUserHistoryDao(jdbcTemplate); DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); final var user = new User("gugu", "password", "hkkang@woowahan.com"); @@ -31,7 +33,6 @@ void setUp() { @Test void testChangePassword() { - final var userHistoryDao = new UserHistoryDao(jdbcTemplate); final var userService = new UserService(userDao, userHistoryDao); final var newPassword = "qqqqq"; @@ -46,8 +47,7 @@ void testChangePassword() { @Test void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 - final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userService = new UserService(userDao, mockUserHistoryDao); final var newPassword = "newPassword"; final var createBy = "gugu"; 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 58fb5207f1..2dca5b4436 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -39,6 +39,19 @@ public void execute(final String sql, final Object... params) { } } + public void executeWithConnection(final Connection conn, final String sql, final Object... params) { + try (final PreparedStatement pstmt = conn.prepareStatement(sql)) { + log.debug("query : {}", sql); + + setCondition(params, pstmt); + + pstmt.executeUpdate(); + } catch (final SQLException e) { + log.error(e.getMessage(), e); + throw new DataAccessException(e); + } + } + public List query(final String sql, final RowMapper rowMapper) { try ( final Connection conn = dataSource.getConnection(); @@ -64,7 +77,7 @@ public List query(final String sql, final RowMapper rowMapper) { public Optional queryForObject(final String sql, final RowMapper rowMapper, final Object... params) { try ( final Connection conn = dataSource.getConnection(); - final PreparedStatement pstmt = getPreparedStatement(sql, conn, params); + final PreparedStatement pstmt = getPreparedStatement(conn, sql, params); final ResultSet rs = pstmt.executeQuery() ) { log.debug("query : {}", sql); @@ -81,7 +94,7 @@ public Optional queryForObject(final String sql, final RowMapper rowMa } } - private PreparedStatement getPreparedStatement(final String sql, final Connection conn, final Object[] params) throws SQLException { + private PreparedStatement getPreparedStatement(final Connection conn, final String sql, final Object[] params) throws SQLException { final PreparedStatement pstmt = conn.prepareStatement(sql); setCondition(params, pstmt); return pstmt;