From bd71827b651d068893944afce45e472de37699ef Mon Sep 17 00:00:00 2001 From: hum02 Date: Sun, 8 Oct 2023 22:34:09 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EA=B3=BC=20=EB=A1=9C=EA=B9=85=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 7 +++ .../com/techcourse/dao/UserHistoryDao.java | 50 ++++--------------- .../com/techcourse/service/UserService.java | 25 ++++++++-- .../service/MockUserHistoryDao.java | 4 +- .../techcourse/service/UserServiceTest.java | 16 +++--- .../jdbc/core/JdbcTemplate.java | 17 +++++++ .../support/ConnectionManager.java | 29 +++++++++++ 7 files changed, 97 insertions(+), 51 deletions(-) create mode 100644 jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 2d105f3a5f..6065d7618a 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -7,6 +7,7 @@ import org.springframework.jdbc.core.RowMapper; import javax.sql.DataSource; +import java.sql.Connection; import java.util.List; import java.util.Optional; @@ -36,6 +37,12 @@ public void update(final User user) { jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); } + public void update(final Connection connection, final User user) { + final String sql = "update users set account = ?, password = ?, email = ? where users.id = ?"; + + jdbcTemplate.update(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); + } + public List findAll() { final String sql = "select id, account, password, email from users"; diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index edb4338caa..c42f023922 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -1,62 +1,32 @@ package com.techcourse.dao; import com.techcourse.domain.UserHistory; -import org.springframework.jdbc.core.JdbcTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; public class UserHistoryDao { private static final Logger log = LoggerFactory.getLogger(UserHistoryDao.class); - private final DataSource dataSource; + private final JdbcTemplate jdbcTemplate; public UserHistoryDao(final DataSource dataSource) { - this.dataSource = dataSource; + this.jdbcTemplate = new JdbcTemplate(dataSource); } public UserHistoryDao(final JdbcTemplate jdbcTemplate) { - this.dataSource = null; + this.jdbcTemplate = jdbcTemplate; } - public void log(final UserHistory userHistory) { - final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; - - Connection conn = null; - PreparedStatement pstmt = null; - try { - conn = dataSource.getConnection(); - pstmt = conn.prepareStatement(sql); - - log.debug("query : {}", sql); - - pstmt.setLong(1, userHistory.getUserId()); - pstmt.setString(2, userHistory.getAccount()); - pstmt.setString(3, userHistory.getPassword()); - pstmt.setString(4, userHistory.getEmail()); - pstmt.setObject(5, userHistory.getCreatedAt()); - pstmt.setString(6, userHistory.getCreateBy()); - 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 void log(final Connection conn, final UserHistory userHistory) { + final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) " + + "values (?, ?, ?, ?, ?, ?)"; + + jdbcTemplate.update(conn, 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 924e71ba5d..ec60f24aad 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -4,15 +4,21 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; +import org.springframework.transaction.support.ConnectionManager; + +import javax.sql.DataSource; +import java.sql.Connection; public class UserService { private final UserDao userDao; private final UserHistoryDao userHistoryDao; + private final DataSource dataSource; - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { + public UserService(UserDao userDao, UserHistoryDao userHistoryDao, DataSource dataSource) { this.userDao = userDao; this.userHistoryDao = userHistoryDao; + this.dataSource = dataSource; } public User findById(final long id) { @@ -27,7 +33,20 @@ public void insert(final User 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)); + + Connection connection = null; + try { + connection = dataSource.getConnection(); + connection.setAutoCommit(false); + + userDao.update(connection, user); + userHistoryDao.log(connection, new UserHistory(user, createBy)); + + connection.commit(); + } catch (Exception e) { + ConnectionManager.rollback(e, connection); + } finally { + ConnectionManager.close(connection); + } } } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 2ee12b195f..9937b42bbc 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -5,6 +5,8 @@ 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) { @@ -12,7 +14,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..594e8bd81f 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -5,24 +5,26 @@ 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 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 javax.sql.DataSource; 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 DataSource dataSource; @BeforeEach void setUp() { - this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + this.dataSource = DataSourceConfig.getInstance(); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.userDao = new UserDao(jdbcTemplate); DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); @@ -33,7 +35,7 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userService = new UserService(userDao, userHistoryDao, dataSource); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -48,7 +50,7 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userService = new UserService(userDao, userHistoryDao, dataSource); 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 1dd5baf425..93b171bdc8 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -30,6 +30,10 @@ public void update(String sql, Object... arguments) { queryTemplate(sql, ps -> ((PreparedStatement) ps).executeUpdate(), arguments); } + public void update(Connection connection, String sql, Object... arguments) { + queryTemplate(connection, sql, ps -> ((PreparedStatement) ps).executeUpdate(), arguments); + } + public List query(String sql, RowMapper rowMapper, Object... arguments) { return queryTemplate(sql, ps -> { ResultSet rs = ((PreparedStatement) ps).executeQuery(); @@ -69,6 +73,19 @@ private T queryTemplate(String sql, StatementExecutor statementExecutor, } } + private T queryTemplate(Connection connection, String sql, StatementExecutor statementExecutor, Object... arguments) { + try ( + PreparedStatement pstmt = StatementCreator.createStatement(connection, sql, arguments) + ) { + log.debug("query : {}", sql); + + return statementExecutor.execute(pstmt); + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw SqlExceptionTranslator.translateException(e); + } + } + private static class SqlExceptionTranslator { private static final Set BAD_SQL_GRAMMAR_CODES = Set.of( "07", // Dynamic SQL error diff --git a/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java b/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java new file mode 100644 index 0000000000..04f1ade8f3 --- /dev/null +++ b/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java @@ -0,0 +1,29 @@ +package org.springframework.transaction.support; + +import org.springframework.dao.DataAccessException; + +import java.sql.Connection; +import java.sql.SQLException; + +public abstract class ConnectionManager { + + private ConnectionManager() { + } + + public static void close(Connection connection) { + try { + connection.close(); + } catch (SQLException closeException) { + throw new DataAccessException("failed to close connection", closeException); + } + } + + public static void rollback(Exception e, Connection connection) { + try { + connection.rollback(); + } catch (SQLException rollbackException) { + throw new DataAccessException("failed to rollback transaction", rollbackException); + } + throw new DataAccessException(e); + } +} From 3433bb415c280d8414e2e6458804f89ccc36b662 Mon Sep 17 00:00:00 2001 From: hum02 Date: Sun, 8 Oct 2023 22:38:43 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:Transaction=20synchronization=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 7 --- .../com/techcourse/dao/UserHistoryDao.java | 5 +-- .../com/techcourse/service/UserService.java | 22 ++++++--- .../service/MockUserHistoryDao.java | 4 +- .../techcourse/service/UserServiceTest.java | 20 ++++++--- .../jdbc/core/JdbcTemplate.java | 35 ++++++--------- .../TransactionSynchronizationManager.java | 45 +++++++++++++++++-- 7 files changed, 91 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 6065d7618a..2d105f3a5f 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -7,7 +7,6 @@ import org.springframework.jdbc.core.RowMapper; import javax.sql.DataSource; -import java.sql.Connection; import java.util.List; import java.util.Optional; @@ -37,12 +36,6 @@ public void update(final User user) { jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); } - public void update(final Connection connection, final User user) { - final String sql = "update users set account = ?, password = ?, email = ? where users.id = ?"; - - jdbcTemplate.update(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); - } - public List findAll() { final String sql = "select id, account, password, email from users"; diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index c42f023922..98c0e64c0b 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -6,7 +6,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; -import java.sql.Connection; public class UserHistoryDao { @@ -22,11 +21,11 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public void log(final Connection conn, final UserHistory userHistory) { + 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(conn, sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(), + jdbcTemplate.update(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 ec60f24aad..312d0513d8 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -4,7 +4,9 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; +import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.ConnectionManager; +import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.sql.DataSource; import java.sql.Connection; @@ -30,23 +32,33 @@ public void insert(final User user) { userDao.insert(user); } - public void changePassword(final long id, final String newPassword, final String createBy) { + public void changePassword2(final long id, final String newPassword, final String createBy) { final var user = findById(id); user.changePassword(newPassword); Connection connection = null; try { - connection = dataSource.getConnection(); + connection = DataSourceUtils.getConnection(dataSource); connection.setAutoCommit(false); - userDao.update(connection, user); - userHistoryDao.log(connection, new UserHistory(user, createBy)); + userDao.update(user); + userHistoryDao.log(new UserHistory(user, createBy)); connection.commit(); } catch (Exception e) { ConnectionManager.rollback(e, connection); } finally { - ConnectionManager.close(connection); + DataSourceUtils.releaseConnection(connection, dataSource); + TransactionSynchronizationManager.unbindResource(dataSource); } } + + public void changePassword(final long id, final String newPassword, final String createBy) { + TransactionSynchronizationManager.execute(dataSource, () -> { + final var user = findById(id); + user.changePassword(newPassword); + userDao.update(user); + userHistoryDao.log(new UserHistory(user, createBy)); + }); + } } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 9937b42bbc..2ee12b195f 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -5,8 +5,6 @@ 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) { @@ -14,7 +12,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { } @Override - public void log(final Connection connection, final UserHistory userHistory) { + public void log(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 594e8bd81f..d5e97769dc 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -11,6 +11,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; +import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -35,11 +36,13 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao, dataSource); + var userService = new UserService(userDao, userHistoryDao, dataSource); final var newPassword = "qqqqq"; final var createBy = "gugu"; - userService.changePassword(1L, newPassword, createBy); + + new Thread(() -> userService.changePassword(1L, newPassword, createBy)).start(); + sleep(0.5); final var actual = userService.findById(1L); @@ -55,11 +58,18 @@ void testTransactionRollback() { final var newPassword = "newPassword"; final var createBy = "gugu"; // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. - assertThrows(DataAccessException.class, - () -> userService.changePassword(1L, newPassword, createBy)); + new Thread(() -> assertThrows(DataAccessException.class, + () -> userService.changePassword(1L, newPassword, createBy))).start(); + sleep(0.5); final var actual = userService.findById(1L); - assertThat(actual.getPassword()).isNotEqualTo(newPassword); } + + private void sleep(double seconds) { + try { + TimeUnit.MILLISECONDS.sleep((long) (seconds * 1000)); + } catch (InterruptedException ignored) { + } + } } 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 93b171bdc8..44548972b4 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -16,6 +16,8 @@ import java.util.Optional; import java.util.Set; +import static org.springframework.transaction.support.TransactionSynchronizationManager.getResource; + public class JdbcTemplate { private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class); @@ -30,10 +32,6 @@ public void update(String sql, Object... arguments) { queryTemplate(sql, ps -> ((PreparedStatement) ps).executeUpdate(), arguments); } - public void update(Connection connection, String sql, Object... arguments) { - queryTemplate(connection, sql, ps -> ((PreparedStatement) ps).executeUpdate(), arguments); - } - public List query(String sql, RowMapper rowMapper, Object... arguments) { return queryTemplate(sql, ps -> { ResultSet rs = ((PreparedStatement) ps).executeQuery(); @@ -60,29 +58,24 @@ public Optional queryForObject(String sql, RowMapper rowMapper, Object } private T queryTemplate(String sql, StatementExecutor statementExecutor, Object... arguments) { - try ( - Connection conn = dataSource.getConnection(); - PreparedStatement pstmt = StatementCreator.createStatement(conn, sql, arguments) - ) { - log.debug("query : {}", sql); - - return statementExecutor.execute(pstmt); - } catch (SQLException e) { - log.error(e.getMessage(), e); - throw SqlExceptionTranslator.translateException(e); - } - } - - private T queryTemplate(Connection connection, String sql, StatementExecutor statementExecutor, Object... arguments) { - try ( - PreparedStatement pstmt = StatementCreator.createStatement(connection, sql, arguments) - ) { + Connection conn = getResource(dataSource); + PreparedStatement pstmt = null; + try { + pstmt = StatementCreator.createStatement(conn, sql, arguments); log.debug("query : {}", sql); return statementExecutor.execute(pstmt); } catch (SQLException e) { log.error(e.getMessage(), e); throw SqlExceptionTranslator.translateException(e); + } finally { + try { + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException e) { + throw new JdbcException("cannot close Statement", e); + } } } diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 715557fc66..190b64d5e8 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -1,23 +1,62 @@ package org.springframework.transaction.support; +import org.springframework.dao.DataAccessException; + import javax.sql.DataSource; import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; import java.util.Map; public abstract class TransactionSynchronizationManager { private static final ThreadLocal> resources = new ThreadLocal<>(); - private TransactionSynchronizationManager() {} + private TransactionSynchronizationManager() { + } public static Connection getResource(DataSource key) { - return null; + if (resources.get() == null) { + try { + Map threadResource = new HashMap<>(); + threadResource.put(key, key.getConnection()); + resources.set(threadResource); + } catch (SQLException e) { + throw new DataAccessException("cannot get Connection", e); + } + } + + return resources.get().get(key); } public static void bindResource(DataSource key, Connection value) { } public static Connection unbindResource(DataSource key) { - return null; + try { + Map resource = resources.get(); + Connection connection = resource.get(key); + connection.close(); + resources.remove(); + return connection; + } catch (Exception e) { + throw new DataAccessException("connection close failed", e); + } + } + + public static void execute(DataSource dataSource, Runnable runnable) { + Connection connection = null; + try { + connection = getResource(dataSource); + connection.setAutoCommit(false); + + runnable.run(); + + connection.commit(); + } catch (Exception e) { + ConnectionManager.rollback(e, connection); + } finally { + ConnectionManager.close(connection); + } } } From 064bb276c799b2bf44f04412b9de31bf37364f38 Mon Sep 17 00:00:00 2001 From: hum02 Date: Sun, 8 Oct 2023 22:28:41 +0900 Subject: [PATCH 3/9] =?UTF-8?q?refactor:dataSourceUtils=EB=A1=9C=20conecti?= =?UTF-8?q?on=EB=B0=9B=EC=95=84=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 4 ++-- .../com/techcourse/service/UserService.java | 24 ------------------- .../java/com/techcourse/dao/UserDaoTest.java | 6 ++--- .../techcourse/service/UserServiceTest.java | 8 +++---- .../jdbc/core/JdbcTemplate.java | 5 ++-- .../support/ConnectionManager.java | 9 ++++--- .../TransactionSynchronizationManager.java | 23 ++++++++++-------- 7 files changed, 28 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 2d105f3a5f..a21d3e80e5 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -57,9 +57,9 @@ public Optional findById(final Long id) { return jdbcTemplate.queryForObject(sql, rowMapper, id); } - public List findByAccount(final String account) { + public Optional findByAccount(final String account) { final String sql = "select id, account, password, email from users where users.account = ?"; - return jdbcTemplate.query(sql, rowMapper, account); + return jdbcTemplate.queryForObject(sql, rowMapper, account); } } diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 312d0513d8..b4079ab63d 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -4,12 +4,9 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; -import org.springframework.jdbc.datasource.DataSourceUtils; -import org.springframework.transaction.support.ConnectionManager; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.sql.DataSource; -import java.sql.Connection; public class UserService { @@ -32,27 +29,6 @@ public void insert(final User user) { userDao.insert(user); } - public void changePassword2(final long id, final String newPassword, final String createBy) { - final var user = findById(id); - user.changePassword(newPassword); - - Connection connection = null; - try { - connection = DataSourceUtils.getConnection(dataSource); - connection.setAutoCommit(false); - - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); - - connection.commit(); - } catch (Exception e) { - ConnectionManager.rollback(e, connection); - } finally { - DataSourceUtils.releaseConnection(connection, dataSource); - TransactionSynchronizationManager.unbindResource(dataSource); - } - } - public void changePassword(final long id, final String newPassword, final String createBy) { TransactionSynchronizationManager.execute(dataSource, () -> { final var user = findById(id); diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index b74a42a6b8..8d8d0ed196 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -6,8 +6,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; class UserDaoTest { @@ -40,9 +38,9 @@ void findById() { @Test void findByAccount() { final var account = "gugu"; - List users = userDao.findByAccount(account); + User user = userDao.findByAccount(account).get(); - assertThat(users).extracting("account").containsOnly("gugu"); + assertThat(user.getAccount()).isEqualTo("gugu"); } @Test diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index d5e97769dc..9488597ce4 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -41,8 +41,7 @@ void testChangePassword() { final var newPassword = "qqqqq"; final var createBy = "gugu"; - new Thread(() -> userService.changePassword(1L, newPassword, createBy)).start(); - sleep(0.5); + userService.changePassword(1L, newPassword, createBy); final var actual = userService.findById(1L); @@ -57,9 +56,8 @@ void testTransactionRollback() { final var newPassword = "newPassword"; final var createBy = "gugu"; - // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. - new Thread(() -> assertThrows(DataAccessException.class, - () -> userService.changePassword(1L, newPassword, createBy))).start(); + + assertThrows(DataAccessException.class, () -> userService.changePassword(1L, newPassword, createBy)); sleep(0.5); final var actual = userService.findById(1L); 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 44548972b4..d675d2e42f 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -5,6 +5,7 @@ import org.springframework.jdbc.BadGrammarJdbcException; import org.springframework.jdbc.CannotGetJdbcConnectionException; import org.springframework.jdbc.JdbcException; +import org.springframework.jdbc.datasource.DataSourceUtils; import javax.sql.DataSource; import java.sql.Connection; @@ -16,8 +17,6 @@ import java.util.Optional; import java.util.Set; -import static org.springframework.transaction.support.TransactionSynchronizationManager.getResource; - public class JdbcTemplate { private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class); @@ -58,7 +57,7 @@ public Optional queryForObject(String sql, RowMapper rowMapper, Object } private T queryTemplate(String sql, StatementExecutor statementExecutor, Object... arguments) { - Connection conn = getResource(dataSource); + Connection conn = DataSourceUtils.getConnection(dataSource); PreparedStatement pstmt = null; try { pstmt = StatementCreator.createStatement(conn, sql, arguments); diff --git a/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java b/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java index 04f1ade8f3..f2542f8004 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/ConnectionManager.java @@ -1,7 +1,9 @@ package org.springframework.transaction.support; import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @@ -10,10 +12,11 @@ public abstract class ConnectionManager { private ConnectionManager() { } - public static void close(Connection connection) { + public static void close(DataSource dataSource, Connection connection) { try { - connection.close(); - } catch (SQLException closeException) { + DataSourceUtils.releaseConnection(connection, dataSource); + TransactionSynchronizationManager.unbindResource(dataSource); + } catch (Exception closeException) { throw new DataAccessException("failed to close connection", closeException); } } diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 190b64d5e8..c51da7b74e 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -1,10 +1,10 @@ package org.springframework.transaction.support; import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; import javax.sql.DataSource; import java.sql.Connection; -import java.sql.SQLException; import java.util.HashMap; import java.util.Map; @@ -16,20 +16,23 @@ private TransactionSynchronizationManager() { } public static Connection getResource(DataSource key) { + if (resources.get() == null) { + return null; + } + return resources.get().get(key); + } + + public static void bindResource(DataSource key, Connection value) { if (resources.get() == null) { try { Map threadResource = new HashMap<>(); - threadResource.put(key, key.getConnection()); resources.set(threadResource); - } catch (SQLException e) { + } catch (Exception e) { throw new DataAccessException("cannot get Connection", e); } } - return resources.get().get(key); - } - - public static void bindResource(DataSource key, Connection value) { + resources.get().put(key, value); } public static Connection unbindResource(DataSource key) { @@ -40,14 +43,14 @@ public static Connection unbindResource(DataSource key) { resources.remove(); return connection; } catch (Exception e) { - throw new DataAccessException("connection close failed", e); + throw new DataAccessException("resource unbind failed", e); } } public static void execute(DataSource dataSource, Runnable runnable) { Connection connection = null; try { - connection = getResource(dataSource); + connection = DataSourceUtils.getConnection(dataSource); connection.setAutoCommit(false); runnable.run(); @@ -56,7 +59,7 @@ public static void execute(DataSource dataSource, Runnable runnable) { } catch (Exception e) { ConnectionManager.rollback(e, connection); } finally { - ConnectionManager.close(connection); + ConnectionManager.close(dataSource, connection); } } } From 31ff2b139db6ad7d967f0ab3723cad94d9218085 Mon Sep 17 00:00:00 2001 From: hum02 Date: Sun, 8 Oct 2023 22:30:46 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:TxUserService=EB=A1=9C=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/AppUserService.java | 36 +++++++++++++++++++ .../com/techcourse/service/TxUserService.java | 32 +++++++++++++++++ .../com/techcourse/service/UserService.java | 36 +++---------------- .../techcourse/service/UserServiceTest.java | 14 +++++--- .../support/TransactionHandler.java | 31 ++++++++++++++++ .../TransactionSynchronizationManager.java | 17 --------- 6 files changed, 112 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/com/techcourse/service/AppUserService.java create mode 100644 app/src/main/java/com/techcourse/service/TxUserService.java create mode 100644 jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java diff --git a/app/src/main/java/com/techcourse/service/AppUserService.java b/app/src/main/java/com/techcourse/service/AppUserService.java new file mode 100644 index 0000000000..ea12997be5 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,36 @@ +package com.techcourse.service; + +import com.techcourse.dao.UserDao; +import com.techcourse.dao.UserHistoryDao; +import com.techcourse.domain.User; +import com.techcourse.domain.UserHistory; + +public class AppUserService implements UserService { + + private final UserDao userDao; + private final UserHistoryDao userHistoryDao; + + public AppUserService(UserDao userDao, UserHistoryDao userHistoryDao) { + this.userDao = userDao; + this.userHistoryDao = userHistoryDao; + } + + @Override + public User findById(final long id) { + return userDao.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 아이디의 회원이 존재하지 않습니다.")); + } + + @Override + public void insert(final User user) { + userDao.insert(user); + } + + @Override + 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)); + } +} diff --git a/app/src/main/java/com/techcourse/service/TxUserService.java b/app/src/main/java/com/techcourse/service/TxUserService.java new file mode 100644 index 0000000000..acc3f956d1 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,32 @@ +package com.techcourse.service; + +import com.techcourse.config.DataSourceConfig; +import com.techcourse.domain.User; +import org.springframework.transaction.support.TransactionHandler; + +public class TxUserService implements UserService { + + private final UserService userService; + private final TransactionHandler transactionHandler; + + public TxUserService(final UserService userService) { + this.userService = userService; + this.transactionHandler = new TransactionHandler(DataSourceConfig.getInstance()); + } + + @Override + public User findById(long id) { + return userService.findById(id); + } + + @Override + public void insert(User user) { + userService.insert(user); + } + + @Override + public void changePassword(final long id, final String newPassword, final String createBy) { + transactionHandler.handle(() -> userService.changePassword(id, newPassword, createBy)); + } +} + diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index b4079ab63d..b14dbcacbf 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,40 +1,12 @@ package com.techcourse.service; -import com.techcourse.dao.UserDao; -import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; -import com.techcourse.domain.UserHistory; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import javax.sql.DataSource; +public interface UserService { -public class UserService { + User findById(final long id); - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; - private final DataSource dataSource; + void insert(final User user); - public UserService(UserDao userDao, UserHistoryDao userHistoryDao, DataSource dataSource) { - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - this.dataSource = dataSource; - } - - public User findById(final long id) { - return userDao.findById(id) - .orElseThrow(() -> new IllegalArgumentException("해당 아이디의 회원이 존재하지 않습니다.")); - } - - public void insert(final User user) { - userDao.insert(user); - } - - public void changePassword(final long id, final String newPassword, final String createBy) { - TransactionSynchronizationManager.execute(dataSource, () -> { - final var user = findById(id); - user.changePassword(newPassword); - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); - }); - } + void changePassword(final long id, final String newPassword, final String createBy); } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index 9488597ce4..2154181c1f 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -36,7 +36,7 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - var userService = new UserService(userDao, userHistoryDao, dataSource); + final var userService = new TxUserService(new AppUserService(userDao, userHistoryDao)); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -52,15 +52,19 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao, dataSource); + // 애플리케이션 서비스 + final var appUserService = new AppUserService(userDao, userHistoryDao); + // 트랜잭션 서비스 추상화 + final var userService = new TxUserService(appUserService); final var newPassword = "newPassword"; final var createBy = "gugu"; - - assertThrows(DataAccessException.class, () -> userService.changePassword(1L, newPassword, createBy)); - sleep(0.5); + // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. + assertThrows(DataAccessException.class, + () -> userService.changePassword(1L, newPassword, createBy)); final var actual = userService.findById(1L); + assertThat(actual.getPassword()).isNotEqualTo(newPassword); } diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java new file mode 100644 index 0000000000..8c8b7818d2 --- /dev/null +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java @@ -0,0 +1,31 @@ +package org.springframework.transaction.support; + +import org.springframework.jdbc.datasource.DataSourceUtils; + +import javax.sql.DataSource; +import java.sql.Connection; + +public class TransactionHandler { + + private final DataSource dataSource; + + public TransactionHandler(DataSource dataSource) { + this.dataSource = dataSource; + } + + public void handle(Runnable runnable) { + Connection connection = null; + try { + connection = DataSourceUtils.getConnection(dataSource); + connection.setAutoCommit(false); + + runnable.run(); + + connection.commit(); + } catch (Exception e) { + ConnectionManager.rollback(e, connection); + } finally { + ConnectionManager.close(dataSource, connection); + } + } +} diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index c51da7b74e..1659db22f0 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -1,7 +1,6 @@ package org.springframework.transaction.support; import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.datasource.DataSourceUtils; import javax.sql.DataSource; import java.sql.Connection; @@ -46,20 +45,4 @@ public static Connection unbindResource(DataSource key) { throw new DataAccessException("resource unbind failed", e); } } - - public static void execute(DataSource dataSource, Runnable runnable) { - Connection connection = null; - try { - connection = DataSourceUtils.getConnection(dataSource); - connection.setAutoCommit(false); - - runnable.run(); - - connection.commit(); - } catch (Exception e) { - ConnectionManager.rollback(e, connection); - } finally { - ConnectionManager.close(dataSource, connection); - } - } } From 7e93cb33697aea6250075b9ce20e82082624d45e Mon Sep 17 00:00:00 2001 From: hum02 Date: Mon, 9 Oct 2023 00:03:26 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:deleteAll=EA=B3=BC=20insert=ED=95=9C?= =?UTF-8?q?=20=ED=84=B0=ED=94=8Cid=20=EB=A6=AC=ED=84=B4=ED=95=98=EB=8A=94?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 10 ++++- app/src/main/resources/schema.sql | 2 +- .../java/com/techcourse/dao/UserDaoTest.java | 15 ++++---- .../techcourse/service/UserServiceTest.java | 25 ++++--------- .../jdbc/core/JdbcTemplate.java | 37 +++++++++++++------ .../jdbc/core/StatementCreator.java | 8 ++++ 6 files changed, 59 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index a21d3e80e5..75754aee0c 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -24,10 +24,10 @@ public UserDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public void insert(final User user) { + public long insert(final User user) { final String sql = "insert into users (account, password, email) values (?, ?, ?)"; - jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail()); + return jdbcTemplate.updateAndReturnKey(sql, user.getAccount(), user.getPassword(), user.getEmail()); } public void update(final User user) { @@ -36,6 +36,12 @@ public void update(final User user) { jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); } + public void deleteAll() { + final String sql = "delete from users"; + + jdbcTemplate.update(sql); + } + public List findAll() { final String sql = "select id, account, password, email from users"; diff --git a/app/src/main/resources/schema.sql b/app/src/main/resources/schema.sql index ba235931f1..25c94108ad 100644 --- a/app/src/main/resources/schema.sql +++ b/app/src/main/resources/schema.sql @@ -1,6 +1,6 @@ create table if not exists users ( id bigint auto_increment, - account varchar(100) not null, + account varchar(100) not null unique, password varchar(100) not null, email varchar(100) not null, primary key(id) diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index 8d8d0ed196..1f3c7035de 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -11,14 +11,15 @@ class UserDaoTest { private UserDao userDao; + private long userId; @BeforeEach void setup() { DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); - userDao = new UserDao(DataSourceConfig.getInstance()); + userDao.deleteAll(); final var user = new User("gugu", "password", "hkkang@woowahan.com"); - userDao.insert(user); + userId = userDao.insert(user); } @Test @@ -30,7 +31,7 @@ void findAll() { @Test void findById() { - final var user = userDao.findById(1L).get(); + final var user = userDao.findById(userId).get(); assertThat(user.getAccount()).isEqualTo("gugu"); } @@ -47,9 +48,9 @@ void findByAccount() { void insert() { final var account = "insert-gugu"; final var user = new User(account, "password", "hkkang@woowahan.com"); - userDao.insert(user); + long insertedId = userDao.insert(user); - final var actual = userDao.findById(2L).get(); + final var actual = userDao.findById(insertedId).get(); assertThat(actual.getAccount()).isEqualTo(account); assertThat(actual.getEmail()).isEqualTo("hkkang@woowahan.com"); @@ -58,12 +59,12 @@ void insert() { @Test void update() { final var newPassword = "password99"; - final var user = userDao.findById(1L).get(); + final var user = userDao.findById(userId).get(); user.changePassword(newPassword); userDao.update(user); - final var actual = userDao.findById(1L).get(); + final var actual = userDao.findById(userId).get(); assertThat(actual.getPassword()).isEqualTo(newPassword); } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index 2154181c1f..22e2380c36 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -10,9 +10,6 @@ import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import javax.sql.DataSource; -import java.util.concurrent.TimeUnit; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -20,17 +17,18 @@ class UserServiceTest { private JdbcTemplate jdbcTemplate; private UserDao userDao; - private DataSource dataSource; + private long userId; @BeforeEach void setUp() { - this.dataSource = DataSourceConfig.getInstance(); + final var dataSource = DataSourceConfig.getInstance(); this.jdbcTemplate = new JdbcTemplate(dataSource); this.userDao = new UserDao(jdbcTemplate); DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); + userDao.deleteAll(); final var user = new User("gugu", "password", "hkkang@woowahan.com"); - userDao.insert(user); + userId = userDao.insert(user); } @Test @@ -41,9 +39,9 @@ void testChangePassword() { final var newPassword = "qqqqq"; final var createBy = "gugu"; - userService.changePassword(1L, newPassword, createBy); + userService.changePassword(userId, newPassword, createBy); - final var actual = userService.findById(1L); + final var actual = userService.findById(userId); assertThat(actual.getPassword()).isEqualTo(newPassword); } @@ -61,17 +59,10 @@ void testTransactionRollback() { final var createBy = "gugu"; // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. assertThrows(DataAccessException.class, - () -> userService.changePassword(1L, newPassword, createBy)); + () -> userService.changePassword(userId, newPassword, createBy)); - final var actual = userService.findById(1L); + final var actual = userService.findById(userId); assertThat(actual.getPassword()).isNotEqualTo(newPassword); } - - private void sleep(double seconds) { - try { - TimeUnit.MILLISECONDS.sleep((long) (seconds * 1000)); - } catch (InterruptedException ignored) { - } - } } 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 d675d2e42f..92fa5e7809 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -12,6 +12,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -31,6 +32,16 @@ public void update(String sql, Object... arguments) { queryTemplate(sql, ps -> ((PreparedStatement) ps).executeUpdate(), arguments); } + public long updateAndReturnKey(String sql, Object... arguments) { + try (Statement statement = queryAndReturnStatement(sql, ps -> ((PreparedStatement) ps).executeUpdate(), arguments)) { + ResultSet resultSet = statement.getGeneratedKeys(); + resultSet.next(); + return resultSet.getLong(1); + } catch (Exception e) { + throw new JdbcException("cannot get key", e); + } + } + public List query(String sql, RowMapper rowMapper, Object... arguments) { return queryTemplate(sql, ps -> { ResultSet rs = ((PreparedStatement) ps).executeQuery(); @@ -58,23 +69,27 @@ public Optional queryForObject(String sql, RowMapper rowMapper, Object private T queryTemplate(String sql, StatementExecutor statementExecutor, Object... arguments) { Connection conn = DataSourceUtils.getConnection(dataSource); - PreparedStatement pstmt = null; - try { - pstmt = StatementCreator.createStatement(conn, sql, arguments); + try (PreparedStatement pstmt = StatementCreator.createStatement(conn, sql, arguments)) { log.debug("query : {}", sql); return statementExecutor.execute(pstmt); } catch (SQLException e) { log.error(e.getMessage(), e); throw SqlExceptionTranslator.translateException(e); - } finally { - try { - if (pstmt != null) { - pstmt.close(); - } - } catch (SQLException e) { - throw new JdbcException("cannot close Statement", e); - } + } + } + + private Statement queryAndReturnStatement(String sql, StatementExecutor statementExecutor, Object... arguments) { + Connection conn = DataSourceUtils.getConnection(dataSource); + try { + PreparedStatement pstmt = StatementCreator.createStatementWithKey(conn, sql, arguments); + log.debug("query : {}", sql); + + statementExecutor.execute(pstmt); + return pstmt; + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw SqlExceptionTranslator.translateException(e); } } diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/StatementCreator.java b/jdbc/src/main/java/org/springframework/jdbc/core/StatementCreator.java index 33832e864e..b3205dd993 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/StatementCreator.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/StatementCreator.java @@ -4,6 +4,8 @@ import java.sql.PreparedStatement; import java.sql.SQLException; +import static java.sql.Statement.RETURN_GENERATED_KEYS; + public class StatementCreator { private StatementCreator() { @@ -15,6 +17,12 @@ public static PreparedStatement createStatement(Connection connection, String sq return preparedStatement; } + public static PreparedStatement createStatementWithKey(Connection connection, String sql, Object... arguments) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql, RETURN_GENERATED_KEYS); + setPreparedStatement(preparedStatement, arguments); + return preparedStatement; + } + private static void setPreparedStatement(PreparedStatement pstmt, Object... arguments) throws SQLException { for (int argumentIdx = 1; argumentIdx < arguments.length + 1; argumentIdx++) { pstmt.setObject(argumentIdx, arguments[argumentIdx - 1]); From 2aef5061c9f91327e49f2e5cb97de2e691c7595e Mon Sep 17 00:00:00 2001 From: hum02 Date: Mon, 9 Oct 2023 00:08:34 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=EC=A4=91=EB=B3=B5=EB=90=9C=20?= =?UTF-8?q?=EC=BB=A4=EB=84=A5=EC=85=98=20close=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaction/support/TransactionSynchronizationManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 1659db22f0..92c8056661 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -38,7 +38,6 @@ public static Connection unbindResource(DataSource key) { try { Map resource = resources.get(); Connection connection = resource.get(key); - connection.close(); resources.remove(); return connection; } catch (Exception e) { From b4e29bde369f27e7f9910f8cc47b59157d31590a Mon Sep 17 00:00:00 2001 From: hum02 Date: Thu, 12 Oct 2023 12:54:21 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=EC=9C=A0=EC=A0=80=EC=9D=98=20findById?= =?UTF-8?q?,insert=EB=A9=94=EC=84=9C=EB=93=9C=EC=97=90=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/service/TxUserService.java | 4 ++-- .../support/TransactionHandler.java | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/TxUserService.java b/app/src/main/java/com/techcourse/service/TxUserService.java index acc3f956d1..e5137018b7 100644 --- a/app/src/main/java/com/techcourse/service/TxUserService.java +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -16,12 +16,12 @@ public TxUserService(final UserService userService) { @Override public User findById(long id) { - return userService.findById(id); + return (User) transactionHandler.handle(() -> userService.findById(id)); } @Override public void insert(User user) { - userService.insert(user); + transactionHandler.handle(() -> userService.insert(user)); } @Override diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java index 8c8b7818d2..b47b09c94a 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionHandler.java @@ -4,6 +4,7 @@ import javax.sql.DataSource; import java.sql.Connection; +import java.util.function.Supplier; public class TransactionHandler { @@ -28,4 +29,22 @@ public void handle(Runnable runnable) { ConnectionManager.close(dataSource, connection); } } + + public T handle(Supplier supplier) { + Connection connection = null; + try { + connection = DataSourceUtils.getConnection(dataSource); + connection.setAutoCommit(false); + + T result = supplier.get(); + + connection.commit(); + return result; + } catch (Exception e) { + ConnectionManager.rollback(e, connection); + return null; + } finally { + ConnectionManager.close(dataSource, connection); + } + } } From 12c061fc5fb9ce76d97bd95fb6304fcacbd5cca6 Mon Sep 17 00:00:00 2001 From: hum02 Date: Thu, 12 Oct 2023 13:01:35 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor(TransactionSynchronizationManager)?= =?UTF-8?q?:key=EC=97=90=20=EB=A7=9E=EB=8A=94=20=EA=B0=92=20=EC=97=86?= =?UTF-8?q?=EC=9D=84=20=EC=8B=9C=20null=EB=B0=98=ED=99=98=ED=95=A8?= =?UTF-8?q?=EC=9D=84=20=EB=AA=85=EC=8B=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaction/support/TransactionSynchronizationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 92c8056661..0527f2fcde 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -18,7 +18,7 @@ public static Connection getResource(DataSource key) { if (resources.get() == null) { return null; } - return resources.get().get(key); + return resources.get().getOrDefault(key, null); } public static void bindResource(DataSource key, Connection value) { From 171a26dbce5cb11a567d556d47d40932932c6e68 Mon Sep 17 00:00:00 2001 From: hum02 Date: Thu, 12 Oct 2023 13:12:59 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:unbind=EC=8B=9C=20connection=20clo?= =?UTF-8?q?se=ED=99=95=EC=9D=B8=20=ED=9B=84=20unbind=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/service/TxUserService.java | 2 +- .../support/TransactionSynchronizationManager.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/TxUserService.java b/app/src/main/java/com/techcourse/service/TxUserService.java index e5137018b7..dc5db9a4ac 100644 --- a/app/src/main/java/com/techcourse/service/TxUserService.java +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -16,7 +16,7 @@ public TxUserService(final UserService userService) { @Override public User findById(long id) { - return (User) transactionHandler.handle(() -> userService.findById(id)); + return transactionHandler.handle(() -> userService.findById(id)); } @Override diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 0527f2fcde..fb628ea4c4 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -38,8 +38,11 @@ public static Connection unbindResource(DataSource key) { try { Map resource = resources.get(); Connection connection = resource.get(key); - resources.remove(); - return connection; + if (connection.isClosed()) { + resources.remove(); + return connection; + } + throw new DataAccessException("connection not closed. failed to unbind resource"); } catch (Exception e) { throw new DataAccessException("resource unbind failed", e); }