Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 연어(황재현) 미션 제출합니다. (#577)
Browse files Browse the repository at this point in the history
* feat: TransactionSynchronizationManager 구현

* refactor: 커넥션 관리 DataSourceUtils 사용하도록 변경

* refactor: DAO가 현재 스레드의 커넥션을 사용하도록 변경

* refactor: Application 로직만 있는 객체와 트랜잭션 관련 객체로 분리

* refactor: rollback의 역할 TransactionSynchronizationManager에게 위임

* refactor: close(), unbindResource() 같이 실행 되도록 변경

* refactor: rollback의 책임 TxUserService에게 위임
  • Loading branch information
nuyh99 authored Oct 11, 2023
1 parent 367b696 commit 16c23df
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 102 deletions.
11 changes: 9 additions & 2 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.techcourse.dao;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
Expand Down Expand Up @@ -32,11 +34,16 @@ public void insert(final User user) {
jdbcTemplate.executeUpdate(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final Connection connection, final User user) {
public void update(final User user) {
final String sql = "UPDATE users SET account = ?, password = ?, email = ? WHERE id = ?";

final long userId = user.getId();
jdbcTemplate.executeUpdate(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), userId);
jdbcTemplate.executeUpdate(getConnection(), sql, user.getAccount(), user.getPassword(), user.getEmail(), userId);
}

private Connection getConnection() {
final DataSource dataSource = DataSourceConfig.getInstance();
return DataSourceUtils.getConnection(dataSource);
}

public List<User> findAll() {
Expand Down
11 changes: 9 additions & 2 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.techcourse.dao;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.UserHistory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
Expand All @@ -18,11 +20,11 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void log(final Connection connection, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
final String sql = "insert into user_history (user_id, account, password, email, created_at, created_by) " +
"values (?, ?, ?, ?, ?, ?)";

jdbcTemplate.executeUpdate(connection,
jdbcTemplate.executeUpdate(getConnection(),
sql,
userHistory.getUserId(),
userHistory.getAccount(),
Expand All @@ -32,4 +34,9 @@ public void log(final Connection connection, final UserHistory userHistory) {
userHistory.getCreateBy()
);
}

private Connection getConnection() {
final DataSource dataSource = DataSourceConfig.getInstance();
return DataSourceUtils.getConnection(dataSource);
}
}
36 changes: 36 additions & 0 deletions app/src/main/java/com/techcourse/service/AppUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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));
}
}
54 changes: 54 additions & 0 deletions app/src/main/java/com/techcourse/service/TxUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.techcourse.service;

import com.techcourse.config.DataSourceConfig;
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 TxUserService implements UserService {

private final UserService userService;

public TxUserService(final UserService userService) {
this.userService = userService;
}

@Override
public User findById(final long id) {
return userService.findById(id);
}

@Override
public void insert(final User user) {
userService.insert(user);
}

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
final DataSource dataSource = DataSourceConfig.getInstance();
final Connection connection = DataSourceUtils.getConnection(dataSource);

try {
connection.setAutoCommit(false);
userService.changePassword(id, newPassword, createBy);
connection.commit();
} catch (final Exception e) {
rollbackAndThrow(connection, e);
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
}
}

private void rollbackAndThrow(final Connection connection, final Exception businessException) {
try {
connection.rollback();
throw new DataAccessException(businessException);
} catch (final SQLException rollbackException) {
throw new DataAccessException(rollbackException);
}
}
}
47 changes: 4 additions & 43 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,13 @@
package com.techcourse.service;

import com.techcourse.config.DataSourceConfig;
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.ConnectionManager;

import java.sql.Connection;
public interface UserService {

public class UserService {
User findById(final long id);

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
void insert(final User user);

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
}
void changePassword(final long id, final String newPassword, final String createBy);

public User findById(final long id) {
return userDao.findById(id);
}

public void insert(final User user) {
userDao.insert(user);
}

public void changePassword(final long id, final String newPassword, final String createBy) {
final ConnectionManager connectionManager = new ConnectionManager(DataSourceConfig.getInstance());

final Connection connection = connectionManager.getConnection();
try {
connection.setAutoCommit(false);

final var user = findById(id);
user.changePassword(newPassword);

userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));

connection.commit();
} catch (final Exception e) {
connectionManager.rollback(connection);
throw new DataAccessException(e);
} finally {
connectionManager.close(connection);
}
}
}
4 changes: 1 addition & 3 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.SQLException;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -62,8 +61,7 @@ void update() throws SQLException {
final var user = userDao.findById(1L);
user.changePassword(newPassword);

final Connection connection = DataSourceConfig.getInstance().getConnection();
userDao.update(connection, user);
userDao.update(user);

final var actual = userDao.findById(1L);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
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) {
super(jdbcTemplate);
}

@Override
public void log(final Connection connection, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
throw new DataAccessException();
}
}
7 changes: 5 additions & 2 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new AppUserService(userDao, userHistoryDao);

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -46,7 +46,10 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
// 애플리케이션 서비스
final var appUserService = new AppUserService(userDao, userHistoryDao);
// 트랜잭션 서비스 추상화
final var userService = new TxUserService(appUserService);

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
// 4단계 미션에서 사용할 것
public abstract class DataSourceUtils {

private DataSourceUtils() {}
private DataSourceUtils() {
}

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
Expand All @@ -30,6 +31,7 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd
public static void releaseConnection(Connection connection, DataSource dataSource) {
try {
connection.close();
TransactionSynchronizationManager.unbindResource(dataSource);
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
package org.springframework.transaction.support;

import javax.annotation.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.isNull;

public abstract class TransactionSynchronizationManager {

private static final ThreadLocal<Map<DataSource, Connection>> resources = new ThreadLocal<>();

private TransactionSynchronizationManager() {}
private TransactionSynchronizationManager() {
}

@Nullable
public static Connection getResource(final DataSource key) {
final Map<DataSource, Connection> connections = getConnections();
return connections.get(key);
}

private static Map<DataSource, Connection> getConnections() {
Map<DataSource, Connection> connections = resources.get();

public static Connection getResource(DataSource key) {
return null;
if (isNull(connections)) {
connections = new HashMap<>();
resources.set(connections);
}

return connections;
}

public static void bindResource(DataSource key, Connection value) {
public static void bindResource(final DataSource key, final Connection value) {
final Map<DataSource, Connection> connections = getConnections();

if (connections.containsKey(key)) {
throw new IllegalStateException("이미 커넥션이 존재합니다.");
}
connections.put(key, value);
}

public static Connection unbindResource(DataSource key) {
return null;
public static Connection unbindResource(final DataSource key) {
final Map<DataSource, Connection> connections = getConnections();
final Connection connection = connections.get(key);

if (isNull(connection)) {
throw new IllegalStateException("커넥션이 존재하지 않습니다.");
}
connections.remove(key);

if (connections.isEmpty()) {
TransactionSynchronizationManager.resources.remove();
}

return connection;
}
}
Loading

0 comments on commit 16c23df

Please sign in to comment.