From 756567ffd575ab4ffb0242dd9982260a5a5dac4b Mon Sep 17 00:00:00 2001 From: songusika Date: Mon, 9 Oct 2023 15:26:03 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9D=EC=8B=9C=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=EC=9D=84=20=ED=99=9C=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=97=90=EC=84=9C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/AppUserService.java | 33 +++++++++++++++ .../com/techcourse/service/TxUserService.java | 30 ++++++++++++++ .../com/techcourse/service/UserService.java | 41 ++----------------- ...erviceTest.java => TxUserServiceTest.java} | 14 ++++--- 4 files changed, 75 insertions(+), 43 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 => TxUserServiceTest.java} (82%) 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..1b0344e763 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,33 @@ +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.support.TransactionTemplate; + +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..4894c64c9c --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,30 @@ +package com.techcourse.service; + +import com.techcourse.domain.User; +import org.springframework.jdbc.support.TransactionTemplate; + +public class TxUserService implements UserService { + + private final UserService userService; + private final TransactionTemplate transactionTemplate; + + public TxUserService(final UserService userService, final TransactionTemplate transactionTemplate) { + this.userService = userService; + this.transactionTemplate = transactionTemplate; + } + + @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) { + transactionTemplate.execute(() -> 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 d76c6e83a7..935aabd8b1 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,46 +1,13 @@ 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.support.TransactionTemplate; -public class UserService { +public interface UserService { - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; - private final TransactionTemplate transactionTemplate; + User findById(final long id); - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final TransactionTemplate transactionTemplate) { - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - this.transactionTemplate = transactionTemplate; - } + void insert(final User user); - 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); - - transactionTemplate.execute(() -> { - updateUser(user); - writeUserHistory(new UserHistory(user, createBy)); - }); - } - - private void updateUser(final User user) { - userDao.update(user); - } - - private void writeUserHistory(final UserHistory userHistory) { - userHistoryDao.log(userHistory); - } + 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/TxUserServiceTest.java similarity index 82% rename from app/src/test/java/com/techcourse/service/UserServiceTest.java rename to app/src/test/java/com/techcourse/service/TxUserServiceTest.java index 3d310b1784..fe8765c8a9 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/TxUserServiceTest.java @@ -1,7 +1,7 @@ package com.techcourse.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import com.techcourse.config.DataSourceConfig; import com.techcourse.dao.UserDao; @@ -9,19 +9,19 @@ import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; 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 org.springframework.jdbc.support.TransactionTemplate; -@Disabled -class UserServiceTest { + +class TxUserServiceTest { private JdbcTemplate jdbcTemplate; private UserDao userDao; private UserHistoryDao userHistoryDao; private TransactionTemplate transactionTemplate; + private UserService appUserService; @BeforeEach void setUp() { @@ -29,6 +29,7 @@ void setUp() { this.userDao = new UserDao(jdbcTemplate); this.userHistoryDao = new UserHistoryDao(jdbcTemplate); this.transactionTemplate = new TransactionTemplate(DataSourceConfig.getInstance()); + this.appUserService = new AppUserService(userDao, userHistoryDao); DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); final var user = new User("gugu", "password", "hkkang@woowahan.com"); @@ -37,7 +38,7 @@ void setUp() { @Test void testChangePassword() { - final var userService = new UserService(userDao, userHistoryDao, transactionTemplate); + final var userService = new TxUserService(appUserService, transactionTemplate); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -52,7 +53,8 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao, transactionTemplate); + final var mockAppUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TxUserService(mockAppUserService, transactionTemplate); final var newPassword = "newPassword"; final var createBy = "gugu"; From ea13b73dd20d8e5f751d8a4e99b7d4c68b1215e2 Mon Sep 17 00:00:00 2001 From: songusika Date: Mon, 9 Oct 2023 16:25:34 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20SQL=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EB=A5=BC=20=EB=A7=8C=EB=93=9C=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20JdbcTemplate=EB=A1=9C=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jdbc/core/JdbcTemplate.java | 17 ++++++++++++++++- .../jdbc/support/ConnectionHolder.java | 18 ++++-------------- 2 files changed, 20 insertions(+), 15 deletions(-) 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 f04ef25a1f..24415a7257 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -2,6 +2,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -16,6 +17,7 @@ public class JdbcTemplate { private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class); + private static final int QUERY_PARAMETER_STEP = 1; private final DataSource dataSource; @@ -50,7 +52,7 @@ public Optional queryForObject(final String sql, final RowMapper rowMa private T execute(final String sql, final PreparedStatementCallback callback, final Object... parameters) { try (final ConnectionHolder connectionHolder = TransactionManager.getConnectionHolder(dataSource); - final PreparedStatement preparedStatement = connectionHolder.createPrepareStatement(sql, parameters); + final PreparedStatement preparedStatement = createPrepareStatement(connectionHolder, sql, parameters); ) { return callback.callback(preparedStatement); } catch (final Exception e) { @@ -58,4 +60,17 @@ private T execute(final String sql, final PreparedStatementCallback callb throw new DataAccessException(e); } } + + public PreparedStatement createPrepareStatement( + final ConnectionHolder connectionHolder, + final String sql, final Object[] parameters + ) throws SQLException { + final PreparedStatement preparedStatement = connectionHolder.getConnection().prepareStatement(sql); + + for (int index = 0; index < parameters.length; index++) { + preparedStatement.setObject(index + QUERY_PARAMETER_STEP, parameters[index]); + } + + return preparedStatement; + } } diff --git a/jdbc/src/main/java/org/springframework/jdbc/support/ConnectionHolder.java b/jdbc/src/main/java/org/springframework/jdbc/support/ConnectionHolder.java index e7156a272c..9a3cd4a3dd 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/support/ConnectionHolder.java +++ b/jdbc/src/main/java/org/springframework/jdbc/support/ConnectionHolder.java @@ -1,13 +1,9 @@ package org.springframework.jdbc.support; import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; public class ConnectionHolder implements AutoCloseable { - private static final int QUERY_PARAMETER_STEP = 1; - private final Connection connection; private final boolean isTransactionActive; @@ -16,16 +12,6 @@ private ConnectionHolder(final Connection connection, final boolean isTransactio this.isTransactionActive = isTransactionActive; } - public PreparedStatement createPrepareStatement(final String sql, final Object[] parameters) throws SQLException { - final PreparedStatement preparedStatement = connection.prepareStatement(sql); - - for (int index = 0; index < parameters.length; index++) { - preparedStatement.setObject(index + QUERY_PARAMETER_STEP, parameters[index]); - } - - return preparedStatement; - } - public static ConnectionHolder activeTransaction(final Connection connection) { return new ConnectionHolder(connection, true); } @@ -34,6 +20,10 @@ public static ConnectionHolder disableTransaction(final Connection connection) { return new ConnectionHolder(connection, false); } + public Connection getConnection() { + return connection; + } + @Override public void close() throws Exception { if (!isTransactionActive) { From aa129d5aadaaf2f83b52f2d90ee8439a5e130c4c Mon Sep 17 00:00:00 2001 From: songusika Date: Mon, 9 Oct 2023 16:25:51 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20=EC=84=B8?= =?UTF-8?q?=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/springframework/jdbc/core/JdbcTemplate.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 24415a7257..4a17b8912c 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -55,9 +55,12 @@ private T execute(final String sql, final PreparedStatementCallback callb final PreparedStatement preparedStatement = createPrepareStatement(connectionHolder, sql, parameters); ) { return callback.callback(preparedStatement); - } catch (final Exception e) { + } catch (final SQLException e) { log.error(e.getMessage(), e); throw new DataAccessException(e); + } catch (final Exception e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e); } } From 6153ac878b00ff27199c1cd0948e70917ed6a29c Mon Sep 17 00:00:00 2001 From: songusika Date: Mon, 9 Oct 2023 16:26:14 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20ThreadLocal=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/springframework/jdbc/support/TransactionManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java b/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java index 3e62022278..0df031f072 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java +++ b/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java @@ -70,9 +70,8 @@ public static void commit(final DataSource dataSource) { action(() -> { if (connections.get().containsKey(dataSource)) { connections.get().get(dataSource).commit(); + clear(dataSource); } - - clear(dataSource); }, e -> new CannotCommitException(e.getMessage())); } From dd109c29d0d7bd334bec9f572c289d8dd6b72496 Mon Sep 17 00:00:00 2001 From: songusika Date: Mon, 9 Oct 2023 16:44:53 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EB=AA=A8=EB=93=A0=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=ED=95=9C=EB=B2=88=EC=97=90=20?= =?UTF-8?q?=EB=8F=8C=EB=A6=B4=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/com/techcourse/dao/UserDaoTest.java | 12 ++++++++++++ .../com/techcourse/service/TxUserServiceTest.java | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index dfa2afae53..7fb7d22d52 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -1,12 +1,16 @@ package com.techcourse.dao; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.spliterator; import com.techcourse.config.DataSourceConfig; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; class UserDaoTest { @@ -41,6 +45,7 @@ void findByAccount() { final var user = userDao.findByAccount(account); assertThat(user.getAccount()).isEqualTo(account); + System.out.println(user.getId()); } @Test @@ -66,4 +71,11 @@ void update() { assertThat(actual.getPassword()).isEqualTo(newPassword); } + + @AfterAll + static void truncate() { + final JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + final String truncate = "TRUNCATE TABLE users RESTART IDENTITY"; + jdbcTemplate.update(truncate); + } } diff --git a/app/src/test/java/com/techcourse/service/TxUserServiceTest.java b/app/src/test/java/com/techcourse/service/TxUserServiceTest.java index fe8765c8a9..ac3cbf4165 100644 --- a/app/src/test/java/com/techcourse/service/TxUserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/TxUserServiceTest.java @@ -1,13 +1,14 @@ package com.techcourse.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.techcourse.config.DataSourceConfig; import com.techcourse.dao.UserDao; import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.dao.DataAccessException; @@ -66,4 +67,11 @@ void testTransactionRollback() { assertThat(actual.getPassword()).isNotEqualTo(newPassword); } + + @AfterAll + static void truncate() { + final JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + final String truncate = "TRUNCATE TABLE users RESTART IDENTITY"; + jdbcTemplate.update(truncate); + } } From 099cbe5dfb71361d38c44ce7f4b8a4e9fe60ae1f Mon Sep 17 00:00:00 2001 From: songusika Date: Fri, 13 Oct 2023 12:32:46 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=EB=A9=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/test/java/com/techcourse/dao/UserDaoTest.java | 1 - app/src/test/java/com/techcourse/service/TxUserServiceTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index 7fb7d22d52..fcfb2482b1 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -45,7 +45,6 @@ void findByAccount() { final var user = userDao.findByAccount(account); assertThat(user.getAccount()).isEqualTo(account); - System.out.println(user.getId()); } @Test diff --git a/app/src/test/java/com/techcourse/service/TxUserServiceTest.java b/app/src/test/java/com/techcourse/service/TxUserServiceTest.java index ac3cbf4165..46ddd14f83 100644 --- a/app/src/test/java/com/techcourse/service/TxUserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/TxUserServiceTest.java @@ -15,7 +15,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.TransactionTemplate; - class TxUserServiceTest { private JdbcTemplate jdbcTemplate; From dcbf0e1955b684987011a673d218593d3ba03e88 Mon Sep 17 00:00:00 2001 From: songusika Date: Tue, 17 Oct 2023 01:13:46 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20TransactionManager=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20TransactionSynchronizationManager=20?= =?UTF-8?q?=EC=99=80=20DataSourceUtils=20=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jdbc/core/JdbcTemplate.java | 4 +- .../jdbc/datasource/DataSourceUtils.java | 35 ++---- .../jdbc/support/TransactionManager.java | 86 +------------- .../TransactionSynchronizationManager.java | 107 +++++++++++++++-- .../java/nextstep/jdbc/JdbcTemplateTest.java | 4 +- .../support/TransactionManagerTest.java | 48 -------- ...TransactionSynchronizationManagerTest.java | 110 ++++++++++++++++++ .../testUtil/TestDataSourceConfig.java | 2 +- .../testUtil/TestDatabaseUtils.java | 2 +- 9 files changed, 229 insertions(+), 169 deletions(-) delete mode 100644 jdbc/src/test/java/nextstep/support/TransactionManagerTest.java create mode 100644 jdbc/src/test/java/org/springframework/transaction/support/TransactionSynchronizationManagerTest.java rename jdbc/src/test/java/{nextstep => }/testUtil/TestDataSourceConfig.java (96%) rename jdbc/src/test/java/{nextstep => }/testUtil/TestDatabaseUtils.java (97%) 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 4a17b8912c..623e611457 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -11,8 +11,8 @@ import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.exception.IncorrectResultSizeDataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.support.ConnectionHolder; -import org.springframework.jdbc.support.TransactionManager; public class JdbcTemplate { @@ -51,7 +51,7 @@ public Optional queryForObject(final String sql, final RowMapper rowMa } private T execute(final String sql, final PreparedStatementCallback callback, final Object... parameters) { - try (final ConnectionHolder connectionHolder = TransactionManager.getConnectionHolder(dataSource); + try (final ConnectionHolder connectionHolder = DataSourceUtils.getConnectionHolder(dataSource); final PreparedStatement preparedStatement = createPrepareStatement(connectionHolder, sql, parameters); ) { return callback.callback(preparedStatement); 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..96b275b144 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -1,37 +1,22 @@ package org.springframework.jdbc.datasource; +import java.sql.Connection; +import javax.sql.DataSource; import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.support.ConnectionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.SQLException; - -// 4단계 미션에서 사용할 것 public abstract class DataSourceUtils { - private DataSourceUtils() {} - - public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { - Connection connection = TransactionSynchronizationManager.getResource(dataSource); - if (connection != null) { - return connection; - } - - try { - connection = dataSource.getConnection(); - TransactionSynchronizationManager.bindResource(dataSource, connection); - return connection; - } catch (SQLException ex) { - throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); - } + private DataSourceUtils() { } - public static void releaseConnection(Connection connection, DataSource dataSource) { - try { - connection.close(); - } catch (SQLException ex) { - throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); + public static ConnectionHolder getConnectionHolder(DataSource dataSource) throws CannotGetJdbcConnectionException { + final Connection connection = TransactionSynchronizationManager.getResource(dataSource); + if (TransactionSynchronizationManager.isTransactionBegan()) { + return ConnectionHolder.activeTransaction(connection); } + + return ConnectionHolder.disableTransaction(connection); } } diff --git a/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java b/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java index 0df031f072..3786e98783 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java +++ b/jdbc/src/main/java/org/springframework/jdbc/support/TransactionManager.java @@ -1,97 +1,19 @@ package org.springframework.jdbc.support; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; import javax.sql.DataSource; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.support.exception.CannotBeginTransactionException; -import org.springframework.jdbc.support.exception.CannotCommitException; -import org.springframework.jdbc.support.exception.CannotGetConnectionException; -import org.springframework.jdbc.support.exception.CannotRollbackException; +import org.springframework.transaction.support.TransactionSynchronizationManager; public class TransactionManager { - private static final ThreadLocal> connections = ThreadLocal.withInitial(HashMap::new); - private static final ThreadLocal isTransactionActive = ThreadLocal.withInitial(() -> false); - public static void beginTransaction() { - isTransactionActive.set(true); - } - - public static ConnectionHolder getConnectionHolder(final DataSource dataSource) { - if (isTransactionActive.get()) { - final Connection connection = guaranteeConnection(dataSource); - beginTransaction(connection); - return ConnectionHolder.activeTransaction(connection); - } - - return ConnectionHolder.disableTransaction(getConnection(dataSource)); - } - - private static Connection guaranteeConnection(final DataSource dataSource) { - if (!connections.get().containsKey(dataSource)) { - Connection connection = getConnection(dataSource); - connections.get().put(dataSource, connection); - return connection; - } - - return connections.get().get(dataSource); - } - - private static Connection getConnection(final DataSource dataSource) { - try { - return dataSource.getConnection(); - } catch (SQLException e) { - throw new CannotGetConnectionException(e.getMessage()); - } - } - - private static void action( - final ConnectionCallBack callBack, - final Function exceptionSupplier - ) { - try { - callBack.action(); - } catch (SQLException e) { - throw exceptionSupplier.apply(e); - } - } - - private static void beginTransaction(final Connection connection) { - action(() -> connection.setAutoCommit(false), - e -> new CannotBeginTransactionException(e.getMessage()) - ); + TransactionSynchronizationManager.beginTransaction(); } public static void commit(final DataSource dataSource) { - action(() -> { - if (connections.get().containsKey(dataSource)) { - connections.get().get(dataSource).commit(); - clear(dataSource); - } - }, e -> new CannotCommitException(e.getMessage())); + TransactionSynchronizationManager.commit(dataSource); } public static void rollback() { - action(() -> { - for (final Connection value : connections.get().values()) { - value.rollback(); - } - clear(); - }, e -> new CannotRollbackException(e.getMessage())); - } - - private static void clear(final DataSource dataSource) { - action(() -> { - final Connection connection = connections.get().remove(dataSource); - connection.close(); - }, e -> new DataAccessException(e.getMessage())); - } - - private static void clear() { - connections.get().clear(); + TransactionSynchronizationManager.rollback(); } } 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..62343ed7bc 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,114 @@ 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.function.Function; +import javax.sql.DataSource; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.support.ConnectionCallBack; +import org.springframework.jdbc.support.exception.CannotBeginTransactionException; +import org.springframework.jdbc.support.exception.CannotCommitException; +import org.springframework.jdbc.support.exception.CannotGetConnectionException; +import org.springframework.jdbc.support.exception.CannotRollbackException; public abstract class TransactionSynchronizationManager { - private static final ThreadLocal> resources = new ThreadLocal<>(); + private static final ThreadLocal> connections = ThreadLocal.withInitial(HashMap::new); + private static final ThreadLocal isTransactionActive = ThreadLocal.withInitial(() -> false); + + private TransactionSynchronizationManager() { + } + + public static void beginTransaction() { + isTransactionActive.set(true); + } + + public static boolean isTransactionBegan() { + return isTransactionActive.get(); + } + + public static Connection getResource(final DataSource dataSource) { + if (isTransactionActive.get()) { + final Connection connection = guaranteeConnection(dataSource); + beginTransaction(connection); + return connection; + } + + return getConnection(dataSource); + } + + private static Connection guaranteeConnection(final DataSource dataSource) { + if (!connections.get().containsKey(dataSource)) { + Connection connection = getConnection(dataSource); + connections.get().put(dataSource, connection); + return connection; + } - private TransactionSynchronizationManager() {} + return connections.get().get(dataSource); + } + + private static Connection getConnection(final DataSource dataSource) { + try { + return dataSource.getConnection(); + } catch (SQLException e) { + throw new CannotGetConnectionException(e.getMessage()); + } + } + + private static void beginTransaction(final Connection connection) { + action(() -> connection.setAutoCommit(false), + e -> new CannotBeginTransactionException(e.getMessage()) + ); + } + + private static void action( + final ConnectionCallBack callBack, + final Function exceptionSupplier + ) { + try { + callBack.action(); + } catch (SQLException e) { + throw exceptionSupplier.apply(e); + } + } + + public static void commit(final DataSource dataSource) { + action(() -> { + if (connections.get().containsKey(dataSource)) { + connections.get().get(dataSource).commit(); + clear(dataSource); + } + }, e -> new CannotCommitException(e.getMessage())); + } + + public static void rollback() { + action(() -> { + for (final Connection value : connections.get().values()) { + value.rollback(); + } + clear(); + }, e -> new CannotRollbackException(e.getMessage())); + } - public static Connection getResource(DataSource key) { - return null; + private static void clear(final DataSource dataSource) { + action(() -> { + final Connection connection = connections.get().remove(dataSource); + clearConnection(connection); + }, e -> new DataAccessException(e.getMessage())); + isTransactionActive.set(false); } - public static void bindResource(DataSource key, Connection value) { + private static void clearConnection(final Connection connection) { + action(connection::close, e -> new DataAccessException(e.getMessage())); } - public static Connection unbindResource(DataSource key) { - return null; + private static void clear() { + connections.get() + .values() + .forEach(TransactionSynchronizationManager::clearConnection); + connections.get().clear(); + isTransactionActive.set(false); } } diff --git a/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java b/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java index 86c63a12db..23fefe14fe 100644 --- a/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java +++ b/jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java @@ -5,8 +5,8 @@ import java.util.List; import java.util.Optional; -import nextstep.testUtil.TestDataSourceConfig; -import nextstep.testUtil.TestDatabaseUtils; +import testUtil.TestDataSourceConfig; +import testUtil.TestDatabaseUtils; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayNameGeneration; diff --git a/jdbc/src/test/java/nextstep/support/TransactionManagerTest.java b/jdbc/src/test/java/nextstep/support/TransactionManagerTest.java deleted file mode 100644 index 9c48af1b0b..0000000000 --- a/jdbc/src/test/java/nextstep/support/TransactionManagerTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package nextstep.support; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.sql.SQLException; -import javax.sql.DataSource; -import nextstep.testUtil.TestDataSourceConfig; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.support.ConnectionHolder; -import org.springframework.jdbc.support.TransactionManager; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class TransactionManagerTest { - - private DataSource dataSource; - - @BeforeEach - void setUp() { - dataSource = TestDataSourceConfig.getInstance(); - } - - @Test - void 트랜잭션_매니저는_트랜잭션을_시작하면_트랜잭션이_켜진_ConnectionHolder를_반환한다() throws SQLException { - // when - TransactionManager.beginTransaction(); - - // expected - final ConnectionHolder connection = TransactionManager.getConnectionHolder(dataSource); - assertThat(connection) - .usingRecursiveComparison() - .comparingOnlyFieldsOfTypes(Boolean.class) - .isEqualTo(ConnectionHolder.activeTransaction(dataSource.getConnection())); - } - - @Test - void 트랜잭션_매니저는_트랜잭션_시작을안하면_트랜잭션이_꺼진_ConnectionHolder를_반환한다() throws SQLException { - // expected - final ConnectionHolder connection = TransactionManager.getConnectionHolder(dataSource); - assertThat(connection) - .usingRecursiveComparison() - .comparingOnlyFieldsOfTypes(Boolean.class) - .isEqualTo(ConnectionHolder.disableTransaction(dataSource.getConnection())); - } -} 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..cbd3267c80 --- /dev/null +++ b/jdbc/src/test/java/org/springframework/transaction/support/TransactionSynchronizationManagerTest.java @@ -0,0 +1,110 @@ +package org.springframework.transaction.support; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Connection; +import javax.sql.DataSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import testUtil.TestDataSourceConfig; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class TransactionSynchronizationManagerTest { + + private DataSource dataSource; + + @BeforeEach + void setUp() { + dataSource = TestDataSourceConfig.getInstance(); + } + + @Test + void 트랜잭션이_시작되지_않았다면_autoCommit_이_켜진_커넥션을_반환한다() throws Exception { + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + assertThat(connection.getAutoCommit()).isTrue(); + } + + @Test + void 트랜잭션이_시작되었다면_autoCommit_이_꺼진_커넥션을_반환한다() throws Exception { + TransactionSynchronizationManager.beginTransaction(); + + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + assertThat(connection.getAutoCommit()).isFalse(); + } + + @Test + void 트랜잭션이_켜진_뒤_커밋을_하면_커넥션이_닫힌다() throws Exception { + // given + TransactionSynchronizationManager.beginTransaction(); + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + + // when + TransactionSynchronizationManager.commit(dataSource); + + // then + assertThat(connection.isClosed()).isTrue(); + } + + @Test + void 트랜잭션이_켜진_뒤_롤백을_하면_커넥션이_닫힌다() throws Exception { + // given + TransactionSynchronizationManager.beginTransaction(); + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + + // when + TransactionSynchronizationManager.rollback(); + + // then + assertThat(connection.isClosed()).isTrue(); + } + + @Test + void 트랜잭션이_켜진_뒤_커넥션을_다시_요청하면_동일한_커넥션을_반환한다() { + // given + TransactionSynchronizationManager.beginTransaction(); + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + + // when + Connection sameConnection = TransactionSynchronizationManager.getResource(dataSource); + + // then + assertThat(connection).isEqualTo(sameConnection); + } + + @Test + void 커밋_이후_커넥션을_다시_요청_하면_새로운_커넥션을_반환한다() { + // given + TransactionSynchronizationManager.beginTransaction(); + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + + // when + TransactionSynchronizationManager.commit(dataSource); + Connection otherConnection = TransactionSynchronizationManager.getResource(dataSource); + + // then + assertThat(connection).isNotEqualTo(otherConnection); + } + + @Test + void 롤백_이후_커넥션을_다시_요청_하면_새로운_커넥션을_반환한다() { + // given + TransactionSynchronizationManager.beginTransaction(); + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + + // when + TransactionSynchronizationManager.rollback(); + Connection otherConnection = TransactionSynchronizationManager.getResource(dataSource); + + // then + assertThat(connection).isNotEqualTo(otherConnection); + } + + @AfterEach + void clear() { + TransactionSynchronizationManager.rollback(); + } +} diff --git a/jdbc/src/test/java/nextstep/testUtil/TestDataSourceConfig.java b/jdbc/src/test/java/testUtil/TestDataSourceConfig.java similarity index 96% rename from jdbc/src/test/java/nextstep/testUtil/TestDataSourceConfig.java rename to jdbc/src/test/java/testUtil/TestDataSourceConfig.java index 7efdf5643a..a70943ee61 100644 --- a/jdbc/src/test/java/nextstep/testUtil/TestDataSourceConfig.java +++ b/jdbc/src/test/java/testUtil/TestDataSourceConfig.java @@ -1,4 +1,4 @@ -package nextstep.testUtil; +package testUtil; import java.util.Objects; import javax.sql.DataSource; diff --git a/jdbc/src/test/java/nextstep/testUtil/TestDatabaseUtils.java b/jdbc/src/test/java/testUtil/TestDatabaseUtils.java similarity index 97% rename from jdbc/src/test/java/nextstep/testUtil/TestDatabaseUtils.java rename to jdbc/src/test/java/testUtil/TestDatabaseUtils.java index 1f91fe7cff..84ad9db15b 100644 --- a/jdbc/src/test/java/nextstep/testUtil/TestDatabaseUtils.java +++ b/jdbc/src/test/java/testUtil/TestDatabaseUtils.java @@ -1,4 +1,4 @@ -package nextstep.testUtil; +package testUtil; import java.io.File; import java.io.IOException;