From 0b1df049d62320b3f69a96469428988719651012 Mon Sep 17 00:00:00 2001 From: fromitive Date: Fri, 11 Oct 2024 10:19:19 +0900 Subject: [PATCH 01/24] =?UTF-8?q?refactor:=20=EC=95=84=EC=A3=BC=20?= =?UTF-8?q?=EC=82=AC=EC=86=8C=ED=95=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/interface21/jdbc/core/JdbcTemplate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java index b33f04d1ef..474488d894 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java @@ -23,10 +23,10 @@ public JdbcTemplate(final DataSource dataSource) { } public void update(String sql, PreparedStatementSetter preparedStatementSetter) { - execute(sql, preparedStatementSetter, (preparedStatement -> { + execute(sql, preparedStatementSetter, (preparedStatement) -> { preparedStatement.executeUpdate(); return null; - })); + }); } private T execute(String sql, PreparedStatementSetter preparedStatementSetter, From b51ab40a35a446b7e33e2a0e8c44685fd24f3b0d Mon Sep 17 00:00:00 2001 From: fromitive Date: Fri, 11 Oct 2024 12:52:46 +0900 Subject: [PATCH 02/24] =?UTF-8?q?feat:=20transaction=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/interface21/jdbc/core/JdbcTemplate.java | 16 +++++++++++++++- .../interface21/jdbc/core/JdbcTemplateTest.java | 15 +++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java index 474488d894..8f96d1d157 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java @@ -5,6 +5,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLTransactionRollbackException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -35,12 +36,25 @@ private T execute(String sql, PreparedStatementSetter preparedStatementSette PreparedStatement preparedStatement = connection.prepareStatement(sql); ) { preparedStatementSetter.setValues(preparedStatement); - return strategy.execute(preparedStatement); + return executeTransaction(strategy, preparedStatement, connection); } catch (SQLException e) { throw new DataAccessException(e); } } + private T executeTransaction(PreparedStatementStrategy strategy, PreparedStatement preparedStatement, + Connection connection) throws SQLException { + try { + connection.setAutoCommit(false); + T result = strategy.execute(preparedStatement); + connection.commit(); + return result; + } catch (SQLException e) { + connection.rollback(); + throw new SQLTransactionRollbackException(e); + } + } + public List query(String sql, PreparedStatementSetter preparedStatementSetter, RowMapper rowMapper) { return execute(sql, preparedStatementSetter, preparedStatement -> getResults(rowMapper, preparedStatement)); } diff --git a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java index 3cabb97d40..be1fa7d3b7 100644 --- a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java +++ b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java @@ -51,8 +51,7 @@ void updateWithException() throws SQLException { // when assertThatExceptionOfType(DataAccessException.class) - .isThrownBy(() -> jdbcTemplate.update("update error", preparedStatementSetter)) - .withCause(sqlException); + .isThrownBy(() -> jdbcTemplate.update("update error", preparedStatementSetter)); // then verify(connection).close(); @@ -115,8 +114,7 @@ void queryWithExecuteQueryException() throws SQLException { // when assertThatExceptionOfType(DataAccessException.class) - .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)) - .withCause(sqlException); + .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)); // then verify(connection).close(); @@ -139,8 +137,7 @@ void queryWithRowMapException() throws SQLException { // when assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)) - .withCause(sqlException); + .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)); // then verify(connection).close(); @@ -224,8 +221,7 @@ void queryForObjectWithExecuteQueryException() throws SQLException { // when assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)) - .withCause(sqlException); + .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)); // then verify(connection).close(); @@ -248,8 +244,7 @@ void queryForObjectWithRowMapException() throws SQLException { // when assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)) - .withCause(sqlException); + .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)); // then verify(connection).close(); From f284767a692f2e43329b343180fe4d12e43a49db Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 11:20:30 +0900 Subject: [PATCH 03/24] =?UTF-8?q?feat:=20transaction=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20update=20query=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface21/jdbc/core/JdbcTemplate.java | 26 +++++++++++-------- .../jdbc/core/JdbcTemplateTest.java | 24 +++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java index 8f96d1d157..cb92a10d3a 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java @@ -5,7 +5,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLTransactionRollbackException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -30,28 +29,33 @@ public void update(String sql, PreparedStatementSetter preparedStatementSetter) }); } + public void update(String sql, Connection connection, PreparedStatementSetter preparedStatementSetter) { + executeWithExternalConnection(sql, connection, preparedStatementSetter, (preparedStatement) -> { + preparedStatement.executeUpdate(); + return null; + }); + } + private T execute(String sql, PreparedStatementSetter preparedStatementSetter, PreparedStatementStrategy strategy) { try (Connection connection = dataSource.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); ) { preparedStatementSetter.setValues(preparedStatement); - return executeTransaction(strategy, preparedStatement, connection); + return strategy.execute(preparedStatement); } catch (SQLException e) { throw new DataAccessException(e); } } - private T executeTransaction(PreparedStatementStrategy strategy, PreparedStatement preparedStatement, - Connection connection) throws SQLException { - try { - connection.setAutoCommit(false); - T result = strategy.execute(preparedStatement); - connection.commit(); - return result; + private T executeWithExternalConnection(String sql, Connection connection, + PreparedStatementSetter preparedStatementSetter, + PreparedStatementStrategy strategy) { + try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { + preparedStatementSetter.setValues(preparedStatement); + return strategy.execute(preparedStatement); } catch (SQLException e) { - connection.rollback(); - throw new SQLTransactionRollbackException(e); + throw new DataAccessException(e); } } diff --git a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java index be1fa7d3b7..7d504a266e 100644 --- a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java +++ b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java @@ -98,6 +98,30 @@ void updateWithNoParameter() throws SQLException { verifyNoInteractions(resultSet); } + @DisplayName("파라미터로 받은 connection을 이용해 update 실행할 때 connection은 close되지 않는다.") + @Test + void updateWithConnectionParameter() throws SQLException { + // given + Connection externalConnection = mock(Connection.class); + PreparedStatement externalPreparedStatement = mock(PreparedStatement.class); + given(externalConnection.prepareStatement(anyString())).willReturn(externalPreparedStatement); + + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + PreparedStatementSetter preparedStatementSetter = (preparedStatement) -> { + }; + + // when + jdbcTemplate.update("insert into user (account, age, email) values ('pororo', 20, 'proro@zzang.com')", + externalConnection, + preparedStatementSetter); + + // then + verify(externalPreparedStatement, times(0)).setObject(anyInt(), any()); + verify(externalPreparedStatement).close(); + verify(externalConnection, times(0)).close(); + verifyNoInteractions(resultSet); + } + @DisplayName("query 내에 쿼리문 실행 중 예외가 발생할 경우 관련된 리소스들이 닫혀야 한다.") @Test From 342ed40b4e88fe91f23d07f035d1c34105510771 Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 11:42:32 +0900 Subject: [PATCH 04/24] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20transaction=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/dao/UserDao.java | 17 +++++++--- .../com/techcourse/dao/UserHistoryDao.java | 15 ++++++++- .../com/techcourse/service/UserService.java | 33 +++++++++++++++---- .../service/MockUserHistoryDao.java | 8 ++++- .../techcourse/service/UserServiceTest.java | 17 +++++----- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 01fcb2f48d..3bc00af398 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -3,6 +3,7 @@ import com.interface21.jdbc.core.JdbcTemplate; import com.interface21.jdbc.core.RowMapper; import com.techcourse.domain.User; +import java.sql.Connection; import java.util.List; import javax.sql.DataSource; import org.slf4j.Logger; @@ -29,8 +30,7 @@ public UserDao(DataSource dataSource) { public void insert(final User user) { final var sql = "insert into users (account, password, email) values (?, ?, ?)"; - jdbcTemplate.update(sql, (preparedStatement) -> - { + jdbcTemplate.update(sql, (preparedStatement) -> { preparedStatement.setObject(1, user.getAccount()); preparedStatement.setObject(2, user.getPassword()); preparedStatement.setObject(3, user.getEmail()); @@ -39,8 +39,7 @@ public void insert(final User user) { public void update(final User user) { final var sql = "update users set account = ?, password = ?, email = ? where id = ?"; - jdbcTemplate.update(sql, (preparedStatement) -> - { + jdbcTemplate.update(sql, (preparedStatement) -> { preparedStatement.setObject(1, user.getAccount()); preparedStatement.setObject(2, user.getPassword()); preparedStatement.setObject(3, user.getEmail()); @@ -67,4 +66,14 @@ public User findByAccount(final String account) { preparedStatement.setObject(1, account); }, USER_ROW_MAPPER); } + + public void update(Connection connection, User user) { + final var sql = "update users set account = ?, password = ?, email = ? where id = ?"; + jdbcTemplate.update(sql, connection, (preparedStatement) -> { + preparedStatement.setObject(1, user.getAccount()); + preparedStatement.setObject(2, user.getPassword()); + preparedStatement.setObject(3, user.getEmail()); + preparedStatement.setObject(4, 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 b3d84651f3..9ebdf4cdf1 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -2,6 +2,7 @@ import com.interface21.jdbc.core.JdbcTemplate; import com.techcourse.domain.UserHistory; +import java.sql.Connection; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +29,19 @@ public void log(final UserHistory userHistory) { preparedStatement.setObject(3, userHistory.getPassword()); preparedStatement.setObject(4, userHistory.getEmail()); preparedStatement.setObject(5, userHistory.getCreatedAt()); - preparedStatement.setObject(5, userHistory.getCreateBy()); + preparedStatement.setObject(6, userHistory.getCreateBy()); + }); + } + + public void log(Connection connection, UserHistory userHistory) { + final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; + jdbcTemplate.update(sql, connection, (preparedStatement) -> { + preparedStatement.setObject(1, userHistory.getUserId()); + preparedStatement.setObject(2, userHistory.getAccount()); + preparedStatement.setObject(3, userHistory.getPassword()); + preparedStatement.setObject(4, userHistory.getEmail()); + preparedStatement.setObject(5, userHistory.getCreatedAt()); + preparedStatement.setObject(6, 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 fcf2159dc8..d8b83c1863 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,16 +1,22 @@ package com.techcourse.service; +import com.interface21.dao.DataAccessException; import com.techcourse.dao.UserDao; import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; public class UserService { - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; + private UserDao userDao; + private UserHistoryDao userHistoryDao; + private DataSource dataSource; - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { + public UserService(DataSource datasource, UserDao userDao, UserHistoryDao userHistoryDao) { + this.dataSource = datasource; this.userDao = userDao; this.userHistoryDao = userHistoryDao; } @@ -23,10 +29,23 @@ 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); + public void changePassword(final long id, final String newPassword, final String createBy) throws SQLException { + Connection connection = dataSource.getConnection(); + try { + connection.setAutoCommit(false); + doChangePassword(id, newPassword, createBy, connection); + } catch (DataAccessException e) { + connection.rollback(); + throw new DataAccessException(e); + } finally { + connection.close(); + } + } + + private void doChangePassword(long id, String newPassword, String createBy, Connection connection) { + User user = findById(id); user.changePassword(newPassword); - userDao.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); + userDao.update(connection, user); + userHistoryDao.log(connection, new UserHistory(user, createBy)); } } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index f3467b8bb4..2fe7707231 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -4,6 +4,7 @@ import com.interface21.jdbc.core.JdbcTemplate; import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.UserHistory; +import java.sql.Connection; public class MockUserHistoryDao extends UserHistoryDao { @@ -12,7 +13,12 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { } @Override - public void log(final UserHistory userHistory) { + public void log(UserHistory userHistory) { + throw new DataAccessException(); + } + + @Override + public void log(Connection connection, UserHistory userHistory) { throw new DataAccessException(); } } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index 025caabea2..a8db68e36d 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -10,30 +10,32 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; +import java.sql.SQLException; +import javax.sql.DataSource; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -@Disabled class UserServiceTest { private JdbcTemplate jdbcTemplate; private UserDao userDao; + private DataSource dataSource; @BeforeEach void setUp() { - this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + this.dataSource = DataSourceConfig.getInstance(); + this.jdbcTemplate = new JdbcTemplate(dataSource); this.userDao = new UserDao(jdbcTemplate); - DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); + DatabasePopulatorUtils.execute(dataSource); final var user = new User("gugu", "password", "hkkang@woowahan.com"); userDao.insert(user); } @Test - void testChangePassword() { + void testChangePassword() throws SQLException { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userService = new UserService(dataSource, userDao, userHistoryDao); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -48,8 +50,7 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); - + final var userService = new UserService(dataSource, userDao, userHistoryDao); final var newPassword = "newPassword"; final var createBy = "gugu"; // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. From 551777493790d223ceb238d7900cda5b56b5fbe6 Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 11:48:29 +0900 Subject: [PATCH 05/24] =?UTF-8?q?test:=20=EC=9D=98=EB=8F=84=ED=95=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EA=B2=80=EC=A6=9D=EA=B5=AC=EB=AC=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface21/jdbc/core/JdbcTemplateTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java index 7d504a266e..2a0cf7bfb2 100644 --- a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java +++ b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java @@ -51,7 +51,8 @@ void updateWithException() throws SQLException { // when assertThatExceptionOfType(DataAccessException.class) - .isThrownBy(() -> jdbcTemplate.update("update error", preparedStatementSetter)); + .isThrownBy(() -> jdbcTemplate.update("update error", preparedStatementSetter)) + .withCause(sqlException); // then verify(connection).close(); @@ -138,7 +139,8 @@ void queryWithExecuteQueryException() throws SQLException { // when assertThatExceptionOfType(DataAccessException.class) - .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)); + .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)) + .withCause(sqlException); // then verify(connection).close(); @@ -161,7 +163,8 @@ void queryWithRowMapException() throws SQLException { // when assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)); + .isThrownBy(() -> jdbcTemplate.query("select error", preparedStatementSetter, mapper)) + .withCause(sqlException); // then verify(connection).close(); @@ -245,7 +248,8 @@ void queryForObjectWithExecuteQueryException() throws SQLException { // when assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)); + .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)) + .withCause(sqlException); // then verify(connection).close(); @@ -268,7 +272,8 @@ void queryForObjectWithRowMapException() throws SQLException { // when assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)); + .isThrownBy(() -> jdbcTemplate.queryForObject("select error", preparedStatementSetter, mapper)) + .withCause(sqlException); // then verify(connection).close(); From f8a2872886b72896e0feee48b6ce6160537db607 Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 19:40:13 +0900 Subject: [PATCH 06/24] =?UTF-8?q?fix:=20commit=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/service/UserService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index d8b83c1863..7dd1883701 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -34,6 +34,7 @@ public void changePassword(final long id, final String newPassword, final String try { connection.setAutoCommit(false); doChangePassword(id, newPassword, createBy, connection); + connection.commit(); } catch (DataAccessException e) { connection.rollback(); throw new DataAccessException(e); From 54ae41537c141411b572beedba782c3c17920867 Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 20:17:00 +0900 Subject: [PATCH 07/24] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=ED=98=95?= =?UTF-8?q?=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20try-catch=20=EA=B5=AC?= =?UTF-8?q?=EB=AC=B8=20=EC=B5=9C=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/service/UserService.java | 26 +++++++++++++++---- .../support/CheckedExceptionExecutor.java | 6 +++++ .../support/SQLExceptionConsumer.java | 14 ++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java create mode 100644 app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 7dd1883701..916d81e2e1 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.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; +import com.techcourse.support.SQLExceptionConsumer; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; @@ -29,20 +30,35 @@ public void insert(final User user) { userDao.insert(user); } - public void changePassword(final long id, final String newPassword, final String createBy) throws SQLException { - Connection connection = dataSource.getConnection(); + public void changePassword(final long id, final String newPassword, final String createBy) { + Connection connection = SQLExceptionConsumer.execute(dataSource::getConnection, "connetion을 가져오는데 실패했습니다"); try { connection.setAutoCommit(false); doChangePassword(id, newPassword, createBy, connection); connection.commit(); - } catch (DataAccessException e) { - connection.rollback(); + } catch (SQLException e) { + rollbackTransaction(connection); throw new DataAccessException(e); } finally { - connection.close(); + closeConnection(connection); } } + private void closeConnection(Connection connection) { + SQLExceptionConsumer.execute(() -> { + connection.close(); + return null; + }, "connection을 닫는데 실패했습니다."); + } + + private void rollbackTransaction(Connection connection) { + SQLExceptionConsumer.execute(() -> { + connection.close(); + return null; + }, "connection을 rollback하는데 실패했습니다."); + } + + private void doChangePassword(long id, String newPassword, String createBy, Connection connection) { User user = findById(id); user.changePassword(newPassword); diff --git a/app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java b/app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java new file mode 100644 index 0000000000..6705d9c375 --- /dev/null +++ b/app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java @@ -0,0 +1,6 @@ +package com.techcourse.support; + +@FunctionalInterface +public interface CheckedExceptionExecutor { + R execute() throws T; +} \ No newline at end of file diff --git a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java b/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java new file mode 100644 index 0000000000..6fa7700153 --- /dev/null +++ b/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java @@ -0,0 +1,14 @@ +package com.techcourse.support; + +import com.interface21.dao.DataAccessException; +import java.sql.SQLException; + +public class SQLExceptionConsumer { + public static R execute(CheckedExceptionExecutor exceptionExecutor, String errorMessage) { + try { + return exceptionExecutor.execute(); + } catch (Exception e) { + throw new DataAccessException(errorMessage); + } + } +} From ae5b6cc053668e8b8d4703525171401210f57d72 Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 20:17:36 +0900 Subject: [PATCH 08/24] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A0=9C=EB=84=A4=EB=A6=AD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/support/SQLExceptionConsumer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java b/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java index 6fa7700153..5a68ce20d8 100644 --- a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java +++ b/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java @@ -4,7 +4,7 @@ import java.sql.SQLException; public class SQLExceptionConsumer { - public static R execute(CheckedExceptionExecutor exceptionExecutor, String errorMessage) { + public static R execute(CheckedExceptionExecutor exceptionExecutor, String errorMessage) { try { return exceptionExecutor.execute(); } catch (Exception e) { From 695fda3118c89dd83b92efa0efbdf5366d68a813 Mon Sep 17 00:00:00 2001 From: fromitive Date: Sat, 12 Oct 2024 20:19:06 +0900 Subject: [PATCH 09/24] =?UTF-8?q?fix:=20rollback=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/service/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 916d81e2e1..4a35612232 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -53,7 +53,7 @@ private void closeConnection(Connection connection) { private void rollbackTransaction(Connection connection) { SQLExceptionConsumer.execute(() -> { - connection.close(); + connection.rollback(); return null; }, "connection을 rollback하는데 실패했습니다."); } From 90cf77c6de4a44bd158684374ca98289053a7e38 Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 12:56:59 +0900 Subject: [PATCH 10/24] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EB=A5=BC?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=ED=95=98=EA=B2=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/support/SQLExceptionConsumer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java b/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java index 5a68ce20d8..e87efec47e 100644 --- a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java +++ b/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java @@ -7,7 +7,7 @@ public class SQLExceptionConsumer { public static R execute(CheckedExceptionExecutor exceptionExecutor, String errorMessage) { try { return exceptionExecutor.execute(); - } catch (Exception e) { + } catch (SQLException e) { throw new DataAccessException(errorMessage); } } From bf6ac86a6c8e3dbadd0f67e3696e34dc4e2b6f44 Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 15:14:15 +0900 Subject: [PATCH 11/24] =?UTF-8?q?refactor:=20=EB=B9=84=EC=A6=88=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=EA=B3=BC=20=ED=8A=B8=EB=A0=8C?= =?UTF-8?q?=EC=A0=9D=EC=85=98=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/AppUserService.java | 35 ++++++++++ .../com/techcourse/service/TxUserService.java | 54 ++++++++++++++++ .../com/techcourse/service/UserService.java | 64 ++----------------- .../techcourse/service/UserServiceTest.java | 14 ++-- 4 files changed, 101 insertions(+), 66 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 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..0aadc5ae97 --- /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 UserDao userDao; + private UserHistoryDao userHistoryDao; + + public AppUserService(UserDao userDao, 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) { + User user = findById(id); + user.changePassword(newPassword); + userDao.update(user); + userHistoryDao.log(new UserHistory(user, createBy)); + } + +} diff --git a/app/src/main/java/com/techcourse/service/TxUserService.java b/app/src/main/java/com/techcourse/service/TxUserService.java new file mode 100644 index 0000000000..d0cd6f0f89 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,54 @@ +package com.techcourse.service; + +import com.interface21.dao.DataAccessException; +import com.interface21.jdbc.datasource.DataSourceUtils; +import com.interface21.transaction.support.TransactionSynchronizationManager; +import com.techcourse.config.DataSourceConfig; +import com.techcourse.domain.User; +import com.techcourse.support.SQLExceptionConsumer; +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; + +public class TxUserService implements UserService { + + private final UserService userService; + + public TxUserService(UserService userService) { + this.userService = userService; + } + + @Override + public User findById(long id) { + return userService.findById(id); + } + + @Override + public void insert(User user) { + userService.insert(user); + } + + @Override + public void changePassword(final long id, final String newPassword, final String createBy) { + DataSource dataSource = DataSourceConfig.getInstance(); + Connection connection = DataSourceUtils.getConnection(dataSource); + try { + connection.setAutoCommit(false); + userService.changePassword(id, newPassword, createBy); + connection.commit(); + } catch (SQLException e) { + rollbackTransaction(connection); + throw new DataAccessException(e); + } finally { + DataSourceUtils.releaseConnection(connection, dataSource); + TransactionSynchronizationManager.unbindResource(dataSource); + } + } + + private void rollbackTransaction(Connection connection) { + SQLExceptionConsumer.execute(() -> { + connection.rollback(); + return null; + }, "connection을 rollback하는데 실패했습니다."); + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index 4a35612232..b14dbcacbf 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,68 +1,12 @@ package com.techcourse.service; -import com.interface21.dao.DataAccessException; -import com.techcourse.dao.UserDao; -import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; -import com.techcourse.domain.UserHistory; -import com.techcourse.support.SQLExceptionConsumer; -import java.sql.Connection; -import java.sql.SQLException; -import javax.sql.DataSource; -public class UserService { +public interface UserService { - private UserDao userDao; - private UserHistoryDao userHistoryDao; - private DataSource dataSource; + User findById(final long id); - public UserService(DataSource datasource, UserDao userDao, UserHistoryDao userHistoryDao) { - this.dataSource = datasource; - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - } + 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) { - Connection connection = SQLExceptionConsumer.execute(dataSource::getConnection, "connetion을 가져오는데 실패했습니다"); - try { - connection.setAutoCommit(false); - doChangePassword(id, newPassword, createBy, connection); - connection.commit(); - } catch (SQLException e) { - rollbackTransaction(connection); - throw new DataAccessException(e); - } finally { - closeConnection(connection); - } - } - - private void closeConnection(Connection connection) { - SQLExceptionConsumer.execute(() -> { - connection.close(); - return null; - }, "connection을 닫는데 실패했습니다."); - } - - private void rollbackTransaction(Connection connection) { - SQLExceptionConsumer.execute(() -> { - connection.rollback(); - return null; - }, "connection을 rollback하는데 실패했습니다."); - } - - - private void doChangePassword(long id, String newPassword, String createBy, Connection connection) { - User user = findById(id); - user.changePassword(newPassword); - userDao.update(connection, user); - userHistoryDao.log(connection, new UserHistory(user, createBy)); - } + void changePassword(final long id, final String newPassword, final String createBy); } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index a8db68e36d..40d95eab15 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -10,7 +10,6 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; -import java.sql.SQLException; import javax.sql.DataSource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,9 +32,10 @@ void setUp() { } @Test - void testChangePassword() throws SQLException { + void testChangePassword() { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(dataSource, userDao, userHistoryDao); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TxUserService(appUserService); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -50,12 +50,14 @@ void testChangePassword() throws SQLException { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(dataSource, userDao, userHistoryDao); + final var appUserService = new AppUserService(userDao, userHistoryDao); + final var userService = new TxUserService(appUserService); + final var newPassword = "newPassword"; - final var createBy = "gugu"; + final var createdBy = "gugu"; // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. assertThrows(DataAccessException.class, - () -> userService.changePassword(1L, newPassword, createBy)); + () -> userService.changePassword(1L, newPassword, createdBy)); final var actual = userService.findById(1L); From 67794e6af958ded3ba0a7d435245e2fc107dcd5a Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 15:14:38 +0900 Subject: [PATCH 12/24] =?UTF-8?q?feat:=20TransactionSynchronizationManager?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TransactionSynchronizationManager.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java index deeeb81b57..b2306e8537 100644 --- a/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java @@ -1,23 +1,30 @@ package com.interface21.transaction.support; -import javax.sql.DataSource; import java.sql.Connection; +import java.util.HashMap; import java.util.Map; +import javax.sql.DataSource; public abstract class TransactionSynchronizationManager { - private static final ThreadLocal> resources = new ThreadLocal<>(); + private static final ThreadLocal> resources = ThreadLocal.withInitial(HashMap::new); - private TransactionSynchronizationManager() {} + private TransactionSynchronizationManager() { + } public static Connection getResource(DataSource key) { - return null; + Map binds = resources.get(); + return binds.get(key); } public static void bindResource(DataSource key, Connection value) { + Map binds = resources.get(); + binds.put(key, value); + resources.set(binds); } public static Connection unbindResource(DataSource key) { - return null; + Map binds = resources.get(); + return binds.remove(key); } } From 676a201bd20135ba2f9e01f9f7e59374851b2c2a Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 15:16:29 +0900 Subject: [PATCH 13/24] =?UTF-8?q?feat:=20connection=EC=9D=84=20Transaction?= =?UTF-8?q?SynchronizationManager=20=EC=97=90=EC=84=9C=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=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 --- jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java index cb92a10d3a..a4814a5709 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java @@ -1,6 +1,7 @@ package com.interface21.jdbc.core; import com.interface21.dao.DataAccessException; +import com.interface21.jdbc.datasource.DataSourceUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -38,7 +39,7 @@ public void update(String sql, Connection connection, PreparedStatementSetter pr private T execute(String sql, PreparedStatementSetter preparedStatementSetter, PreparedStatementStrategy strategy) { - try (Connection connection = dataSource.getConnection(); + try (Connection connection = DataSourceUtils.getConnection(dataSource); PreparedStatement preparedStatement = connection.prepareStatement(sql); ) { preparedStatementSetter.setValues(preparedStatement); From 6d13c4457e07e9cc45d40c42825e7a927581c1e3 Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 16:10:07 +0900 Subject: [PATCH 14/24] =?UTF-8?q?refactor:=20CheckedExceptionExecutor=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EA=B0=9D=EC=B2=B4=20jdbc=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/interface21/jdbc}/support/CheckedExceptionExecutor.java | 2 +- .../com/interface21/jdbc}/support/SQLExceptionConsumer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {app/src/main/java/com/techcourse => jdbc/src/main/java/com/interface21/jdbc}/support/CheckedExceptionExecutor.java (75%) rename {app/src/main/java/com/techcourse => jdbc/src/main/java/com/interface21/jdbc}/support/SQLExceptionConsumer.java (91%) diff --git a/app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java b/jdbc/src/main/java/com/interface21/jdbc/support/CheckedExceptionExecutor.java similarity index 75% rename from app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java rename to jdbc/src/main/java/com/interface21/jdbc/support/CheckedExceptionExecutor.java index 6705d9c375..0fd685c541 100644 --- a/app/src/main/java/com/techcourse/support/CheckedExceptionExecutor.java +++ b/jdbc/src/main/java/com/interface21/jdbc/support/CheckedExceptionExecutor.java @@ -1,4 +1,4 @@ -package com.techcourse.support; +package com.interface21.jdbc.support; @FunctionalInterface public interface CheckedExceptionExecutor { diff --git a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java b/jdbc/src/main/java/com/interface21/jdbc/support/SQLExceptionConsumer.java similarity index 91% rename from app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java rename to jdbc/src/main/java/com/interface21/jdbc/support/SQLExceptionConsumer.java index e87efec47e..a8d1190b7d 100644 --- a/app/src/main/java/com/techcourse/support/SQLExceptionConsumer.java +++ b/jdbc/src/main/java/com/interface21/jdbc/support/SQLExceptionConsumer.java @@ -1,4 +1,4 @@ -package com.techcourse.support; +package com.interface21.jdbc.support; import com.interface21.dao.DataAccessException; import java.sql.SQLException; From e6e2d743e860b8b7d2447f4c1673606f83db00eb Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 16:24:01 +0900 Subject: [PATCH 15/24] =?UTF-8?q?refactor:=20releaseConnection=EC=97=90?= =?UTF-8?q?=EC=84=9C=20TransactionSynchronizationManager=EB=A5=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface21/jdbc/core/JdbcTemplate.java | 9 ++-- .../jdbc/datasource/DataSourceUtils.java | 14 ++++-- .../TransactionSynchronizationManager.java | 1 - .../jdbc/core/JdbcTemplateTest.java | 50 +++++++++---------- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java index a4814a5709..d1095a7d67 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java @@ -39,16 +39,17 @@ public void update(String sql, Connection connection, PreparedStatementSetter pr private T execute(String sql, PreparedStatementSetter preparedStatementSetter, PreparedStatementStrategy strategy) { - try (Connection connection = DataSourceUtils.getConnection(dataSource); - PreparedStatement preparedStatement = connection.prepareStatement(sql); - ) { + Connection connection = DataSourceUtils.getConnection(dataSource); + try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { preparedStatementSetter.setValues(preparedStatement); return strategy.execute(preparedStatement); } catch (SQLException e) { throw new DataAccessException(e); + } finally { + DataSourceUtils.releaseConnection(dataSource); } } - + private T executeWithExternalConnection(String sql, Connection connection, PreparedStatementSetter preparedStatementSetter, PreparedStatementStrategy strategy) { diff --git a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java index 18b5f3e5de..cee1ff3c72 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java @@ -2,15 +2,15 @@ import com.interface21.jdbc.CannotGetJdbcConnectionException; import com.interface21.transaction.support.TransactionSynchronizationManager; - -import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; +import javax.sql.DataSource; // 4단계 미션에서 사용할 것 public abstract class DataSourceUtils { - private DataSourceUtils() {} + private DataSourceUtils() { + } public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { Connection connection = TransactionSynchronizationManager.getResource(dataSource); @@ -27,9 +27,13 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd } } - public static void releaseConnection(Connection connection, DataSource dataSource) { + public static void releaseConnection(DataSource dataSource) { try { - connection.close(); + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + if (connection.getAutoCommit()) { + TransactionSynchronizationManager.unbindResource(dataSource); + connection.close(); + } } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); } diff --git a/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java index b2306e8537..1b041d1eaf 100644 --- a/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/com/interface21/transaction/support/TransactionSynchronizationManager.java @@ -20,7 +20,6 @@ public static Connection getResource(DataSource key) { public static void bindResource(DataSource key, Connection value) { Map binds = resources.get(); binds.put(key, value); - resources.set(binds); } public static Connection unbindResource(DataSource key) { diff --git a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java index 2a0cf7bfb2..49937952a9 100644 --- a/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java +++ b/jdbc/src/test/java/com/interface21/jdbc/core/JdbcTemplateTest.java @@ -32,6 +32,7 @@ class JdbcTemplateTest { void setup() throws Exception { given(this.dataSource.getConnection()).willReturn(this.connection); given(this.connection.prepareStatement(anyString())).willReturn(this.preparedStatement); + given(this.connection.getAutoCommit()).willReturn(true); given(this.preparedStatement.executeQuery()).willReturn(this.resultSet); given(this.preparedStatement.executeQuery(anyString())).willReturn(this.resultSet); given(this.preparedStatement.getConnection()).willReturn(this.connection); @@ -99,31 +100,6 @@ void updateWithNoParameter() throws SQLException { verifyNoInteractions(resultSet); } - @DisplayName("파라미터로 받은 connection을 이용해 update 실행할 때 connection은 close되지 않는다.") - @Test - void updateWithConnectionParameter() throws SQLException { - // given - Connection externalConnection = mock(Connection.class); - PreparedStatement externalPreparedStatement = mock(PreparedStatement.class); - given(externalConnection.prepareStatement(anyString())).willReturn(externalPreparedStatement); - - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); - PreparedStatementSetter preparedStatementSetter = (preparedStatement) -> { - }; - - // when - jdbcTemplate.update("insert into user (account, age, email) values ('pororo', 20, 'proro@zzang.com')", - externalConnection, - preparedStatementSetter); - - // then - verify(externalPreparedStatement, times(0)).setObject(anyInt(), any()); - verify(externalPreparedStatement).close(); - verify(externalConnection, times(0)).close(); - verifyNoInteractions(resultSet); - } - - @DisplayName("query 내에 쿼리문 실행 중 예외가 발생할 경우 관련된 리소스들이 닫혀야 한다.") @Test void queryWithExecuteQueryException() throws SQLException { @@ -332,6 +308,30 @@ void queryForObjectWithNoResultException() throws SQLException { verify(this.preparedStatement).close(); } + @DisplayName("connection.setAutoCommit이 false일 경우 close되지 않는다.") + @Test + void notClosedWhenSetAutoCommitIsFalse() throws SQLException { + // given + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + given(connection.getAutoCommit()).willReturn(false); + PreparedStatementSetter preparedStatementSetter = (preparedStatement) -> { + preparedStatement.setString(1, "pororo"); + preparedStatement.setString(2, "poke"); + }; + SQLException sqlException = new SQLException("업데이트 중 예외 발생"); + given(this.preparedStatement.executeUpdate()).willThrow(sqlException); + + // when + assertThatExceptionOfType(DataAccessException.class) + .isThrownBy(() -> jdbcTemplate.update("update error", preparedStatementSetter)) + .withCause(sqlException); + + // then + verify(preparedStatement).close(); + verify(connection, times(0)).close(); + verifyNoInteractions(resultSet); + } + record TestUser(String account, String password) { } } From 5388089dcda98f31987997a8d1c60cd52d7bd8b6 Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 16:24:10 +0900 Subject: [PATCH 16/24] =?UTF-8?q?refactor:=20TransactionSynchronizationMan?= =?UTF-8?q?ager=20=EC=9D=98=EC=A1=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/techcourse/service/TxUserService.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/TxUserService.java b/app/src/main/java/com/techcourse/service/TxUserService.java index d0cd6f0f89..fb1972e5e3 100644 --- a/app/src/main/java/com/techcourse/service/TxUserService.java +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -2,10 +2,9 @@ import com.interface21.dao.DataAccessException; import com.interface21.jdbc.datasource.DataSourceUtils; -import com.interface21.transaction.support.TransactionSynchronizationManager; +import com.interface21.jdbc.support.SQLExceptionConsumer; import com.techcourse.config.DataSourceConfig; import com.techcourse.domain.User; -import com.techcourse.support.SQLExceptionConsumer; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; @@ -36,12 +35,11 @@ public void changePassword(final long id, final String newPassword, final String connection.setAutoCommit(false); userService.changePassword(id, newPassword, createBy); connection.commit(); - } catch (SQLException e) { + } catch (DataAccessException | SQLException e) { rollbackTransaction(connection); throw new DataAccessException(e); } finally { - DataSourceUtils.releaseConnection(connection, dataSource); - TransactionSynchronizationManager.unbindResource(dataSource); + DataSourceUtils.releaseConnection(dataSource); } } From 3e1957e42b39a12d2e6cc986cd0fcb0a7514a616 Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 16:43:35 +0900 Subject: [PATCH 17/24] =?UTF-8?q?fix:=20changePassword=20=ED=95=A0=20?= =?UTF-8?q?=EB=95=8C=20connection=EC=9D=B4=20close=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/service/TxUserService.java | 2 +- .../interface21/jdbc/datasource/DataSourceUtils.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/TxUserService.java b/app/src/main/java/com/techcourse/service/TxUserService.java index fb1972e5e3..4b47f0dd54 100644 --- a/app/src/main/java/com/techcourse/service/TxUserService.java +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -39,7 +39,7 @@ public void changePassword(final long id, final String newPassword, final String rollbackTransaction(connection); throw new DataAccessException(e); } finally { - DataSourceUtils.releaseConnection(dataSource); + DataSourceUtils.releaseDataSource(dataSource); } } diff --git a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java index cee1ff3c72..2a8ecc0110 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java @@ -31,11 +31,20 @@ public static void releaseConnection(DataSource dataSource) { try { Connection connection = TransactionSynchronizationManager.getResource(dataSource); if (connection.getAutoCommit()) { - TransactionSynchronizationManager.unbindResource(dataSource); connection.close(); } } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); } } + + public static void releaseDataSource(DataSource dataSource) { + try { + Connection connection = TransactionSynchronizationManager.getResource(dataSource); + connection.close(); + TransactionSynchronizationManager.unbindResource(dataSource); + } catch (SQLException ex) { + throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); + } + } } From 493f3a010c5ca5be30037c7143ce59e4e555053b Mon Sep 17 00:00:00 2001 From: fromitive Date: Mon, 14 Oct 2024 16:53:45 +0900 Subject: [PATCH 18/24] =?UTF-8?q?refactor:=20releaseDataSource=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/interface21/jdbc/datasource/DataSourceUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java index 2a8ecc0110..639e7a6715 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java @@ -40,9 +40,8 @@ public static void releaseConnection(DataSource dataSource) { public static void releaseDataSource(DataSource dataSource) { try { - Connection connection = TransactionSynchronizationManager.getResource(dataSource); + Connection connection = TransactionSynchronizationManager.unbindResource(dataSource); connection.close(); - TransactionSynchronizationManager.unbindResource(dataSource); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); } From c6df18a40fc02f698e25c73baed2df386df4a132 Mon Sep 17 00:00:00 2001 From: fromitive Date: Tue, 15 Oct 2024 22:20:37 +0900 Subject: [PATCH 19/24] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=200=20=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/service/AppUserService.java | 3 +- study/src/main/resources/schema.sql | 2 - .../src/test/java/aop/stage0/Stage0Test.java | 25 ++++++++---- .../java/aop/stage0/TransactionHandler.java | 38 ++++++++++++++++++- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/AppUserService.java b/app/src/main/java/com/techcourse/service/AppUserService.java index 0aadc5ae97..a687f8be9b 100644 --- a/app/src/main/java/com/techcourse/service/AppUserService.java +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -4,6 +4,7 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; +import org.springframework.transaction.annotation.Transactional; public class AppUserService implements UserService { private UserDao userDao; @@ -25,11 +26,11 @@ public void insert(final User user) { } @Override + @Transactional public void changePassword(final long id, final String newPassword, final String createBy) { User user = findById(id); user.changePassword(newPassword); userDao.update(user); userHistoryDao.log(new UserHistory(user, createBy)); } - } diff --git a/study/src/main/resources/schema.sql b/study/src/main/resources/schema.sql index b495c2298b..bf9c44ac95 100644 --- a/study/src/main/resources/schema.sql +++ b/study/src/main/resources/schema.sql @@ -1,5 +1,3 @@ -# mysql 8.0.30부터는 statement.execute()으로 여러 쿼리를 한 번에 실행할 수 없다. -# 멀티 쿼리 옵션을 url로 전달하도록 수정하는 방법을 찾아서 적용하자. CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, account VARCHAR(100) NOT NULL, diff --git a/study/src/test/java/aop/stage0/Stage0Test.java b/study/src/test/java/aop/stage0/Stage0Test.java index 079cc6b5a0..2fdb1479f1 100644 --- a/study/src/test/java/aop/stage0/Stage0Test.java +++ b/study/src/test/java/aop/stage0/Stage0Test.java @@ -1,5 +1,9 @@ package aop.stage0; +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; + import aop.DataAccessException; import aop.StubUserHistoryDao; import aop.domain.User; @@ -7,6 +11,7 @@ import aop.repository.UserHistoryDao; import aop.service.AppUserService; import aop.service.UserService; +import java.lang.reflect.Proxy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -15,10 +20,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.PlatformTransactionManager; -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; - @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class Stage0Test { @@ -44,8 +45,12 @@ void setUp() { @Test void testChangePassword() { - final var appUserService = new AppUserService(userDao, userHistoryDao); - final UserService userService = null; + AppUserService appUserService = new AppUserService(userDao, userHistoryDao); + UserService userService = (UserService) Proxy.newProxyInstance( + this.getClass().getClassLoader(), + new Class[]{UserService.class}, + new TransactionHandler(platformTransactionManager, appUserService) + ); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -58,8 +63,12 @@ void testChangePassword() { @Test void testTransactionRollback() { - final var appUserService = new AppUserService(userDao, stubUserHistoryDao); - final UserService userService = null; + AppUserService appUserService = new AppUserService(userDao, stubUserHistoryDao); + UserService userService = (UserService) Proxy.newProxyInstance( + this.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..5d682ed7f3 100644 --- a/study/src/test/java/aop/stage0/TransactionHandler.java +++ b/study/src/test/java/aop/stage0/TransactionHandler.java @@ -1,15 +1,49 @@ package aop.stage0; +import aop.DataAccessException; +import aop.Transactional; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; public class TransactionHandler implements InvocationHandler { /** * @Transactional 어노테이션이 존재하는 메서드만 트랜잭션 기능을 적용하도록 만들어보자. */ + private final PlatformTransactionManager platformTransactionManager; + private final Object target; + + public TransactionHandler(PlatformTransactionManager platformTransactionManager, Object target) { + this.platformTransactionManager = platformTransactionManager; + this.target = target; + } + @Override - public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { - return null; + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes()); + if (targetMethod.isAnnotationPresent(Transactional.class)) { + return doTransaction(proxy, method, args); + } + return method.invoke(target, args); + } + + private Object doTransaction(Object proxy, Method method, Object[] args) throws Throwable { + TransactionStatus transactionStatus = platformTransactionManager.getTransaction( + new DefaultTransactionDefinition( + TransactionDefinition.PROPAGATION_REQUIRED) + ); + try { + Object result = method.invoke(target, args); + platformTransactionManager.commit(transactionStatus); + return result; + } catch (InvocationTargetException e) { + platformTransactionManager.rollback(transactionStatus); + throw new DataAccessException(e); + } } } From 82cc622d91d45046c76da33bf23c2b6287ee22ba Mon Sep 17 00:00:00 2001 From: fromitive Date: Wed, 16 Oct 2024 00:47:49 +0900 Subject: [PATCH 20/24] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=201=20=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/aop/stage1/Stage1Test.java | 27 +++++++++++++++---- .../java/aop/stage1/TransactionAdvice.java | 26 ++++++++++++++++-- .../java/aop/stage1/TransactionAdvisor.java | 11 ++++++-- .../java/aop/stage1/TransactionPointcut.java | 6 ++--- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/study/src/test/java/aop/stage1/Stage1Test.java b/study/src/test/java/aop/stage1/Stage1Test.java index 113b2e7d03..320c17ab8a 100644 --- a/study/src/test/java/aop/stage1/Stage1Test.java +++ b/study/src/test/java/aop/stage1/Stage1Test.java @@ -1,5 +1,8 @@ package aop.stage1; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import aop.DataAccessException; import aop.StubUserHistoryDao; import aop.domain.User; @@ -9,13 +12,11 @@ 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; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class Stage1Test { @@ -41,7 +42,9 @@ void setUp() { @Test void testChangePassword() { - final UserService userService = null; + UserService target = new UserService(userDao, userHistoryDao); + ProxyFactoryBean proxyFactoryBean = buildProxyFactoryBean(target); + final UserService userService = (UserService) proxyFactoryBean.getObject(); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -52,9 +55,23 @@ void testChangePassword() { assertThat(actual.getPassword()).isEqualTo(newPassword); } + private ProxyFactoryBean buildProxyFactoryBean(UserService target) { + TransactionPointcut pointcut = new TransactionPointcut(); + TransactionAdvice advice = new TransactionAdvice(platformTransactionManager); + TransactionAdvisor advisor = new TransactionAdvisor(pointcut, advice); + + ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); + proxyFactoryBean.setTarget(target); + proxyFactoryBean.setProxyTargetClass(true); + proxyFactoryBean.addAdvisor(advisor); + return proxyFactoryBean; + } + @Test void testTransactionRollback() { - final UserService userService = null; + UserService target = new UserService(userDao, stubUserHistoryDao); + ProxyFactoryBean proxyFactoryBean = buildProxyFactoryBean(target); + 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..efe4a4fefd 100644 --- a/study/src/test/java/aop/stage1/TransactionAdvice.java +++ b/study/src/test/java/aop/stage1/TransactionAdvice.java @@ -1,15 +1,37 @@ 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.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; /** * 어드바이스(advice). 부가기능을 담고 있는 클래스 */ -public class TransactionAdvice implements MethodInterceptor { +public class TransactionAdvice implements MethodInterceptor { + + private final PlatformTransactionManager platformTransactionManager; + + public TransactionAdvice(PlatformTransactionManager platformTransactionManager) { + this.platformTransactionManager = platformTransactionManager; + } @Override public Object invoke(final MethodInvocation invocation) throws Throwable { - return null; + TransactionStatus transactionStatus = platformTransactionManager.getTransaction( + new DefaultTransactionDefinition( + TransactionDefinition.PROPAGATION_REQUIRED) + ); + try { + Object result = invocation.proceed(); + platformTransactionManager.commit(transactionStatus); + return result; + } catch (DataAccessException e) { + platformTransactionManager.rollback(transactionStatus); + 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..db3051d7da 100644 --- a/study/src/test/java/aop/stage1/TransactionAdvisor.java +++ b/study/src/test/java/aop/stage1/TransactionAdvisor.java @@ -9,15 +9,22 @@ * AOP의 애스팩트(aspect)에 해당되는 클래스다. */ public class TransactionAdvisor implements PointcutAdvisor { + private final Pointcut pointcut; + private final Advice advice; + + public TransactionAdvisor(Pointcut pointcut, Advice advice) { + this.pointcut = pointcut; + this.advice = advice; + } @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..33ffd3b589 100644 --- a/study/src/test/java/aop/stage1/TransactionPointcut.java +++ b/study/src/test/java/aop/stage1/TransactionPointcut.java @@ -1,8 +1,8 @@ package aop.stage1; -import org.springframework.aop.support.StaticMethodMatcherPointcut; - +import aop.Transactional; import java.lang.reflect.Method; +import org.springframework.aop.support.StaticMethodMatcherPointcut; /** * 포인트컷(pointcut). 어드바이스를 적용할 조인 포인트를 선별하는 클래스. @@ -14,6 +14,6 @@ public class TransactionPointcut extends StaticMethodMatcherPointcut { @Override public boolean matches(final Method method, final Class targetClass) { - return false; + return method.isAnnotationPresent(Transactional.class); } } From 8af7a2dd21ff23fe033754843b6fcb2b84eadf5c Mon Sep 17 00:00:00 2001 From: fromitive Date: Wed, 16 Oct 2024 01:17:12 +0900 Subject: [PATCH 21/24] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=202=20=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/aop/stage2/AopConfig.java | 39 +++++++++++++++++++ .../src/test/java/aop/stage2/Stage2Test.java | 6 +-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/study/src/test/java/aop/stage2/AopConfig.java b/study/src/test/java/aop/stage2/AopConfig.java index 0a6e7f124e..7428fc2adf 100644 --- a/study/src/test/java/aop/stage2/AopConfig.java +++ b/study/src/test/java/aop/stage2/AopConfig.java @@ -1,8 +1,47 @@ package aop.stage2; +import aop.stage1.TransactionAdvice; +import aop.stage1.TransactionAdvisor; +import aop.stage1.TransactionPointcut; +import org.aopalliance.aop.Advice; +import org.springframework.aop.Advisor; +import org.springframework.aop.Pointcut; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; @Configuration public class AopConfig { + private final PlatformTransactionManager platformTransactionManager; + + public AopConfig(PlatformTransactionManager platformTransactionManager) { + this.platformTransactionManager = platformTransactionManager; + } + + @Bean + public Pointcut pointcut() { + return new TransactionPointcut(); + } + + @Bean + public Advice advice() { + return new TransactionAdvice(platformTransactionManager); + } + + @Bean + public Advisor advisor() { + return new TransactionAdvisor(pointcut(), advice()); + } + + @Bean + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + // 이 친구가 proxyfactorybean을 자동생성해주는 역할을 한다. + // 등록한 pointcut 조건에 맞춰 advice에 등록된 invoke 메서드를 실행한다. + DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + // targetClass를 true로 설정해줘야 인터페이스를 따로 찾지 않고 구현한 targetClass에 직접 aop가 붙는다. + defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); + return defaultAdvisorAutoProxyCreator; + } } diff --git a/study/src/test/java/aop/stage2/Stage2Test.java b/study/src/test/java/aop/stage2/Stage2Test.java index ae46641880..7f2e229ace 100644 --- a/study/src/test/java/aop/stage2/Stage2Test.java +++ b/study/src/test/java/aop/stage2/Stage2Test.java @@ -1,5 +1,8 @@ package aop.stage2; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import aop.DataAccessException; import aop.StubUserHistoryDao; import aop.domain.User; @@ -10,9 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class Stage2Test { From f53e17b645d6a7f0f8a5fa5a347850180e0ae253 Mon Sep 17 00:00:00 2001 From: fromitive Date: Tue, 22 Oct 2024 13:42:25 +0900 Subject: [PATCH 22/24] =?UTF-8?q?refactor:=20close()=EC=8B=9C=20Transactio?= =?UTF-8?q?nSynchronizationManager=EC=97=90=EC=84=9C=EB=8F=84=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C=20=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 --- .../java/com/interface21/jdbc/datasource/DataSourceUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java index 639e7a6715..b207d8ba7a 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java @@ -31,7 +31,7 @@ public static void releaseConnection(DataSource dataSource) { try { Connection connection = TransactionSynchronizationManager.getResource(dataSource); if (connection.getAutoCommit()) { - connection.close(); + releaseDataSource(dataSource); } } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection"); From 1900e792197208defd9aad8fc3f5ca631f275c96 Mon Sep 17 00:00:00 2001 From: fromitive Date: Tue, 22 Oct 2024 13:45:08 +0900 Subject: [PATCH 23/24] =?UTF-8?q?refactor:=20AppUserService=EC=97=90=20Tra?= =?UTF-8?q?nsactional=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/service/AppUserService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/techcourse/service/AppUserService.java b/app/src/main/java/com/techcourse/service/AppUserService.java index a687f8be9b..30f2278bf3 100644 --- a/app/src/main/java/com/techcourse/service/AppUserService.java +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -4,7 +4,6 @@ import com.techcourse.dao.UserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; -import org.springframework.transaction.annotation.Transactional; public class AppUserService implements UserService { private UserDao userDao; @@ -26,7 +25,6 @@ public void insert(final User user) { } @Override - @Transactional public void changePassword(final long id, final String newPassword, final String createBy) { User user = findById(id); user.changePassword(newPassword); From e85e136b89d2b29ab52a634358328550e83853ae Mon Sep 17 00:00:00 2001 From: fromitive Date: Tue, 22 Oct 2024 13:55:22 +0900 Subject: [PATCH 24/24] =?UTF-8?q?fix:=20get=EC=9D=84=20=ED=95=A0=EB=95=8C?= =?UTF-8?q?=20=EA=B0=92=20=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=EB=A5=BC?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/interface21/jdbc/datasource/DataSourceUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java index b207d8ba7a..f4764d851c 100644 --- a/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java @@ -30,7 +30,7 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd public static void releaseConnection(DataSource dataSource) { try { Connection connection = TransactionSynchronizationManager.getResource(dataSource); - if (connection.getAutoCommit()) { + if (connection != null && connection.getAutoCommit()) { releaseDataSource(dataSource); } } catch (SQLException ex) {