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..e01ad1aae1 --- /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) { + User 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..c668d78dd4 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,111 @@ +package com.techcourse.service; + +import com.techcourse.config.DataSourceConfig; +import com.techcourse.domain.User; +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; + +public class TxUserService implements UserService { + + private final AppUserService appUserService; + private final DataSource datasource = DataSourceConfig.getInstance(); + + public TxUserService(AppUserService appUserService) { + this.appUserService = appUserService; + } + + @Override + public User findById(long id) { + return execute(() -> appUserService.findById(id)); + } + + @Override + public void insert(User user) { + execute(() -> appUserService.insert(user)); + } + + @Override + public void changePassword(long id, String newPassword, String createBy) { + execute(() -> appUserService.changePassword(id, newPassword, createBy)); + } + + private void execute(ExecutableWithoutReturn executable) { + try (TransactionExecutor executor = TransactionExecutor.initTransaction(datasource)) { + try { + executable.execute(); + executor.commit(); + } catch (Exception e) { + executor.rollback(); + throw new DataAccessException(); + } + } + } + + private T execute(ExecutableWithReturn executable) { + try (TransactionExecutor executor = TransactionExecutor.initTransaction(datasource)) { + try { + T result = executable.execute(); + executor.commit(); + return result; + } catch (Exception e) { + executor.rollback(); + throw new DataAccessException(); + } + } + } + + @FunctionalInterface + private interface ExecutableWithoutReturn { + void execute(); + } + + @FunctionalInterface + private interface ExecutableWithReturn { + T execute(); + } + + private static class TransactionExecutor implements AutoCloseable { + + private final DataSource dataSource; + private final Connection connection; + + private TransactionExecutor(DataSource dataSource, Connection connection) { + this.dataSource = dataSource; + this.connection = connection; + } + + public static TransactionExecutor initTransaction(DataSource dataSource) { + try { + Connection connection = DataSourceUtils.getConnection(dataSource); + connection.setAutoCommit(false); + return new TransactionExecutor(dataSource, connection); + } catch (SQLException e) { + throw new RuntimeException(); + } + } + + public void commit() { + try { + connection.commit(); + } catch (SQLException e) { + throw new RuntimeException(); + } + } + + public void rollback() { + try { + connection.rollback(); + } catch (SQLException e) { + throw new RuntimeException(); + } + } + + @Override + public void close() { + DataSourceUtils.releaseConnection(dataSource); + } + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 11df6c21e1..42d01bf760 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,61 +1,10 @@ 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.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.DataAccessException; -import org.springframework.transaction.support.TransactionSynchronizationManager; -public class UserService { +public interface 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) { - 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) { - DataSource dataSource = DataSourceConfig.getInstance(); - Connection conn = null; - try { - conn = dataSource.getConnection(); - conn.setAutoCommit(false); - TransactionSynchronizationManager.bindResource(dataSource, conn); - User user = findById(id); - user.changePassword(newPassword); - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); - conn.commit(); - } catch (Exception e) { - log.error(e.getMessage(), e); - if (conn != null) { - try { - TransactionSynchronizationManager.unbindResource(dataSource); - conn.rollback(); - conn.close(); - } catch (SQLException ex) { - log.error(ex.getMessage(), ex); - } - } - throw new DataAccessException(); - } - } + User findById(final long id); + void insert(final User user); + 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 83bc1d3505..2ca423005c 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -31,7 +31,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 +46,10 @@ 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 var userService = new TxUserService(appUserService); final var newPassword = "newPassword"; final var createBy = "gugu"; diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/InstantiateUtil.java b/jdbc/src/main/java/org/springframework/jdbc/core/InstantiateUtil.java deleted file mode 100644 index 115f1b0327..0000000000 --- a/jdbc/src/main/java/org/springframework/jdbc/core/InstantiateUtil.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.springframework.jdbc.core; - -import java.lang.reflect.Constructor; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; - -public class InstantiateUtil { - - public static T instantiate(ResultSet rs, Class requiredType, Object[] initArgs) - throws Exception { - ResultSetMetaData metaData = rs.getMetaData(); - int columnCount = metaData.getColumnCount(); - - Class[] columnTypes = new Class[columnCount]; - for (int i = 1; i <= columnCount; i++) { - int columnType = metaData.getColumnType(i); - columnTypes[i - 1] = ColumnTypes.convertToClass(columnType); - } - Constructor constructor = requiredType.getDeclaredConstructor(columnTypes); - return requiredType.cast(constructor.newInstance(initArgs)); - } -} 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 fb024480a4..e24b07f21b 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -1,8 +1,10 @@ package org.springframework.jdbc.core; +import java.lang.reflect.Constructor; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -11,7 +13,7 @@ import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.IncorrectResultSizeDataAccessException; -import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.jdbc.datasource.DataSourceUtils; public class JdbcTemplate { @@ -23,32 +25,49 @@ public JdbcTemplate(final DataSource dataSource) { this.dataSource = dataSource; } - public int update(String sql, Object... args) throws DataAccessException { + public int update(String sql, Object... args) { return execute(sql, (pstmt) -> { prepareStatement(pstmt, args); return pstmt.executeUpdate(); }); } - public T queryForObject(String sql, Class requiredType, Object... args) - throws DataAccessException { + public T queryForObject(String sql, Class requiredType, Object... args) { return execute(sql, (pstmt) -> { prepareStatement(pstmt, args); try (ResultSet rs = pstmt.executeQuery()) { - int columnCount = rs.getMetaData().getColumnCount(); - if (rs.first() && rs.isLast()) { - Object[] initArgs = new Object[columnCount]; - for (int i = 1; i <= columnCount; i++) { - initArgs[i - 1] = rs.getObject(i); - } - return InstantiateUtil.instantiate(rs, requiredType, initArgs); - } - throw new IncorrectResultSizeDataAccessException(); + return instantiate(rs, requiredType); } }); } - public List query(String sql, RowMapper rowMapper) throws DataAccessException { + private T instantiate(ResultSet rs, Class requiredType) throws Exception { + int columnCount = rs.getMetaData().getColumnCount(); + if (rs.first() && rs.isLast()) { + Object[] initArgs = new Object[columnCount]; + for (int i = 1; i <= columnCount; i++) { + initArgs[i - 1] = rs.getObject(i); + } + return instantiate(rs, requiredType, initArgs); + } + throw new IncorrectResultSizeDataAccessException(); + } + + private T instantiate(ResultSet rs, Class requiredType, Object[] initArgs) + throws Exception { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + Class[] columnTypes = new Class[columnCount]; + for (int i = 1; i <= columnCount; i++) { + int columnType = metaData.getColumnType(i); + columnTypes[i - 1] = ColumnTypes.convertToClass(columnType); + } + Constructor constructor = requiredType.getDeclaredConstructor(columnTypes); + return requiredType.cast(constructor.newInstance(initArgs)); + } + + public List query(String sql, RowMapper rowMapper) { return execute(sql, (pstmt) -> { try (ResultSet resultSet = pstmt.executeQuery()) { List results = new ArrayList<>(); @@ -67,34 +86,14 @@ private void prepareStatement(PreparedStatement pstmt, Object[] args) throws SQL } private T execute(String sql, StatementExecution function) { - Connection conn = null; + Connection conn; try { - conn = getConnection(); + conn = DataSourceUtils.getConnection(dataSource); PreparedStatement pstmt = conn.prepareStatement(sql); return function.apply(pstmt); } catch (Exception e) { log.error(e.getMessage(), e); throw new DataAccessException(e); - } finally { - try { - if (isConnectionManuallyInstantiated(conn)) { - conn.close(); - } - } catch (SQLException e) { - log.error(e.getMessage(), e); - throw new DataAccessException(e); - } - } - } - - private Connection getConnection() throws SQLException { - if (TransactionSynchronizationManager.getResource(dataSource) != null) { - return TransactionSynchronizationManager.getResource(dataSource); } - return dataSource.getConnection(); - } - - private boolean isConnectionManuallyInstantiated(Connection conn) { - return conn != null && TransactionSynchronizationManager.getResource(dataSource) == null; } } 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..3ce77bef7f 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -27,8 +27,9 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd } } - public static void releaseConnection(Connection connection, DataSource dataSource) { + public static void releaseConnection(DataSource dataSource) { try { + Connection connection = TransactionSynchronizationManager.unbindResource(dataSource); connection.close(); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");