diff --git a/app/src/main/java/com/techcourse/dao/MySqlUserDao.java b/app/src/main/java/com/techcourse/dao/JdbcUserDao.java similarity index 94% rename from app/src/main/java/com/techcourse/dao/MySqlUserDao.java rename to app/src/main/java/com/techcourse/dao/JdbcUserDao.java index b08ca3b5ef..0cfeebac90 100644 --- a/app/src/main/java/com/techcourse/dao/MySqlUserDao.java +++ b/app/src/main/java/com/techcourse/dao/JdbcUserDao.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Optional; -public class MySqlUserDao implements UserDao { +public class JdbcUserDao implements UserDao { private static final RowMapper USER_ROW_MAPPER = resultSet -> { final long userId = resultSet.getLong("id"); @@ -19,7 +19,7 @@ public class MySqlUserDao implements UserDao { private final JdbcTemplate jdbcTemplate; - public MySqlUserDao(final JdbcTemplate jdbcTemplate) { + public JdbcUserDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } diff --git a/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java b/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java new file mode 100644 index 0000000000..3e7ab14333 --- /dev/null +++ b/app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java @@ -0,0 +1,34 @@ +package com.techcourse.dao; + +import com.techcourse.domain.UserHistory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class JdbcUserHistoryDao implements UserHistoryDao { + + private final JdbcTemplate jdbcTemplate; + + public JdbcUserHistoryDao(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void log(final UserHistory userHistory) { + final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; + jdbcTemplate.update(sql, + userHistory.getUserId(), + userHistory.getAccount(), + userHistory.getPassword(), + userHistory.getEmail(), + userHistory.getCreatedAt(), + userHistory.getCreateBy() + ); + } + +} diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index 1edd2559b7..7890231af5 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -1,62 +1,9 @@ package com.techcourse.dao; import com.techcourse.domain.UserHistory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.jdbc.core.JdbcTemplate; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; +public interface UserHistoryDao { -public class UserHistoryDao { + void log(final UserHistory userHistory); - private static final Logger log = LoggerFactory.getLogger(UserHistoryDao.class); - - private final DataSource dataSource; - - public UserHistoryDao(final DataSource dataSource) { - this.dataSource = dataSource; - } - - public UserHistoryDao(final JdbcTemplate jdbcTemplate) { - this.dataSource = null; - } - - public void log(final UserHistory userHistory) { - final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; - - Connection conn = null; - PreparedStatement pstmt = null; - try { - conn = dataSource.getConnection(); - pstmt = conn.prepareStatement(sql); - - log.debug("query : {}", sql); - - pstmt.setLong(1, userHistory.getUserId()); - pstmt.setString(2, userHistory.getAccount()); - pstmt.setString(3, userHistory.getPassword()); - pstmt.setString(4, userHistory.getEmail()); - pstmt.setObject(5, userHistory.getCreatedAt()); - pstmt.setString(6, userHistory.getCreateBy()); - pstmt.executeUpdate(); - } catch (SQLException e) { - log.error(e.getMessage(), e); - throw new RuntimeException(e); - } finally { - try { - if (pstmt != null) { - pstmt.close(); - } - } catch (SQLException ignored) {} - - try { - if (conn != null) { - conn.close(); - } - } catch (SQLException ignored) {} - } - } } diff --git a/app/src/main/java/com/techcourse/domain/UserHistory.java b/app/src/main/java/com/techcourse/domain/UserHistory.java index 7ae4fdb813..82c92ea649 100644 --- a/app/src/main/java/com/techcourse/domain/UserHistory.java +++ b/app/src/main/java/com/techcourse/domain/UserHistory.java @@ -1,5 +1,6 @@ package com.techcourse.domain; +import java.sql.Connection; import java.time.LocalDateTime; public class UserHistory { diff --git a/app/src/main/java/com/techcourse/repository/UserRepository.java b/app/src/main/java/com/techcourse/repository/UserRepository.java index 41549085df..d3cee31a41 100644 --- a/app/src/main/java/com/techcourse/repository/UserRepository.java +++ b/app/src/main/java/com/techcourse/repository/UserRepository.java @@ -3,6 +3,7 @@ import com.techcourse.dao.UserDao; import com.techcourse.domain.User; +import java.sql.Connection; import java.util.List; public class UserRepository { diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index de3db6d685..b20446a516 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,19 +1,24 @@ package com.techcourse.service; import com.techcourse.dao.UserDao; -import com.techcourse.dao.UserHistoryDao; +import com.techcourse.dao.JdbcUserHistoryDao; import com.techcourse.domain.User; import com.techcourse.domain.UserHistory; import com.techcourse.repository.UserRepository; +import org.springframework.jdbc.transaction.TransactionManager; + +import javax.sql.DataSource; public class UserService { + private final TransactionManager transactionManager; private final UserRepository userRepository; - private final UserHistoryDao userHistoryDao; + private final JdbcUserHistoryDao jdbcUserHistoryDao; - public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { + public UserService(final DataSource dataSource, final UserDao userDao, final JdbcUserHistoryDao jdbcUserHistoryDao) { + this.transactionManager = new TransactionManager(dataSource); this.userRepository = new UserRepository(userDao); - this.userHistoryDao = userHistoryDao; + this.jdbcUserHistoryDao = jdbcUserHistoryDao; } public User findById(final long id) { @@ -25,9 +30,12 @@ public void insert(final User user) { } public void changePassword(final long id, final String newPassword, final String createBy) { - final var user = findById(id); - user.changePassword(newPassword); - userRepository.update(user); - userHistoryDao.log(new UserHistory(user, createBy)); + transactionManager.execute(connection -> { + final var user = findById(id); + user.changePassword(newPassword); + userRepository.update(user); + jdbcUserHistoryDao.log(new UserHistory(user, createBy)); + return null; + }); } } diff --git a/app/src/test/java/com/techcourse/dao/MySqlUserDaoTest.java b/app/src/test/java/com/techcourse/dao/JdbcUserDaoTest.java similarity index 96% rename from app/src/test/java/com/techcourse/dao/MySqlUserDaoTest.java rename to app/src/test/java/com/techcourse/dao/JdbcUserDaoTest.java index 7c22727ad4..44d75e6667 100644 --- a/app/src/test/java/com/techcourse/dao/MySqlUserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/JdbcUserDaoTest.java @@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; -class MySqlUserDaoTest { +class JdbcUserDaoTest { private UserDao userDao; @@ -17,7 +17,7 @@ class MySqlUserDaoTest { void setup() { DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); - userDao = new MySqlUserDao(jdbcTemplate); + userDao = new JdbcUserDao(jdbcTemplate); final User user = new User("gugu", "password", "hkkang@woowahan.com"); userDao.insert(user); } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockJdbcUserHistoryDao.java similarity index 65% rename from app/src/test/java/com/techcourse/service/MockUserHistoryDao.java rename to app/src/test/java/com/techcourse/service/MockJdbcUserHistoryDao.java index 2ee12b195f..b4432997bf 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockJdbcUserHistoryDao.java @@ -1,13 +1,13 @@ package com.techcourse.service; -import com.techcourse.dao.UserHistoryDao; +import com.techcourse.dao.JdbcUserHistoryDao; import com.techcourse.domain.UserHistory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -public class MockUserHistoryDao extends UserHistoryDao { +public class MockJdbcUserHistoryDao extends JdbcUserHistoryDao { - public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { + public MockJdbcUserHistoryDao(final JdbcTemplate jdbcTemplate) { super(jdbcTemplate); } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java index e93ba4b770..8caca2c38b 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -1,12 +1,12 @@ package com.techcourse.service; import com.techcourse.config.DataSourceConfig; -import com.techcourse.dao.MySqlUserDao; -import com.techcourse.dao.UserHistoryDao; +import com.techcourse.dao.JdbcUserDao; +import com.techcourse.dao.JdbcUserHistoryDao; +import com.techcourse.dao.UserDao; import com.techcourse.domain.User; import com.techcourse.support.jdbc.init.DatabasePopulatorUtils; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -14,16 +14,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -@Disabled class UserServiceTest { private JdbcTemplate jdbcTemplate; - private MySqlUserDao userDao; + private UserDao userDao; @BeforeEach void setUp() { this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); - this.userDao = new MySqlUserDao(jdbcTemplate); + this.userDao = new JdbcUserDao(jdbcTemplate); DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); final var user = new User("gugu", "password", "hkkang@woowahan.com"); @@ -32,8 +31,8 @@ void setUp() { @Test void testChangePassword() { - final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userHistoryDao = new JdbcUserHistoryDao(jdbcTemplate); + final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -47,8 +46,8 @@ void testChangePassword() { @Test void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 - final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(userDao, userHistoryDao); + final var userHistoryDao = new MockJdbcUserHistoryDao(jdbcTemplate); + final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao); final var newPassword = "newPassword"; final var createBy = "gugu"; 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 6ea342eca0..3c18d92ec7 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -1,5 +1,7 @@ package org.springframework.jdbc.core; +import org.springframework.jdbc.transaction.TransactionManager; + import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; @@ -12,10 +14,10 @@ public class JdbcTemplate { - private final DataSource dataSource; + private final TransactionManager transactionManager; public JdbcTemplate(final DataSource dataSource) { - this.dataSource = dataSource; + this.transactionManager = new TransactionManager(dataSource); } public int update(final String sql, final Object... conditions) { @@ -41,10 +43,10 @@ private T getRowByQuery(final PreparedStatement preparedStatement, final Row } private T getResult(final PreparedStatementExecutor executor, final String sql, final Object... conditions) { - PreparedStatement preparedStatement = null; - try { - Connection connection = dataSource.getConnection(); - preparedStatement = connection.prepareStatement(sql); + Connection connection = transactionManager.getConnection(); + try ( + PreparedStatement preparedStatement = connection.prepareStatement(sql); + ) { setConditions(preparedStatement, conditions); return executor.query(preparedStatement); } catch (SQLException e) { diff --git a/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionExecutor.java b/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionExecutor.java new file mode 100644 index 0000000000..99168d5978 --- /dev/null +++ b/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionExecutor.java @@ -0,0 +1,9 @@ +package org.springframework.jdbc.transaction; + +import java.sql.Connection; + +public interface TransactionExecutor { + + T execute(final Connection connection); + +} diff --git a/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java b/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java new file mode 100644 index 0000000000..bf6d52d8b6 --- /dev/null +++ b/jdbc/src/main/java/org/springframework/jdbc/transaction/TransactionManager.java @@ -0,0 +1,80 @@ +package org.springframework.jdbc.transaction; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class TransactionManager { + + private static final ThreadLocal connectionInThread = new ThreadLocal<>(); + + private final DataSource dataSource; + + public TransactionManager(DataSource dataSource) { + this.dataSource = dataSource; + } + + public T execute(final TransactionExecutor transactionExecutor) { + try { + Connection connection = begin(); + T response = transactionExecutor.execute(connection); + commit(); + return response; + } catch (RuntimeException e) { + rollback(); + throw e; + } + } + + public Connection begin() { + Connection connection = getConnection(); + try { + connection.setAutoCommit(false); + } catch (SQLException e) { + throw new RuntimeException(e); + } + connectionInThread.set(connection); + return connection; + } + + public void commit() { + Connection connection = getConnection(); + try { + connection.commit(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + init(connection); + } + + public void rollback() { + Connection connection = getConnection(); + try { + connection.rollback(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + init(connection); + } + + private void init(final Connection connection) { + try { + connection.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + connectionInThread.remove(); + } + + public Connection getConnection() { + if (connectionInThread.get() == null) { + try { + return dataSource.getConnection(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + return connectionInThread.get(); + } + +}