From d5726e25e2f77b8796fe02671643722f8cb450d4 Mon Sep 17 00:00:00 2001 From: 70825 Date: Tue, 10 Oct 2023 03:18:25 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 5 -- .../com/techcourse/dao/UserHistoryDao.java | 4 +- .../techcourse/service/AppUserService.java | 37 +++++++++++ .../com/techcourse/service/TxUserService.java | 36 ++++++++++ .../com/techcourse/service/UserService.java | 65 ++----------------- .../service/MockUserHistoryDao.java | 2 +- .../techcourse/service/UserServiceTest.java | 9 ++- .../jdbc/core/JdbcTemplate.java | 18 +---- .../support/TransactionExecutor.java | 7 ++ .../TransactionSynchronizationManager.java | 15 ++++- .../support/TransactionTemplate.java | 58 +++++++++++++++++ 11 files changed, 168 insertions(+), 88 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/TransactionExecutor.java create mode 100644 jdbc/src/main/java/org/springframework/transaction/support/TransactionTemplate.java diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index d39702fc6f..a885290e7a 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -27,11 +27,6 @@ public void insert(final User user) { jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail()); } - public void update(final Connection conn, final User user) { - final String sql = "update users set account = ?, password = ?, email = ? where id = ?"; - jdbcTemplate.update(conn, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); - } - public void update(final User user) { final var sql = "UPDATE users SET account = ?, password = ?, email = ? WHERE id = ?"; jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId()); diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index e1a7673efe..9beacb8ecf 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -16,9 +16,9 @@ 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/AppUserService.java b/app/src/main/java/com/techcourse/service/AppUserService.java new file mode 100644 index 0000000000..0e8e4dc021 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,37 @@ +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 java.util.NoSuchElementException; + +public class AppUserService implements UserService { + + private final UserDao userDao; + private final UserHistoryDao userHistoryDao; + + public AppUserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { + this.userDao = userDao; + this.userHistoryDao = userHistoryDao; + } + + @Override + public User findById(final Long id) { + return userDao.findById(id) + .orElseThrow(NoSuchElementException::new); + } + + @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..c16cfd8d01 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,36 @@ +package com.techcourse.service; + +import com.techcourse.domain.User; +import org.springframework.transaction.support.TransactionTemplate; + +public class TxUserService implements UserService { + + private final TransactionTemplate transactionTemplate; + private final UserService userService; + + public TxUserService(final TransactionTemplate transactionTemplate, final UserService userService) { + this.transactionTemplate = transactionTemplate; + this.userService = userService; + } + + @Override + public User findById(final Long id) { + return userService.findById(id); + } + + @Override + public void insert(final User user) { + transactionTemplate.execute(() -> { + userService.insert(user); + return null; + }); + } + + @Override + public void changePassword(final Long id, final String newPassword, final String createBy) { + transactionTemplate.execute(() -> { + userService.changePassword(id, newPassword, createBy); + return null; + }); + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 3ac313a33a..906ab052e6 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,69 +1,12 @@ 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 javax.sql.DataSource; -import org.springframework.dao.DataAccessException; -public class UserService { +public interface UserService { - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; - private final DataSource dataSource; + User findById(final Long id); - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - this.dataSource = DataSourceConfig.getInstance(); - } + void insert(final User user); - public User findById(final long id) { - return userDao.findById(id) - .orElseThrow(IllegalArgumentException::new); - } - - public void insert(final User user) { - userDao.insert(user); - } - - public void changePassword(final long id, final String newPassword, final String createBy) { - Connection conn = null; - try { - conn = dataSource.getConnection(); - conn.setAutoCommit(false); - - final var user = findById(id); - user.changePassword(newPassword); - userDao.update(conn, user); - userHistoryDao.log(dataSource.getConnection(), new UserHistory(user, createBy)); - - conn.commit(); - } catch (final SQLException e) { - rollback(conn); - throw new DataAccessException(e); - } finally { - close(conn); - } - } - - private void rollback(final Connection conn) { - try { - conn.rollback(); - } catch (final SQLException e) { - throw new DataAccessException("fail rollback"); - } - } - - private void close(final Connection conn) { - try { - conn.close(); - } catch (final SQLException e) { - throw new DataAccessException("fail close"); - } - } + void changePassword(final Long id, final String newPassword, final String createBy); } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 930200453d..3d79d30584 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -13,7 +13,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { } @Override - public void log(final Connection conn, 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 3c1a7afbad..9095cd1b8a 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.support.TransactionTemplate; class UserServiceTest { @@ -32,7 +33,9 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var transactionTemplate = new TransactionTemplate(DataSourceConfig.getInstance()); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TxUserService(transactionTemplate, appUserService); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -47,7 +50,9 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var transactionTemplate = new TransactionTemplate(DataSourceConfig.getInstance()); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TxUserService(transactionTemplate, appUserService); 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 7aa47d7592..5879e945c8 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; public class JdbcTemplate { @@ -26,10 +27,6 @@ public JdbcTemplate(final DataSource dataSource) { this.dataSource = dataSource; } - public int update(final Connection conn, final String query, final Object... args) { - return execute(conn, this::executeUpdate, query, args); - } - public int update(final String query, final Object... args) { return execute(this::executeUpdate, query, args); } @@ -42,18 +39,9 @@ private int executeUpdate(final PreparedStatement pstmt) { } } - private T execute(final Connection conn, final Function function, final String query, final Object... args) { - try (final PreparedStatement pstmt = getPreparedStatement(conn, query, args)) { - return function.apply(pstmt); - } catch (SQLException e) { - log.error(e.getMessage(), e); - throw new DataAccessException(e); - } - } - private T execute(final Function function, final String query, final Object... args) { - try (final Connection conn = dataSource.getConnection(); - final PreparedStatement pstmt = getPreparedStatement(conn, query, args)) { + final Connection conn = DataSourceUtils.getConnection(dataSource); + try (final PreparedStatement pstmt = getPreparedStatement(conn, query, args)) { return function.apply(pstmt); } catch (SQLException e) { log.error(e.getMessage(), e); diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionExecutor.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionExecutor.java new file mode 100644 index 0000000000..db9421c270 --- /dev/null +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionExecutor.java @@ -0,0 +1,7 @@ +package org.springframework.transaction.support; + +@FunctionalInterface +public interface TransactionExecutor { + + T execute(); +} 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 6a418c370d..bca49fa959 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -1,6 +1,7 @@ package org.springframework.transaction.support; import java.sql.Connection; +import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; @@ -12,13 +13,23 @@ private TransactionSynchronizationManager() { } public static Connection getResource(DataSource key) { - return null; + Map connections = resources.get(); + if (connections == null) { + return null; + } + return connections.get(key); } public static void bindResource(DataSource key, Connection value) { + Map connections = resources.get(); + if (connections == null) { + connections = new HashMap<>(); + resources.set(connections); + } + connections.put(key, value); } public static Connection unbindResource(DataSource key) { - return null; + return resources.get().remove(key); } } diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionTemplate.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionTemplate.java new file mode 100644 index 0000000000..e80222fa3c --- /dev/null +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionTemplate.java @@ -0,0 +1,58 @@ +package org.springframework.transaction.support; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.function.Supplier; +import javax.sql.DataSource; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; + +public class TransactionTemplate { + + private final DataSource dataSource; + + public TransactionTemplate(final DataSource dataSource) { + this.dataSource = dataSource; + } + + public T execute(final TransactionExecutor executor) { + final Connection connection = beginTransaction(); + try { + final T result = executor.execute(); + commit(connection); + return result; + } catch (final DataAccessException e) { + rollback(connection); + throw e; + } finally { + DataSourceUtils.releaseConnection(connection, dataSource); + TransactionSynchronizationManager.unbindResource(dataSource); + } + } + + private Connection beginTransaction() { + final Connection connection = DataSourceUtils.getConnection(dataSource); + try { + connection.setAutoCommit(false); + } catch (final SQLException e) { + throw new DataAccessException(e.getMessage(), e); + } + return connection; + } + + private void commit(final Connection connection) { + try { + connection.commit(); + } catch (final SQLException e) { + throw new DataAccessException(e.getMessage(), e); + } + } + + private void rollback(final Connection connection) { + try { + connection.rollback(); + } catch (final SQLException e) { + throw new DataAccessException(e.getMessage(), e); + } + } +}