Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 다즐(최우창) 미션 제출합니다. (#566)
Browse files Browse the repository at this point in the history
* feat: 트랜잭션 서비스 추상화

* feat: TransactionSynchronizationManager 적용

* feat: TransactionSynchronizationManager 사용하도록 수정

* test: UserService 트랜잭션 테스트
  • Loading branch information
woo-chang committed Oct 9, 2023
1 parent bdab7c4 commit 18da6a6
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 92 deletions.
35 changes: 35 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,35 @@
package com.techcourse.service;

import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;

public class AppUserService implements UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;

public AppUserService(UserDao userDao, UserHistoryDao userHistoryDao) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
}

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

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

@Override
public void changePassword(long id, String newPassword, String createBy) {
var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
}
}
36 changes: 36 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,36 @@
package com.techcourse.service;

import com.techcourse.domain.User;
import org.springframework.transaction.support.TransactionTemplate;

public class TxUserService implements UserService {

private final UserService userService;
private final TransactionTemplate transactionTemplate;

public TxUserService(UserService userService, TransactionTemplate transactionTemplate) {
this.userService = userService;
this.transactionTemplate = transactionTemplate;
}

@Override
public User findById(long id) {
return transactionTemplate.execute(() -> userService.findById(id));
}

@Override
public void insert(User user) {
transactionTemplate.execute(() -> {
userService.insert(user);
return null;
});
}

@Override
public void changePassword(long id, String newPassword, String createBy) {
transactionTemplate.execute(() -> {
userService.changePassword(id, newPassword, createBy);
return null;
});
}
}
34 changes: 4 additions & 30 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
package com.techcourse.service;

import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import org.springframework.transaction.support.TransactionTemplate;

public class UserService {
public interface UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
private final TransactionTemplate transactionTemplate;
User findById(long id);

public UserService(UserDao userDao, UserHistoryDao userHistoryDao, TransactionTemplate transactionTemplate) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
this.transactionTemplate = transactionTemplate;
}
void insert(User user);

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

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

public void changePassword(long id, String newPassword, String createBy) {
transactionTemplate.execute(() -> {
var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
return null;
});
}
void changePassword(long id, String newPassword, String createBy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionTemplate;

class UserServiceTest {
class AppUserServiceTest {

private JdbcTemplate jdbcTemplate;
private TransactionTemplate transactionTemplate;
Expand All @@ -34,7 +34,7 @@ void setUp() {
@Test
void testChangePassword() {
var userHistoryDao = new UserHistoryDao(jdbcTemplate);
var userService = new UserService(userDao, userHistoryDao, transactionTemplate);
var userService = new AppUserService(userDao, userHistoryDao);

var newPassword = "qqqqq";
var createBy = "gugu";
Expand All @@ -49,7 +49,8 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
var userService = new UserService(userDao, userHistoryDao, transactionTemplate);
var appUserService = new AppUserService(userDao, userHistoryDao);
var userService = new TxUserService(appUserService, transactionTemplate);

var newPassword = "newPassword";
var createBy = "gugu";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.support.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class JdbcTemplate {

private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);

private final DataSourceTransactionManager transactionManager;
private final DataSource dataSource;

public JdbcTemplate(DataSource dataSource) {
this.transactionManager = new DataSourceTransactionManager(dataSource);
this.dataSource = dataSource;
}

private <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) {
var con = transactionManager.getConnection();
var con = DataSourceUtils.getConnection(dataSource);
try (var ps = psc.createPreparedStatement(con)) {
return action.doInPreparedStatement(ps);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
} finally {
transactionManager.release(con);
DataSourceUtils.releaseConnection(con, dataSource);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package org.springframework.jdbc.datasource;

import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.transaction.support.TransactionSynchronizationManager;

// 4단계 미션에서 사용할 것
public abstract class DataSourceUtils {

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

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
var connection = TransactionSynchronizationManager.getResource(dataSource);
if (connection != null) {
return connection;
}
Expand All @@ -28,8 +28,13 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd
}

public static void releaseConnection(Connection connection, DataSource dataSource) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
return;
}

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
Expand Up @@ -3,75 +3,48 @@
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class DataSourceTransactionManager {

private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

private final DataSource dataSource;

public DataSourceTransactionManager(DataSource dataSource) {
this.dataSource = dataSource;
}

public void startTransaction() {
public void doGetTransaction() {
try {
var connection = getConnection();
var connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);
connectionHolder.set(connection);
TransactionSynchronizationManager.setActualTransactionActive(true);
} catch (SQLException e) {
throw new RuntimeException("트랜잭션 시작 실패", e);
}
}

public Connection getConnection() {
var connection = connectionHolder.get();
if (connection != null) {
return connection;
}

try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException("커넥션 획득 실패", e);
}
}

public void commit() {
public void doCommit() {
try {
var connection = getConnection();
var connection = DataSourceUtils.getConnection(dataSource);
connection.commit();
close(connection, true);
release(connection);
} catch (SQLException e) {
throw new RuntimeException("커밋 실패", e);
}
}

public void rollback() {
public void doRollback() {
try {
var connection = getConnection();
var connection = DataSourceUtils.getConnection(dataSource);
connection.rollback();
close(connection, true);
release(connection);
} catch (SQLException e) {
throw new RuntimeException("롤백 실패", e);
}
}

public void release(Connection connection) {
if (connectionHolder.get() != connection) {
close(connection, false);
}
}

private void close(Connection connection, boolean clear) {
if (clear) {
connectionHolder.remove();
}

try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException("커넥션 종료 실패", e);
}
private void release(Connection connection) {
TransactionSynchronizationManager.setActualTransactionActive(false);
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
package org.springframework.transaction.support;

import javax.sql.DataSource;
import static java.lang.ThreadLocal.withInitial;

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

public abstract class TransactionSynchronizationManager {

private static final ThreadLocal<Map<DataSource, Connection>> resources = new ThreadLocal<>();
private static final ThreadLocal<Map<DataSource, Connection>> resources = withInitial(HashMap::new);
private static final ThreadLocal<Boolean> actualTransactionActive = withInitial(() -> false);

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

public static Connection getResource(DataSource key) {
return null;
return getResources().get(key);
}

public static void bindResource(DataSource key, Connection value) {
getResources().put(key, value);
}

public static Connection unbindResource(DataSource key) {
return null;
return getResources().remove(key);
}

private static Map<DataSource, Connection> getResources() {
return resources.get();
}

public static boolean isActualTransactionActive() {
return actualTransactionActive.get();
}

public static void setActualTransactionActive(boolean active) {
actualTransactionActive.set(active);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public TransactionTemplate(DataSource dataSource) {

public <T> T execute(TransactionCallBack<T> action) {
try {
transactionManager.startTransaction();
transactionManager.doGetTransaction();
T result = action.doInTransaction();
transactionManager.commit();
transactionManager.doCommit();
return result;
} catch (RuntimeException | Error e) {
transactionManager.rollback();
transactionManager.doRollback();
throw e;
}
}
Expand Down

0 comments on commit 18da6a6

Please sign in to comment.