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..94450d766c --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,32 @@ +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(final UserDao userDao, final UserHistoryDao userHistoryDao) { + this.userDao = userDao; + this.userHistoryDao = userHistoryDao; + } + + public User findById(final long id) { + return userDao.findById(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)); + } +} 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..5fcfe3f98d --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,41 @@ +package com.techcourse.service; + +import com.techcourse.domain.User; +import javax.sql.DataSource; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +public class TxUserService implements UserService { + + private final UserService userService; + private final DataSource dataSource; + + public TxUserService(final UserService userService, final DataSource dataSource) { + this.userService = userService; + this.dataSource = dataSource; + } + + @Override + public User findById(final long id) { + return userService.findById(id); + } + + @Override + public void insert(final User user) { + userService.insert(user); + } + + @Override + public void changePassword(final long id, final String newPassword, final String createBy) { + TransactionSynchronizationManager.start(); + + try { + userService.changePassword(id, newPassword, createBy); + TransactionSynchronizationManager.commit(dataSource); + } catch (Exception e) { + TransactionSynchronizationManager.rollback(dataSource); + throw e; + } finally { + TransactionSynchronizationManager.release(dataSource); + } + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 51a88bfbc8..669f722504 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,43 +1,11 @@ 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.jdbc.core.TransactionManager; -public class UserService { +public interface UserService { - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; + User findById(final long id); + void insert(final User user); + void changePassword(final long id, final String newPassword, final String createBy); - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - } - - public User findById(final long id) { - return userDao.findById(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); - - TransactionManager.start(); - try { - user.changePassword(newPassword); - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); - TransactionManager.commit(); - } catch (Exception e) { - TransactionManager.rollback(); - throw e; - } finally { - TransactionManager.release(); - } - } } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/AppUserServiceTest.java similarity index 78% rename from app/src/test/java/com/techcourse/service/UserServiceTest.java rename to app/src/test/java/com/techcourse/service/AppUserServiceTest.java index 83bc1d3505..86ccd96540 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/AppUserServiceTest.java @@ -5,6 +5,7 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; +import javax.sql.DataSource; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.junit.jupiter.api.BeforeEach; @@ -13,17 +14,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -class UserServiceTest { +class AppUserServiceTest { private JdbcTemplate jdbcTemplate; private UserDao userDao; + private DataSource dataSource; @BeforeEach void setUp() { - this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + dataSource = DataSourceConfig.getInstance(); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.userDao = new UserDao(jdbcTemplate); - DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); + DatabasePopulatorUtils.execute(dataSource); final var user = new User("gugu", "password", "hkkang@woowahan.com"); userDao.insert(user); } @@ -31,7 +34,7 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userService = new AppUserService(userDao, userHistoryDao); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -46,7 +49,8 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final TxUserService userService = new TxUserService(appUserService, 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 33d271e05d..8bf1eb96d2 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -10,6 +10,8 @@ import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; public class JdbcTemplate { @@ -42,7 +44,7 @@ public Optional query(final String sql, RowMapper rowMapper, final Obj return Optional.ofNullable(result.get(0)); } catch (SQLException e) { log.error(e.getMessage(), e); - throw new RuntimeException(e); + throw new DataAccessException(e); } } @@ -71,7 +73,7 @@ public List queryPlural( return extractResultFrom(resultSet, rowMapper); } catch (SQLException e) { log.error(e.getMessage(), e); - throw new RuntimeException(e); + throw new DataAccessException(e); } } @@ -88,7 +90,7 @@ private List extractResultFrom(final ResultSet resultSet, final RowMapper public void execute(final String sql, final Object... values) { - final Connection connection = TransactionManager.getConnection(dataSource); + final Connection connection = DataSourceUtils.getConnection(dataSource); try ( final PreparedStatement pstmt = connection.prepareStatement(sql) @@ -101,7 +103,7 @@ public void execute(final String sql, final Object... values) { pstmt.executeUpdate(); } catch (SQLException e) { log.error(e.getMessage(), e); - throw new RuntimeException(e); + throw new DataAccessException(e); } } } diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/TransactionManager.java b/jdbc/src/main/java/org/springframework/jdbc/core/TransactionManager.java deleted file mode 100644 index 20ae9aef2a..0000000000 --- a/jdbc/src/main/java/org/springframework/jdbc/core/TransactionManager.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.springframework.jdbc.core; - -import java.sql.Connection; -import java.sql.SQLException; -import javax.sql.DataSource; - -public class TransactionManager { - - private static final ThreadLocal resources = new ThreadLocal<>(); - private static final ThreadLocal actualTransactionActive = new ThreadLocal<>(); - - private TransactionManager() { - } - - public static void start() { - actualTransactionActive.set(Boolean.TRUE); - } - - public static Connection getConnection(final DataSource dataSource) { - if (resources.get() == null) { - createConnectionFrom(dataSource); - } - - final Connection connection = resources.get(); - - if (isTransactionActive()) { - setAutoCommit(connection); - } - - return connection; - } - - private static void setAutoCommit(final Connection connection) { - try { - connection.setAutoCommit(false); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - private static void createConnectionFrom(final DataSource dataSource) { - try { - resources.set(dataSource.getConnection()); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - private static boolean isTransactionActive() { - final Boolean isActive = actualTransactionActive.get(); - return isActive != null && isActive; - } - - public static void commit() { - final Connection connection = resources.get(); - - try { - connection.commit(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - public static void rollback() { - try { - if (isTransactionActive()) { - final Connection connection = resources.get(); - connection.rollback(); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - - public static void release() { - final Connection connection = resources.get(); - - try { - connection.close(); - } catch (SQLException e) { - throw new RuntimeException(e); - } finally { - actualTransactionActive.remove(); - resources.remove(); - } - } -} diff --git a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java index 3c40bfec52..8f164810e6 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -1,5 +1,6 @@ package org.springframework.jdbc.datasource; +import java.util.function.Function; import org.springframework.jdbc.CannotGetJdbcConnectionException; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -10,28 +11,29 @@ // 4단계 미션에서 사용할 것 public abstract class DataSourceUtils { - private DataSourceUtils() {} + private DataSourceUtils() { + } - public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { - Connection connection = TransactionSynchronizationManager.getResource(dataSource); - if (connection != null) { - return connection; - } + public static Connection getConnection(DataSource dataSource) { + return TransactionSynchronizationManager.getResource(dataSource) + .orElseGet(() -> createConnection(dataSource)); + } - try { - connection = dataSource.getConnection(); - TransactionSynchronizationManager.bindResource(dataSource, connection); - return connection; - } catch (SQLException ex) { - throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); - } + private static Connection createConnection(final DataSource dataSource) { + try { + final Connection connection = dataSource.getConnection(); + TransactionSynchronizationManager.bindResource(dataSource, connection); + return connection; + } catch (SQLException ex) { + throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); } + } - public static void releaseConnection(Connection connection, DataSource dataSource) { - try { - connection.close(); - } catch (SQLException ex) { - throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); - } + public static void releaseConnection(Connection connection) { + try { + connection.close(); + } catch (SQLException ex) { + throw new CannotGetJdbcConnectionException("Failed to close JDBC 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 715557fc66..cd62362f67 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,107 @@ package org.springframework.transaction.support; -import javax.sql.DataSource; import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import javax.sql.DataSource; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; + +public class TransactionSynchronizationManager { + + private static final ThreadLocal> resources = new ThreadLocal<>(); + private static final ThreadLocal actualTransactionActive = new ThreadLocal<>(); + + private TransactionSynchronizationManager() { + } + + public static void start() { + actualTransactionActive.set(Boolean.TRUE); + } + + private static Connection getConnection(final DataSource dataSource) { + final Map resource = getResources(); + + final Connection connection = resource.get(dataSource); + + if (connection == null) { + return null; + } + + if (isTransactionActive()) { + begin(connection); + } -public abstract class TransactionSynchronizationManager { + return connection; + } - private static final ThreadLocal> resources = new ThreadLocal<>(); + public static Optional getResource(final DataSource dataSource) { + if (getResources() == null) { + return Optional.empty(); + } + + return Optional.ofNullable(getConnection(dataSource)); + } + + public static void bindResource(final DataSource dataSource, final Connection connection) { + if (getResources() == null) { + resources.set(new HashMap<>()); + } + + getResources() + .put(dataSource, connection); + } + + private static void begin(final Connection connection) { + try { + connection.setAutoCommit(false); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + private static boolean isTransactionActive() { + final Boolean isActive = actualTransactionActive.get(); + return isActive != null && isActive; + } - private TransactionSynchronizationManager() {} + public static void commit(final DataSource dataSource) { + final Connection connection = getResources().get(dataSource); - public static Connection getResource(DataSource key) { - return null; + try { + connection.commit(); + } catch (SQLException e) { + throw new DataAccessException(e); } + } + + public static void rollback(final DataSource dataSource) { + final Connection connection = getResources().get(dataSource); - public static void bindResource(DataSource key, Connection value) { + try { + if (isTransactionActive()) { + connection.rollback(); + } + } catch (SQLException e) { + throw new DataAccessException(e); } + } - public static Connection unbindResource(DataSource key) { - return null; + public static void release(final DataSource dataSource) { + final Connection connection = getResources().get(dataSource); + + if (connection == null) { + throw new DataAccessException("not exist connection"); } + + DataSourceUtils.releaseConnection(connection); + actualTransactionActive.remove(); + getResources().remove(dataSource); + } + + private static Map getResources() { + return resources.get(); + } }