From 1427c2985f379f64f5981d4b36139f72b416885b Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sat, 7 Oct 2023 19:22:45 +0900 Subject: [PATCH 01/17] =?UTF-8?q?feat:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EB=A7=A4=EB=8B=88?= =?UTF-8?q?=EC=A0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TransactionSynchronizationManager.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) 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..8e79cd4f3b 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -8,16 +8,25 @@ public abstract class TransactionSynchronizationManager { private static final ThreadLocal> resources = new ThreadLocal<>(); - private TransactionSynchronizationManager() {} + private TransactionSynchronizationManager() { + } + + public static Connection getResource(final DataSource key) { + final Map mappings = resources.get(); + return mappings.get(key); + } - public static Connection getResource(DataSource key) { - return null; + public static void bindResource(final DataSource key, final Connection value) { + final Map mappings = resources.get(); + mappings.put(key, value); } - public static void bindResource(DataSource key, Connection value) { + public static Connection unbindResource(final DataSource key) { + final Map mappings = resources.get(); + return mappings.remove(key); } - public static Connection unbindResource(DataSource key) { - return null; + public static void release() { + resources.remove(); } } From 25a8c99b53422317c7e2df28e0f79eebddc96956 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sat, 7 Oct 2023 19:38:01 +0900 Subject: [PATCH 02/17] =?UTF-8?q?feat:=20DataSourceUtils=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jdbc/datasource/DataSourceUtils.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 158d1b5b10..27e36261fd 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -6,19 +6,22 @@ import java.sql.Connection; import java.sql.SQLException; -// 4단계 미션에서 사용할 것 public abstract class DataSourceUtils { - private DataSourceUtils() {} + private DataSourceUtils() { + } - public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { - Connection connection = TransactionSynchronizationManager.getResource(dataSource); + public static Connection getConnection(final DataSource dataSource) { + final Connection connection = TransactionSynchronizationManager.getResource(dataSource); if (connection != null) { return connection; } + return generateConnection(dataSource); + } + private static Connection generateConnection(final DataSource dataSource) { try { - connection = dataSource.getConnection(); + final Connection connection = dataSource.getConnection(); TransactionSynchronizationManager.bindResource(dataSource, connection); return connection; } catch (SQLException ex) { @@ -26,8 +29,9 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd } } - public static void releaseConnection(Connection connection, DataSource dataSource) { + public static void releaseConnection(final DataSource dataSource) { try { + Connection connection = TransactionSynchronizationManager.unbindResource(dataSource); connection.close(); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); From 9ba3021f0c6b52bb00a0720820e6b4a68e3a7b44 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sat, 7 Oct 2023 20:13:52 +0900 Subject: [PATCH 03/17] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EB=A7=A4=EB=8B=88?= =?UTF-8?q?=EC=A0=80=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EC=BB=A4=EB=84=A5?= =?UTF-8?q?=EC=85=98=EC=9D=84=20=EA=B3=B5=EC=9C=A0=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 6 --- .../com/techcourse/dao/UserHistoryDao.java | 4 +- .../com/techcourse/service/UserService.java | 21 +++------- .../service/MockUserHistoryDao.java | 2 +- .../jdbc/core/JdbcTemplate.java | 6 --- .../jdbc/core/StatementAgent.java | 39 +++++++++++++------ .../TransactionSynchronizationManager.java | 5 +++ 7 files changed, 42 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index c1468623ab..8a304d3b3d 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -5,7 +5,6 @@ import org.springframework.jdbc.core.RowMapper; import javax.sql.DataSource; -import java.sql.Connection; import java.util.List; public class UserDao { @@ -32,11 +31,6 @@ public void insert(final User user) { jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail()); } - public void update(final Connection connection, final User user) { - final var sql = "update users set account = ?, password = ?, email = ? where id = ?"; - jdbcTemplate.update(connection, 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 81a7ad6df1..e01cf66ba4 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -17,12 +17,12 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public void log(final Connection connection, 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 (?, ?, ?, ?, ?, ?)"; log.debug("query = {}", sql); - jdbcTemplate.update(connection, 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/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index b8ae97f664..28e8dc0d60 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -5,6 +5,7 @@ import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; import javax.sql.DataSource; import java.sql.Connection; @@ -17,7 +18,7 @@ public class UserService { private final DataSource dataSource; - public UserService(UserDao userDao, UserHistoryDao userHistoryDao, DataSource dataSource) { + public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final DataSource dataSource) { this.userDao = userDao; this.userHistoryDao = userHistoryDao; this.dataSource = dataSource; @@ -34,20 +35,20 @@ public void insert(final User user) { public void changePassword(final long id, final String newPassword, final String createBy) { Connection connection = null; try { - connection = dataSource.getConnection(); + connection = DataSourceUtils.getConnection(dataSource); connection.setAutoCommit(false); final var user = findById(id); user.changePassword(newPassword); - userDao.update(connection, user); - userHistoryDao.log(connection, new UserHistory(user, createBy)); + userDao.update(user); + userHistoryDao.log(new UserHistory(user, createBy)); connection.commit(); } catch (SQLException | RuntimeException e) { tryRollback(connection); throw new DataAccessException(e); } finally { - tryCloseConnection(connection); + DataSourceUtils.releaseConnection(dataSource); } } @@ -60,14 +61,4 @@ private void tryRollback(Connection connection) { throw new DataAccessException(ex); } } - - private void tryCloseConnection(Connection connection) { - try { - if (connection != null) { - connection.close(); - } - } catch (SQLException e) { - throw new DataAccessException(e); - } - } } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 9937b42bbc..bfbc87cfa4 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -14,7 +14,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { } @Override - public void log(final Connection connection, final UserHistory userHistory) { + public void log(UserHistory userHistory) { throw new DataAccessException(); } } 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 1f0e597d46..681f9d8689 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,6 @@ package org.springframework.jdbc.core; import javax.sql.DataSource; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; @@ -16,11 +15,6 @@ public JdbcTemplate(final DataSource dataSource) { this.statementAgent = new StatementAgent(dataSource); } - public int update(final Connection connection, final String sql, final Object... args) { - StatementCallback callback = PreparedStatement::executeUpdate; - return statementAgent.service(connection, sql, callback, args); - } - public int update(final String sql, final Object... args) { StatementCallback callback = PreparedStatement::executeUpdate; return statementAgent.service(sql, callback, args); diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/StatementAgent.java b/jdbc/src/main/java/org/springframework/jdbc/core/StatementAgent.java index fe20e01591..06e425d372 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/StatementAgent.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/StatementAgent.java @@ -3,6 +3,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; +import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.sql.DataSource; import java.sql.Connection; @@ -19,29 +20,34 @@ public StatementAgent(final DataSource dataSource) { this.dataSource = dataSource; } - public T service(final Connection connection, final String sql, final StatementCallback statementCallback, - final Object... args) { - try (final PreparedStatement statement = connection.prepareStatement(sql)) { + public T service(final String sql, final StatementCallback statementCallback, final Object... args) { + final Connection connection = particpateOrCreateConnection(); + try (final PreparedStatement statement = connection.prepareStatement(sql)) { setParameters(statement, args); return statementCallback.call(statement); } catch (SQLException e) { log.error("statement agent service failed."); throw new DataAccessException(e); + } finally { + closeConnectionIfNotParticipated(connection); } } - public T service(final String sql, final StatementCallback statementCallback, final Object... args) { - try (final Connection connection = dataSource.getConnection(); - final PreparedStatement statement = connection.prepareStatement(sql)) { - - setParameters(statement, args); - return statementCallback.call(statement); + private Connection particpateOrCreateConnection() { + final Connection connection = TransactionSynchronizationManager.getResource(dataSource); + if (connection == null) { + return generateConnection(); + } + return connection; + } + private Connection generateConnection() { + try { + return dataSource.getConnection(); } catch (SQLException e) { - log.error("statement agent service failed."); - throw new DataAccessException(e); + throw new RuntimeException(e); } } @@ -50,4 +56,15 @@ private void setParameters(final PreparedStatement statement, final Object[] arg statement.setObject(i + 1, args[i]); } } + + private void closeConnectionIfNotParticipated(final Connection connection) { + try { + Connection existingConnection = TransactionSynchronizationManager.getResource(dataSource); + if (existingConnection != connection) { + connection.close(); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } } 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 8e79cd4f3b..27434c806f 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -2,12 +2,17 @@ import javax.sql.DataSource; import java.sql.Connection; +import java.util.HashMap; import java.util.Map; public abstract class TransactionSynchronizationManager { private static final ThreadLocal> resources = new ThreadLocal<>(); + static { + resources.set(new HashMap<>()); + } + private TransactionSynchronizationManager() { } From 81547472972924d475bb208ba8a116f9de2f6b18 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 13:40:52 +0900 Subject: [PATCH 04/17] =?UTF-8?q?refactor:=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=EB=A5=BC=20=ED=99=9C=EC=9A=A9=ED=95=9C=20?= =?UTF-8?q?=ED=94=84=EB=A1=9D=EC=8B=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/AppUserService.java | 35 +++++++++++ .../service/TransactionUserService.java | 59 ++++++++++++++++++ .../com/techcourse/service/UserService.java | 60 ++----------------- .../techcourse/service/UserServiceTest.java | 6 +- 4 files changed, 102 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/com/techcourse/service/AppUserService.java create mode 100644 app/src/main/java/com/techcourse/service/TransactionUserService.java 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..a8d453b094 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,35 @@ +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; + } + + @Override + public User findById(final long id) { + return userDao.findById(id); + } + + @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/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java new file mode 100644 index 0000000000..efa81e8f52 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -0,0 +1,59 @@ +package com.techcourse.service; + +import com.techcourse.domain.User; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class TransactionUserService implements UserService { + + private AppUserService appUserService; + private DataSource dataSource; + + + public TransactionUserService(final AppUserService appUserService, final DataSource dataSource) { + this.appUserService = appUserService; + this.dataSource = dataSource; + } + + @Override + public User findById(final long id) { + return appUserService.findById(id); + } + + @Override + public void insert(final User user) { + appUserService.insert(user); + } + + @Override + public void changePassword(final long id, final String newPassword, final String createBy) { + Connection connection = null; + try { + connection = DataSourceUtils.getConnection(dataSource); + connection.setAutoCommit(false); + + appUserService.changePassword(id, newPassword, createBy); + + connection.commit(); + } catch (SQLException | RuntimeException e) { + tryRollback(connection); + throw new DataAccessException(e); + } finally { + DataSourceUtils.releaseConnection(dataSource); + } + } + + private void tryRollback(Connection connection) { + try { + if (connection != null) { + connection.rollback(); + } + } catch (SQLException ex) { + throw new DataAccessException(ex); + } + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 28e8dc0d60..647aa7f699 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,64 +1,12 @@ 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.dao.DataAccessException; -import org.springframework.jdbc.datasource.DataSourceUtils; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.SQLException; +public interface UserService { -public class UserService { + User findById(final long id); - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; + void insert(final User user); - private final DataSource dataSource; - - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final DataSource dataSource) { - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - this.dataSource = dataSource; - } - - 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) { - Connection connection = null; - try { - connection = DataSourceUtils.getConnection(dataSource); - connection.setAutoCommit(false); - - final var user = findById(id); - user.changePassword(newPassword); - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); - - connection.commit(); - } catch (SQLException | RuntimeException e) { - tryRollback(connection); - throw new DataAccessException(e); - } finally { - DataSourceUtils.releaseConnection(dataSource); - } - } - - private void tryRollback(Connection connection) { - try { - if (connection != null) { - connection.rollback(); - } - } catch (SQLException ex) { - throw new DataAccessException(ex); - } - } + void changePassword(final long id, final String newPassword, final String createdBy); } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index 408875fbc2..22c45eb5ea 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -35,7 +35,8 @@ void setUp() { @Test void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao, dataSource); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TransactionUserService(appUserService, dataSource); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -50,7 +51,8 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao, dataSource); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TransactionUserService(appUserService, dataSource); final var newPassword = "newPassword"; final var createBy = "gugu"; From 448581dbda6c0616b521e44ae8d47ca4892d5c2a Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 13:56:26 +0900 Subject: [PATCH 05/17] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EA=B4=80=EC=8B=AC=EC=82=AC=EB=A5=BC=20=EB=8B=B4?= =?UTF-8?q?=EB=8B=B9=ED=95=98=EB=8A=94=20`Transaction`=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/service/Transaction.java | 46 +++++++++++++++++++ .../service/TransactionUserService.java | 24 ++-------- 2 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/techcourse/service/Transaction.java diff --git a/app/src/main/java/com/techcourse/service/Transaction.java b/app/src/main/java/com/techcourse/service/Transaction.java new file mode 100644 index 0000000000..8c332e5cce --- /dev/null +++ b/app/src/main/java/com/techcourse/service/Transaction.java @@ -0,0 +1,46 @@ +package com.techcourse.service; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class Transaction implements AutoCloseable { + + private final DataSource dataSource; + private final Connection connection; + + private Transaction(final DataSource dataSource, final Connection connection) { + this.dataSource = dataSource; + this.connection = connection; + } + + public static Transaction start(final DataSource dataSource) { + try { + Connection connection = DataSourceUtils.getConnection(dataSource); + connection.setAutoCommit(false); + return new Transaction(dataSource, connection); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + public void commit() throws SQLException { + connection.commit(); + } + + public void rollback() { + try { + connection.rollback(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + @Override + public void close() { + DataSourceUtils.releaseConnection(dataSource); + } +} diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index efa81e8f52..dfc22a3f96 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -2,10 +2,8 @@ import com.techcourse.domain.User; import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.datasource.DataSourceUtils; import javax.sql.DataSource; -import java.sql.Connection; import java.sql.SQLException; public class TransactionUserService implements UserService { @@ -31,29 +29,15 @@ public void insert(final User user) { @Override public void changePassword(final long id, final String newPassword, final String createBy) { - Connection connection = null; + Transaction transaction = Transaction.start(dataSource); try { - connection = DataSourceUtils.getConnection(dataSource); - connection.setAutoCommit(false); - appUserService.changePassword(id, newPassword, createBy); - - connection.commit(); + transaction.commit(); } catch (SQLException | RuntimeException e) { - tryRollback(connection); + transaction.rollback(); throw new DataAccessException(e); } finally { - DataSourceUtils.releaseConnection(dataSource); - } - } - - private void tryRollback(Connection connection) { - try { - if (connection != null) { - connection.rollback(); - } - } catch (SQLException ex) { - throw new DataAccessException(ex); + transaction.close(); } } } From e5da2ae58fcbddd7d98e615c705dba62accb6581 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 14:05:26 +0900 Subject: [PATCH 06/17] =?UTF-8?q?refactor:=20=EB=A1=A4=EB=B0=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/TransactionFailedException.java | 8 ++++++++ .../com/techcourse/service/TransactionUserService.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/techcourse/service/TransactionFailedException.java diff --git a/app/src/main/java/com/techcourse/service/TransactionFailedException.java b/app/src/main/java/com/techcourse/service/TransactionFailedException.java new file mode 100644 index 0000000000..f41cc35c9f --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TransactionFailedException.java @@ -0,0 +1,8 @@ +package com.techcourse.service; + +public class TransactionFailedException extends RuntimeException { + + public TransactionFailedException(Throwable e) { + super(e); + } +} diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index dfc22a3f96..278760a04b 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -35,7 +35,7 @@ public void changePassword(final long id, final String newPassword, final String transaction.commit(); } catch (SQLException | RuntimeException e) { transaction.rollback(); - throw new DataAccessException(e); + throw new TransactionFailedException(e); } finally { transaction.close(); } From bedd241494e1a96b5bef770cfc9b05c464e17395 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 14:05:52 +0900 Subject: [PATCH 07/17] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/service/TransactionUserService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 278760a04b..18b461437e 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -8,8 +8,8 @@ public class TransactionUserService implements UserService { - private AppUserService appUserService; - private DataSource dataSource; + private final AppUserService appUserService; + private final DataSource dataSource; public TransactionUserService(final AppUserService appUserService, final DataSource dataSource) { From 65d3369d25bd45f0c8e64a0e6846aa81428b2c9b Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 14:11:01 +0900 Subject: [PATCH 08/17] =?UTF-8?q?refactor:=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A9=94=EC=86=8C=EB=93=9C?= =?UTF-8?q?=EB=93=A4=EB=8F=84=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98?= =?UTF-8?q?=EC=9D=84=20=EC=88=98=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/service/Transaction.java | 8 ++++++ .../service/TransactionUserService.java | 25 ++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/Transaction.java b/app/src/main/java/com/techcourse/service/Transaction.java index 8c332e5cce..beb3170115 100644 --- a/app/src/main/java/com/techcourse/service/Transaction.java +++ b/app/src/main/java/com/techcourse/service/Transaction.java @@ -39,6 +39,14 @@ public void rollback() { } } + public void setReadOnly() { + try { + connection.setReadOnly(true); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + @Override public void close() { DataSourceUtils.releaseConnection(dataSource); diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 18b461437e..4f4b5598f9 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -1,7 +1,6 @@ package com.techcourse.service; import com.techcourse.domain.User; -import org.springframework.dao.DataAccessException; import javax.sql.DataSource; import java.sql.SQLException; @@ -19,12 +18,32 @@ public TransactionUserService(final AppUserService appUserService, final DataSou @Override public User findById(final long id) { - return appUserService.findById(id); + Transaction transaction = Transaction.start(dataSource); + transaction.setReadOnly(); + try { + User user = appUserService.findById(id); + transaction.commit(); + return user; + } catch (SQLException | RuntimeException e) { + transaction.rollback(); + throw new TransactionFailedException(e); + } finally { + transaction.close(); + } } @Override public void insert(final User user) { - appUserService.insert(user); + Transaction transaction = Transaction.start(dataSource); + try { + appUserService.insert(user); + transaction.commit(); + } catch (SQLException | RuntimeException e) { + transaction.rollback(); + throw new TransactionFailedException(e); + } finally { + transaction.close(); + } } @Override From 310eab6120d8b10826b2ff03db183a75eae83353 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 16:40:37 +0900 Subject: [PATCH 09/17] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A1=B0=EC=A0=95=20=EB=B0=8F=20schema.sq?= =?UTF-8?q?l=20=EC=A3=BC=EC=84=9D=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/dao/UserHistoryDao.java | 2 -- .../techcourse/service/MockUserHistoryDao.java | 2 -- docs/README.md | 6 ++++++ study/src/main/resources/schema.sql | 18 +++++++++--------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index e01cf66ba4..8e096bf265 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -5,8 +5,6 @@ import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; -import java.sql.Connection; - public class UserHistoryDao { private static final Logger log = LoggerFactory.getLogger(UserHistoryDao.class); diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index bfbc87cfa4..0ab4810cc0 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -5,8 +5,6 @@ import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import java.sql.Connection; - public class MockUserHistoryDao extends UserHistoryDao { public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { diff --git a/docs/README.md b/docs/README.md index c1fa3c841c..d2d94ba5e6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,3 +5,9 @@ - ThreadLocal을 통해 커넥션 객체 자체를 스레드 변수로 등록하고, 필요한 곳에서 사용하는 방법은 어떨까? - 문제는 해결되겠지만 그럼에도 Service에 트랜잭션 관련 코드가 남아 있다. - AOP를 사용해야 할 시점이 아닐까? + + +### step 4 기록 +- 트랜잭션 동기화 매니져는 왜 Map 형태로 구현이 되어 있을까? + - ThreadLocal의 경우 하나의 스레드에 대한 지역변수처럼 작동한다. + - 그렇다면 Map을 사용하는 이유는 하나의 서비스에서 여러 DataSource를 사용할 일을 대비하기 위함이 아닐까? diff --git a/study/src/main/resources/schema.sql b/study/src/main/resources/schema.sql index 6915176378..bf9c44ac95 100644 --- a/study/src/main/resources/schema.sql +++ b/study/src/main/resources/schema.sql @@ -5,12 +5,12 @@ CREATE TABLE IF NOT EXISTS users ( email VARCHAR(100) NOT NULL ) ENGINE=INNODB; --- CREATE TABLE IF NOT EXISTS user_history ( --- id BIGINT AUTO_INCREMENT PRIMARY KEY, --- user_id BIGINT NOT NULL, --- account VARCHAR(100) NOT NULL, --- password VARCHAR(100) NOT NULL, --- email VARCHAR(100) NOT NULL, --- created_at DATETIME NOT NULL, --- created_by VARCHAR(100) NOT NULL --- ) ENGINE=INNODB; +CREATE TABLE IF NOT EXISTS user_history ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + account VARCHAR(100) NOT NULL, + password VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL, + created_at DATETIME NOT NULL, + created_by VARCHAR(100) NOT NULL +) ENGINE=INNODB; From f1bec488cf9f79cdbcb452c88781b594fb70e957 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 16:43:11 +0900 Subject: [PATCH 10/17] =?UTF-8?q?test:=20AOP=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/aop/stage0/Stage0Test.java | 11 ++++++-- .../java/aop/stage0/TransactionHandler.java | 26 +++++++++++++++++-- .../src/test/java/aop/stage1/Stage1Test.java | 24 +++++++++++++++-- .../java/aop/stage1/TransactionAdvice.java | 25 ++++++++++++++++-- .../java/aop/stage1/TransactionAdvisor.java | 13 ++++++++-- .../java/aop/stage1/TransactionPointcut.java | 7 ++++- study/src/test/java/aop/stage2/AopConfig.java | 22 ++++++++++++++++ 7 files changed, 117 insertions(+), 11 deletions(-) diff --git a/study/src/test/java/aop/stage0/Stage0Test.java b/study/src/test/java/aop/stage0/Stage0Test.java index 079cc6b5a0..e143b79204 100644 --- a/study/src/test/java/aop/stage0/Stage0Test.java +++ b/study/src/test/java/aop/stage0/Stage0Test.java @@ -6,6 +6,7 @@ import aop.repository.UserDao; import aop.repository.UserHistoryDao; import aop.service.AppUserService; +import aop.service.TxUserService; import aop.service.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,6 +16,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.PlatformTransactionManager; +import java.lang.reflect.Proxy; + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -45,7 +48,7 @@ void setUp() { @Test void testChangePassword() { final var appUserService = new AppUserService(userDao, userHistoryDao); - final UserService userService = null; + final UserService userService = new TxUserService(platformTransactionManager, appUserService); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -59,7 +62,11 @@ void testChangePassword() { @Test void testTransactionRollback() { final var appUserService = new AppUserService(userDao, stubUserHistoryDao); - final UserService userService = null; + final UserService userService = (UserService) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{UserService.class}, + new TransactionHandler(platformTransactionManager, appUserService) + ); final var newPassword = "newPassword"; final var createBy = "gugu"; diff --git a/study/src/test/java/aop/stage0/TransactionHandler.java b/study/src/test/java/aop/stage0/TransactionHandler.java index 2aa8445aba..c3a81182c8 100644 --- a/study/src/test/java/aop/stage0/TransactionHandler.java +++ b/study/src/test/java/aop/stage0/TransactionHandler.java @@ -1,15 +1,37 @@ package aop.stage0; +import aop.DataAccessException; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class TransactionHandler implements InvocationHandler { + private final PlatformTransactionManager transactionManager; + private final Object target; + + public TransactionHandler(final PlatformTransactionManager transactionManager, Object target) { + this.transactionManager = transactionManager; + this.target = target; + } + /** * @Transactional 어노테이션이 존재하는 메서드만 트랜잭션 기능을 적용하도록 만들어보자. */ @Override - public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { - return null; + public Object invoke(final Object proxy, final Method method, final Object[] args) { + final TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + Object result = method.invoke(target, args); + transactionManager.commit(status); + return result; + } catch (RuntimeException | IllegalAccessException | InvocationTargetException e) { + transactionManager.rollback(status); + throw new DataAccessException(e); + } } } diff --git a/study/src/test/java/aop/stage1/Stage1Test.java b/study/src/test/java/aop/stage1/Stage1Test.java index 113b2e7d03..401bd8368e 100644 --- a/study/src/test/java/aop/stage1/Stage1Test.java +++ b/study/src/test/java/aop/stage1/Stage1Test.java @@ -5,10 +5,12 @@ import aop.domain.User; import aop.repository.UserDao; import aop.repository.UserHistoryDao; +import aop.service.AppUserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.PlatformTransactionManager; @@ -41,7 +43,16 @@ void setUp() { @Test void testChangePassword() { - final UserService userService = null; + final ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); + proxyFactoryBean.setTarget(new UserService(userDao, userHistoryDao)); + + final TransactionPointcut pointcut = new TransactionPointcut(); + final TransactionAdvice advice = new TransactionAdvice(platformTransactionManager); + final TransactionAdvisor advisor = new TransactionAdvisor(advice, pointcut); + + proxyFactoryBean.addAdvisor(advisor); + + final UserService userService = (UserService) proxyFactoryBean.getObject(); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -54,7 +65,16 @@ void testChangePassword() { @Test void testTransactionRollback() { - final UserService userService = null; + final ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); + proxyFactoryBean.setTarget(new UserService(userDao, stubUserHistoryDao)); + + final TransactionPointcut pointcut = new TransactionPointcut(); + final TransactionAdvice advice = new TransactionAdvice(platformTransactionManager); + final TransactionAdvisor advisor = new TransactionAdvisor(advice, pointcut); + + proxyFactoryBean.addAdvisor(advisor); + + final UserService userService = (UserService) proxyFactoryBean.getObject(); final var newPassword = "newPassword"; final var createBy = "gugu"; diff --git a/study/src/test/java/aop/stage1/TransactionAdvice.java b/study/src/test/java/aop/stage1/TransactionAdvice.java index 03a03a84e5..0f74a0a439 100644 --- a/study/src/test/java/aop/stage1/TransactionAdvice.java +++ b/study/src/test/java/aop/stage1/TransactionAdvice.java @@ -1,15 +1,36 @@ package aop.stage1; +import aop.DataAccessException; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; /** * 어드바이스(advice). 부가기능을 담고 있는 클래스 */ -public class TransactionAdvice implements MethodInterceptor { +public class TransactionAdvice implements MethodInterceptor { + + private final PlatformTransactionManager transactionManager; + + public TransactionAdvice(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } @Override public Object invoke(final MethodInvocation invocation) throws Throwable { - return null; + final TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + Object result = invocation.proceed(); + transactionManager.commit(status); + return result; + } catch (RuntimeException | IllegalAccessException | InvocationTargetException e) { + transactionManager.rollback(status); + throw new DataAccessException(e); + } } } diff --git a/study/src/test/java/aop/stage1/TransactionAdvisor.java b/study/src/test/java/aop/stage1/TransactionAdvisor.java index 7abc27516c..51d075d9ab 100644 --- a/study/src/test/java/aop/stage1/TransactionAdvisor.java +++ b/study/src/test/java/aop/stage1/TransactionAdvisor.java @@ -1,6 +1,7 @@ package aop.stage1; import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.Interceptor; import org.springframework.aop.Pointcut; import org.springframework.aop.PointcutAdvisor; @@ -10,14 +11,22 @@ */ public class TransactionAdvisor implements PointcutAdvisor { + private final Interceptor advice; + private final Pointcut pointcut; + + public TransactionAdvisor(final Interceptor advice, final Pointcut pointcut) { + this.advice = advice; + this.pointcut = pointcut; + } + @Override public Pointcut getPointcut() { - return null; + return pointcut; } @Override public Advice getAdvice() { - return null; + return advice; } @Override diff --git a/study/src/test/java/aop/stage1/TransactionPointcut.java b/study/src/test/java/aop/stage1/TransactionPointcut.java index 29ff854890..4b8d94816c 100644 --- a/study/src/test/java/aop/stage1/TransactionPointcut.java +++ b/study/src/test/java/aop/stage1/TransactionPointcut.java @@ -1,8 +1,12 @@ package aop.stage1; +import aop.Transactional; import org.springframework.aop.support.StaticMethodMatcherPointcut; +import org.springframework.stereotype.Service; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Arrays; /** * 포인트컷(pointcut). 어드바이스를 적용할 조인 포인트를 선별하는 클래스. @@ -14,6 +18,7 @@ public class TransactionPointcut extends StaticMethodMatcherPointcut { @Override public boolean matches(final Method method, final Class targetClass) { - return false; + return targetClass.isAnnotationPresent(Service.class) && + method.isAnnotationPresent(Transactional.class); } } diff --git a/study/src/test/java/aop/stage2/AopConfig.java b/study/src/test/java/aop/stage2/AopConfig.java index 0a6e7f124e..78704b6c9f 100644 --- a/study/src/test/java/aop/stage2/AopConfig.java +++ b/study/src/test/java/aop/stage2/AopConfig.java @@ -1,8 +1,30 @@ package aop.stage2; +import aop.stage1.TransactionAdvice; +import aop.stage1.TransactionAdvisor; +import aop.stage1.TransactionPointcut; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; @Configuration public class AopConfig { + @Autowired + private PlatformTransactionManager platformTransactionManager; + + @Bean + public TransactionAdvisor transactionAdvisor() { + TransactionAdvice advice = new TransactionAdvice(platformTransactionManager); + TransactionPointcut pointcut = new TransactionPointcut(); + + return new TransactionAdvisor(advice, pointcut); + } + + @Bean + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + return new DefaultAdvisorAutoProxyCreator(); + } } From 3317e137944b7e43ba5f163704771c775354874c Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 17:13:25 +0900 Subject: [PATCH 11/17] =?UTF-8?q?chore:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EA=B4=80=EB=A0=A8=20=EC=98=88=EC=99=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20(DataAccessException=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/TransactionFailedException.java | 8 -------- .../com/techcourse/service/TransactionUserService.java | 7 ++++--- 2 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 app/src/main/java/com/techcourse/service/TransactionFailedException.java diff --git a/app/src/main/java/com/techcourse/service/TransactionFailedException.java b/app/src/main/java/com/techcourse/service/TransactionFailedException.java deleted file mode 100644 index f41cc35c9f..0000000000 --- a/app/src/main/java/com/techcourse/service/TransactionFailedException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.techcourse.service; - -public class TransactionFailedException extends RuntimeException { - - public TransactionFailedException(Throwable e) { - super(e); - } -} diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 4f4b5598f9..12c4c5b1ce 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -1,6 +1,7 @@ package com.techcourse.service; import com.techcourse.domain.User; +import org.springframework.dao.DataAccessException; import javax.sql.DataSource; import java.sql.SQLException; @@ -26,7 +27,7 @@ public User findById(final long id) { return user; } catch (SQLException | RuntimeException e) { transaction.rollback(); - throw new TransactionFailedException(e); + throw new DataAccessException(e); } finally { transaction.close(); } @@ -40,7 +41,7 @@ public void insert(final User user) { transaction.commit(); } catch (SQLException | RuntimeException e) { transaction.rollback(); - throw new TransactionFailedException(e); + throw new DataAccessException(e); } finally { transaction.close(); } @@ -54,7 +55,7 @@ public void changePassword(final long id, final String newPassword, final String transaction.commit(); } catch (SQLException | RuntimeException e) { transaction.rollback(); - throw new TransactionFailedException(e); + throw new DataAccessException(e); } finally { transaction.close(); } From 03f1992539deda477f977919a790e1803e312545 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 17:47:30 +0900 Subject: [PATCH 12/17] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/service/Transaction.java | 4 +- .../service/TransactionUserService.java | 79 +++++++++++-------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/Transaction.java b/app/src/main/java/com/techcourse/service/Transaction.java index beb3170115..93d38a7034 100644 --- a/app/src/main/java/com/techcourse/service/Transaction.java +++ b/app/src/main/java/com/techcourse/service/Transaction.java @@ -39,9 +39,9 @@ public void rollback() { } } - public void setReadOnly() { + public void setReadOnly(final boolean readOnly) { try { - connection.setReadOnly(true); + connection.setReadOnly(readOnly); } catch (SQLException e) { throw new DataAccessException(e); } diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 12c4c5b1ce..2a71a14473 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -5,6 +5,7 @@ import javax.sql.DataSource; import java.sql.SQLException; +import java.util.function.Supplier; public class TransactionUserService implements UserService { @@ -19,45 +20,61 @@ public TransactionUserService(final AppUserService appUserService, final DataSou @Override public User findById(final long id) { - Transaction transaction = Transaction.start(dataSource); - transaction.setReadOnly(); - try { - User user = appUserService.findById(id); - transaction.commit(); - return user; - } catch (SQLException | RuntimeException e) { - transaction.rollback(); - throw new DataAccessException(e); - } finally { - transaction.close(); - } + return new TransactionExecutor() + .setDataSource(dataSource) + .setReadOnlyTrue() + .executeTransactionWithSupplier(() -> appUserService.findById(id)); } @Override public void insert(final User user) { - Transaction transaction = Transaction.start(dataSource); - try { - appUserService.insert(user); - transaction.commit(); - } catch (SQLException | RuntimeException e) { - transaction.rollback(); - throw new DataAccessException(e); - } finally { - transaction.close(); - } + new TransactionExecutor() + .setDataSource(dataSource) + .executeTransactionWithRunnable(() -> appUserService.insert(user)); } @Override public void changePassword(final long id, final String newPassword, final String createBy) { - Transaction transaction = Transaction.start(dataSource); - try { - appUserService.changePassword(id, newPassword, createBy); - transaction.commit(); - } catch (SQLException | RuntimeException e) { - transaction.rollback(); - throw new DataAccessException(e); - } finally { - transaction.close(); + new TransactionExecutor() + .setDataSource(dataSource) + .executeTransactionWithRunnable(() -> appUserService.changePassword(id, newPassword, createBy)); + } + + static class TransactionExecutor { + + private DataSource dataSource; + private boolean readOnly = false; + + private TransactionExecutor setReadOnlyTrue() { + this.readOnly = true; + return this; + } + + private TransactionExecutor setDataSource(final DataSource dataSource) { + this.dataSource = dataSource; + return this; + } + + private void executeTransactionWithRunnable(final Runnable runnable) { + executeTransactionWithSupplier(() -> { + runnable.run(); + return null; + }); + } + + private T executeTransactionWithSupplier(Supplier supplier) { + Transaction transaction = Transaction.start(dataSource); + transaction.setReadOnly(readOnly); + try { + T result = supplier.get(); + transaction.commit(); + return result; + } catch (SQLException | RuntimeException e) { + transaction.rollback(); + throw new DataAccessException(e); + } finally { + transaction.close(); + } } } } From b856a18e0b9a8efe98d79a29c6b14b376e8e5b05 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 17:58:09 +0900 Subject: [PATCH 13/17] =?UTF-8?q?docs:=20=EA=B3=A0=EB=AF=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/README.md b/docs/README.md index d2d94ba5e6..6c47c7d5cc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,3 +11,9 @@ - 트랜잭션 동기화 매니져는 왜 Map 형태로 구현이 되어 있을까? - ThreadLocal의 경우 하나의 스레드에 대한 지역변수처럼 작동한다. - 그렇다면 Map을 사용하는 이유는 하나의 서비스에서 여러 DataSource를 사용할 일을 대비하기 위함이 아닐까? + +- 스프링을 사용하지 않는 환경에서 Spring AOP 같은 기능은 어떻게 만들 수 있을까? + - 동적으로 런타임에 프록시를 만들고 싶다. + - 스프링의 경우 Context로 빈들을 관리하기 때문에 런타임 주입 시 프록시 객체로 주입할 수 있다. + - 하지만 스프링을 사용하지 않는 경우에는 `new 객체();` 처럼 선언을 하니 런타임에 프록시로 구현체를 갈아끼울 수 없다. + - 객체간 의존관계를 스프링이 설정한다는 점 덕분에 Spring AOP가 가능한게 아닐까? From 9c26212ca015a178a68760b9d73a018891174fd7 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 18:20:01 +0900 Subject: [PATCH 14/17] =?UTF-8?q?refactor:=20=EB=B9=8C=EB=8D=94=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/service/TransactionUserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 2a71a14473..3e1563dfa2 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -40,7 +40,7 @@ public void changePassword(final long id, final String newPassword, final String .executeTransactionWithRunnable(() -> appUserService.changePassword(id, newPassword, createBy)); } - static class TransactionExecutor { + private static class TransactionExecutor { private DataSource dataSource; private boolean readOnly = false; From 7732af1bb6ef2a8b880c406385df98ebc522e745 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 20:12:19 +0900 Subject: [PATCH 15/17] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B0=9C=ED=96=89=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/service/TransactionUserService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 3e1563dfa2..965ba248ff 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -12,7 +12,6 @@ public class TransactionUserService implements UserService { private final AppUserService appUserService; private final DataSource dataSource; - public TransactionUserService(final AppUserService appUserService, final DataSource dataSource) { this.appUserService = appUserService; this.dataSource = dataSource; From 37e3783038f5deb65309cf09447be60af70491b2 Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Sun, 8 Oct 2023 20:19:55 +0900 Subject: [PATCH 16/17] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/service/TransactionUserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/service/TransactionUserService.java b/app/src/main/java/com/techcourse/service/TransactionUserService.java index 965ba248ff..641909e040 100644 --- a/app/src/main/java/com/techcourse/service/TransactionUserService.java +++ b/app/src/main/java/com/techcourse/service/TransactionUserService.java @@ -61,7 +61,7 @@ private void executeTransactionWithRunnable(final Runnable runnable) { }); } - private T executeTransactionWithSupplier(Supplier supplier) { + private T executeTransactionWithSupplier(final Supplier supplier) { Transaction transaction = Transaction.start(dataSource); transaction.setReadOnly(readOnly); try { From 51c1cb9c3881a79230741a550bdfe9225eabb1bd Mon Sep 17 00:00:00 2001 From: woosung1223 Date: Tue, 10 Oct 2023 10:28:05 +0900 Subject: [PATCH 17/17] =?UTF-8?q?refactor:=20ThreadLocal=EC=9D=84=20?= =?UTF-8?q?=EC=A0=95=EC=A0=81=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=EB=A1=9C=20=EC=B4=88=EA=B8=B0=ED=99=94?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/TransactionSynchronizationManager.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 27434c806f..f1a93eac7e 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -7,11 +7,7 @@ public abstract class TransactionSynchronizationManager { - private static final ThreadLocal> resources = new ThreadLocal<>(); - - static { - resources.set(new HashMap<>()); - } + private static final ThreadLocal> resources = ThreadLocal.withInitial(HashMap::new); private TransactionSynchronizationManager() { }