From 2cc0bf57b516b87fe290da287036da029702725f Mon Sep 17 00:00:00 2001 From: wyc Date: Thu, 12 Oct 2023 10:39:18 +0900 Subject: [PATCH] =?UTF-8?q?[JDBC=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=ED=98=84=ED=95=98=EA=B8=B0=20-=204?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20=EB=B2=A0=EB=B2=A0(=EC=B5=9C=EC=9B=90?= =?UTF-8?q?=EC=9A=A9)=20=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.=20=20(#590)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Service 계층 분리 * refactor: Connection을 TransactionSynchronizationManager로 관리한다. * refactor: 와일드카드 제거 * refactor: unbindResource 메서드는 반환값이 존재하지 않는다. * feat: TSM 테스트코드 작성 --- app/build.gradle | 1 + .../techcourse/dao/JdbcUserHistoryDao.java | 7 ---- .../com/techcourse/domain/UserHistory.java | 1 - .../techcourse/repository/UserRepository.java | 1 - .../techcourse/service/AppUserService.java | 37 +++++++++++++++++ .../com/techcourse/service/TxUserService.java | 40 +++++++++++++++++++ .../com/techcourse/service/UserService.java | 40 +++---------------- ...rviceTest.java => AppUserServiceTest.java} | 10 +++-- .../jdbc/core/JdbcTemplate.java | 9 ++--- .../jdbc/datasource/DataSourceUtils.java | 2 +- .../jdbc/transaction/TransactionManager.java | 36 ++++------------- .../TransactionSynchronizationManager.java | 19 +++++++-- ...TransactionSynchronizationManagerTest.java | 37 +++++++++++++++++ 13 files changed, 156 insertions(+), 84 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 rename app/src/test/java/com/techcourse/service/{UserServiceTest.java => AppUserServiceTest.java} (86%) create mode 100644 jdbc/src/test/java/org/springframework/transaction/support/TransactionSynchronizationManagerTest.java diff --git a/app/build.gradle b/app/build.gradle index 00444e6f91..9d36e34567 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation "org.apache.commons:commons-lang3:3.13.0" implementation "com.fasterxml.jackson.core:jackson-databind:2.15.2" implementation "com.h2database:h2:2.2.220" + implementation project(path: ':study') testImplementation "org.assertj:assertj-core:3.24.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2" diff --git a/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java b/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java index 3e7ab14333..123be14454 100644 --- a/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java @@ -1,15 +1,8 @@ package com.techcourse.dao; import com.techcourse.domain.UserHistory; -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 JdbcUserHistoryDao implements UserHistoryDao { private final JdbcTemplate jdbcTemplate; diff --git a/app/src/main/java/com/techcourse/domain/UserHistory.java b/app/src/main/java/com/techcourse/domain/UserHistory.java index 82c92ea649..7ae4fdb813 100644 --- a/app/src/main/java/com/techcourse/domain/UserHistory.java +++ b/app/src/main/java/com/techcourse/domain/UserHistory.java @@ -1,6 +1,5 @@ package com.techcourse.domain; -import java.sql.Connection; import java.time.LocalDateTime; public class UserHistory { diff --git a/app/src/main/java/com/techcourse/repository/UserRepository.java b/app/src/main/java/com/techcourse/repository/UserRepository.java index d3cee31a41..41549085df 100644 --- a/app/src/main/java/com/techcourse/repository/UserRepository.java +++ b/app/src/main/java/com/techcourse/repository/UserRepository.java @@ -3,7 +3,6 @@ import com.techcourse.dao.UserDao; import com.techcourse.domain.User; -import java.sql.Connection; import java.util.List; public class UserRepository { 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..2ae7914975 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,37 @@ +package com.techcourse.service; + +import com.techcourse.dao.JdbcUserHistoryDao; +import com.techcourse.dao.UserDao; +import com.techcourse.domain.User; +import com.techcourse.domain.UserHistory; +import com.techcourse.repository.UserRepository; + +public class AppUserService implements UserService { + + private final UserRepository userRepository; + private final JdbcUserHistoryDao jdbcUserHistoryDao; + + public AppUserService(final UserDao userDao, final JdbcUserHistoryDao jdbcUserHistoryDao) { + this.userRepository = new UserRepository(userDao); + this.jdbcUserHistoryDao = jdbcUserHistoryDao; + } + + @Override + public User findById(final long id) { + return userRepository.findById(id); + } + + @Override + public void insert(final User user) { + userRepository.save(user); + } + + @Override + public void changePassword(final long id, final String newPassword, final String createBy) { + final var user = findById(id); + user.changePassword(newPassword); + userRepository.update(user); + jdbcUserHistoryDao.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..161435993a --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,40 @@ +package com.techcourse.service; + +import com.techcourse.domain.User; +import org.springframework.jdbc.transaction.TransactionManager; + +import static com.techcourse.config.DataSourceConfig.getInstance; + +public class TxUserService implements UserService { + + private final UserService userService; + private final TransactionManager transactionManager; + + public TxUserService(UserService userService) { + this.userService = userService; + this.transactionManager = new TransactionManager(getInstance()); + } + + @Override + public User findById(final long id) { + return transactionManager.execute(connection -> userService.findById(id)); + } + + @Override + public void insert(final User user) { + transactionManager.execute(connection -> { + userService.insert(user); + return null; + } + ); + } + + @Override + public void changePassword(long id, String newPassword, String createBy) { + transactionManager.execute(connection -> { + 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 b20446a516..563ecd2c1e 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,41 +1,11 @@ package com.techcourse.service; -import com.techcourse.dao.UserDao; -import com.techcourse.dao.JdbcUserHistoryDao; import com.techcourse.domain.User; -import com.techcourse.domain.UserHistory; -import com.techcourse.repository.UserRepository; -import org.springframework.jdbc.transaction.TransactionManager; -import javax.sql.DataSource; +public interface UserService { -public class UserService { + User findById(final long id); + void insert(final User user); + void changePassword(final long id, final String newPassword, final String createBy); - private final TransactionManager transactionManager; - private final UserRepository userRepository; - private final JdbcUserHistoryDao jdbcUserHistoryDao; - - public UserService(final DataSource dataSource, final UserDao userDao, final JdbcUserHistoryDao jdbcUserHistoryDao) { - this.transactionManager = new TransactionManager(dataSource); - this.userRepository = new UserRepository(userDao); - this.jdbcUserHistoryDao = jdbcUserHistoryDao; - } - - public User findById(final long id) { - return userRepository.findById(id); - } - - public void insert(final User user) { - userRepository.save(user); - } - - public void changePassword(final long id, final String newPassword, final String createBy) { - transactionManager.execute(connection -> { - final var user = findById(id); - user.changePassword(newPassword); - userRepository.update(user); - jdbcUserHistoryDao.log(new UserHistory(user, createBy)); - return null; - }); - } -} +} \ No newline at end of file diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/AppUserServiceTest.java similarity index 86% rename from app/src/test/java/com/techcourse/service/UserServiceTest.java rename to app/src/test/java/com/techcourse/service/AppUserServiceTest.java index 8caca2c38b..7b6b46939b 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/AppUserServiceTest.java @@ -14,7 +14,7 @@ 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; @@ -32,7 +32,7 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new JdbcUserHistoryDao(jdbcTemplate); - final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao); + final var userService = new AppUserService(userDao, userHistoryDao); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -47,7 +47,10 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockJdbcUserHistoryDao(jdbcTemplate); - final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao); + // 애플리케이션 서비스 + final var appUserService = new AppUserService(userDao, userHistoryDao); + // 트랜잭션 서비스 추상화 + final var userService = new TxUserService(appUserService); final var newPassword = "newPassword"; final var createBy = "gugu"; @@ -59,4 +62,5 @@ void testTransactionRollback() { assertThat(actual.getPassword()).isNotEqualTo(newPassword); } + } 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 3c18d92ec7..a08f7129f0 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -1,7 +1,5 @@ package org.springframework.jdbc.core; -import org.springframework.jdbc.transaction.TransactionManager; - import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; @@ -11,13 +9,14 @@ import java.util.Optional; import static java.util.Collections.singletonList; +import static org.springframework.jdbc.datasource.DataSourceUtils.getConnection; public class JdbcTemplate { - private final TransactionManager transactionManager; + private final DataSource dataSource; public JdbcTemplate(final DataSource dataSource) { - this.transactionManager = new TransactionManager(dataSource); + this.dataSource = dataSource; } public int update(final String sql, final Object... conditions) { @@ -43,7 +42,7 @@ private T getRowByQuery(final PreparedStatement preparedStatement, final Row } private T getResult(final PreparedStatementExecutor executor, final String sql, final Object... conditions) { - Connection connection = transactionManager.getConnection(); + Connection connection = getConnection(dataSource); try ( PreparedStatement preparedStatement = connection.prepareStatement(sql); ) { 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..a1b7ce13ca 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -7,7 +7,6 @@ import java.sql.Connection; import java.sql.SQLException; -// 4단계 미션에서 사용할 것 public abstract class DataSourceUtils { private DataSourceUtils() {} @@ -30,6 +29,7 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd public static void releaseConnection(Connection connection, DataSource dataSource) { try { connection.close(); + TransactionSynchronizationManager.unbindResource(dataSource); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); } diff --git a/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java b/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java index bf6d52d8b6..4a110268e2 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java +++ b/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java @@ -4,9 +4,10 @@ import java.sql.Connection; import java.sql.SQLException; -public class TransactionManager { +import static org.springframework.jdbc.datasource.DataSourceUtils.getConnection; +import static org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection; - private static final ThreadLocal connectionInThread = new ThreadLocal<>(); +public class TransactionManager { private final DataSource dataSource; @@ -27,54 +28,33 @@ public T execute(final TransactionExecutor transactionExecutor) { } public Connection begin() { - Connection connection = getConnection(); + Connection connection = getConnection(dataSource); try { connection.setAutoCommit(false); } catch (SQLException e) { throw new RuntimeException(e); } - connectionInThread.set(connection); return connection; } public void commit() { - Connection connection = getConnection(); + Connection connection = getConnection(dataSource); try { connection.commit(); } catch (SQLException e) { throw new RuntimeException(e); } - init(connection); + releaseConnection(connection, dataSource); } public void rollback() { - Connection connection = getConnection(); + Connection connection = getConnection(dataSource); try { connection.rollback(); } catch (SQLException e) { throw new RuntimeException(e); } - init(connection); - } - - private void init(final Connection connection) { - try { - connection.close(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - connectionInThread.remove(); - } - - public Connection getConnection() { - if (connectionInThread.get() == null) { - try { - return dataSource.getConnection(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - return connectionInThread.get(); + releaseConnection(connection, dataSource); } } 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..c0c48350f7 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -2,6 +2,7 @@ import javax.sql.DataSource; import java.sql.Connection; +import java.util.HashMap; import java.util.Map; public abstract class TransactionSynchronizationManager { @@ -11,13 +12,25 @@ public abstract class TransactionSynchronizationManager { private TransactionSynchronizationManager() {} public static Connection getResource(DataSource key) { - return null; + final Map resource = resources.get(); + if (resource == null) { + return null; + } + return resource.get(key); } public static void bindResource(DataSource key, Connection value) { + Map resource = resources.get(); + if (resource == null) { + resource = new HashMap<>(); + resources.set(resource); + } + resource.put(key, value); } - public static Connection unbindResource(DataSource key) { - return null; + public static void unbindResource(DataSource key) { + final Map resource = resources.get(); + resource.remove(key); } + } diff --git a/jdbc/src/test/java/org/springframework/transaction/support/TransactionSynchronizationManagerTest.java b/jdbc/src/test/java/org/springframework/transaction/support/TransactionSynchronizationManagerTest.java new file mode 100644 index 0000000000..b7a5784fa6 --- /dev/null +++ b/jdbc/src/test/java/org/springframework/transaction/support/TransactionSynchronizationManagerTest.java @@ -0,0 +1,37 @@ +package org.springframework.transaction.support; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.sql.DataSource; +import java.sql.Connection; + +import static org.mockito.Mockito.mock; +import static org.springframework.transaction.support.TransactionSynchronizationManager.*; + +class TransactionSynchronizationManagerTest { + + DataSource dataSource = mock(DataSource.class); + Connection connection = mock(Connection.class); + + @Test + void 커넥션이_null이라면_null을_반환한다() { + Connection resource = getResource(dataSource); + Assertions.assertThat(resource).isNull(); + } + + @Test + void 커넥션을_반환한다() { + bindResource(dataSource, connection); + Connection resource = getResource(dataSource); + Assertions.assertThat(resource).isNotNull(); + } + + @Test + void 커넥션을_바인딩한다() { + bindResource(dataSource, connection); + Connection resource = getResource(dataSource); + Assertions.assertThat(resource).isNotNull(); + } + +} \ No newline at end of file