Skip to content

Commit

Permalink
refactor: TransactionManager 기능을 TransactionSynchronizationManager 와 …
Browse files Browse the repository at this point in the history
…DataSourceUtils 로 분리
  • Loading branch information
Songusika committed Oct 16, 2023
1 parent 099cbe5 commit dcbf0e1
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.exception.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.ConnectionHolder;
import org.springframework.jdbc.support.TransactionManager;

public class JdbcTemplate {

Expand Down Expand Up @@ -51,7 +51,7 @@ public <T> Optional<T> queryForObject(final String sql, final RowMapper<T> rowMa
}

private <T> T execute(final String sql, final PreparedStatementCallback<T> callback, final Object... parameters) {
try (final ConnectionHolder connectionHolder = TransactionManager.getConnectionHolder(dataSource);
try (final ConnectionHolder connectionHolder = DataSourceUtils.getConnectionHolder(dataSource);
final PreparedStatement preparedStatement = createPrepareStatement(connectionHolder, sql, parameters);
) {
return callback.callback(preparedStatement);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
package org.springframework.jdbc.datasource;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

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

private DataSourceUtils() {}

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
if (connection != null) {
return connection;
}

try {
connection = dataSource.getConnection();
TransactionSynchronizationManager.bindResource(dataSource, connection);
return connection;
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
}
private DataSourceUtils() {
}

public static void releaseConnection(Connection connection, DataSource dataSource) {
try {
connection.close();
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");
public static ConnectionHolder getConnectionHolder(DataSource dataSource) throws CannotGetJdbcConnectionException {
final Connection connection = TransactionSynchronizationManager.getResource(dataSource);
if (TransactionSynchronizationManager.isTransactionBegan()) {
return ConnectionHolder.activeTransaction(connection);
}

return ConnectionHolder.disableTransaction(connection);
}
}
Original file line number Diff line number Diff line change
@@ -1,97 +1,19 @@
package org.springframework.jdbc.support;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.exception.CannotBeginTransactionException;
import org.springframework.jdbc.support.exception.CannotCommitException;
import org.springframework.jdbc.support.exception.CannotGetConnectionException;
import org.springframework.jdbc.support.exception.CannotRollbackException;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class TransactionManager {

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

public static void beginTransaction() {
isTransactionActive.set(true);
}

public static ConnectionHolder getConnectionHolder(final DataSource dataSource) {
if (isTransactionActive.get()) {
final Connection connection = guaranteeConnection(dataSource);
beginTransaction(connection);
return ConnectionHolder.activeTransaction(connection);
}

return ConnectionHolder.disableTransaction(getConnection(dataSource));
}

private static Connection guaranteeConnection(final DataSource dataSource) {
if (!connections.get().containsKey(dataSource)) {
Connection connection = getConnection(dataSource);
connections.get().put(dataSource, connection);
return connection;
}

return connections.get().get(dataSource);
}

private static Connection getConnection(final DataSource dataSource) {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new CannotGetConnectionException(e.getMessage());
}
}

private static void action(
final ConnectionCallBack callBack,
final Function<SQLException, DataAccessException> exceptionSupplier
) {
try {
callBack.action();
} catch (SQLException e) {
throw exceptionSupplier.apply(e);
}
}

private static void beginTransaction(final Connection connection) {
action(() -> connection.setAutoCommit(false),
e -> new CannotBeginTransactionException(e.getMessage())
);
TransactionSynchronizationManager.beginTransaction();
}

public static void commit(final DataSource dataSource) {
action(() -> {
if (connections.get().containsKey(dataSource)) {
connections.get().get(dataSource).commit();
clear(dataSource);
}
}, e -> new CannotCommitException(e.getMessage()));
TransactionSynchronizationManager.commit(dataSource);
}

public static void rollback() {
action(() -> {
for (final Connection value : connections.get().values()) {
value.rollback();
}
clear();
}, e -> new CannotRollbackException(e.getMessage()));
}

private static void clear(final DataSource dataSource) {
action(() -> {
final Connection connection = connections.get().remove(dataSource);
connection.close();
}, e -> new DataAccessException(e.getMessage()));
}

private static void clear() {
connections.get().clear();
TransactionSynchronizationManager.rollback();
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,114 @@
package org.springframework.transaction.support;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.ConnectionCallBack;
import org.springframework.jdbc.support.exception.CannotBeginTransactionException;
import org.springframework.jdbc.support.exception.CannotCommitException;
import org.springframework.jdbc.support.exception.CannotGetConnectionException;
import org.springframework.jdbc.support.exception.CannotRollbackException;

public abstract class TransactionSynchronizationManager {

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

private TransactionSynchronizationManager() {
}

public static void beginTransaction() {
isTransactionActive.set(true);
}

public static boolean isTransactionBegan() {
return isTransactionActive.get();
}

public static Connection getResource(final DataSource dataSource) {
if (isTransactionActive.get()) {
final Connection connection = guaranteeConnection(dataSource);
beginTransaction(connection);
return connection;
}

return getConnection(dataSource);
}

private static Connection guaranteeConnection(final DataSource dataSource) {
if (!connections.get().containsKey(dataSource)) {
Connection connection = getConnection(dataSource);
connections.get().put(dataSource, connection);
return connection;
}

private TransactionSynchronizationManager() {}
return connections.get().get(dataSource);
}

private static Connection getConnection(final DataSource dataSource) {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new CannotGetConnectionException(e.getMessage());
}
}

private static void beginTransaction(final Connection connection) {
action(() -> connection.setAutoCommit(false),
e -> new CannotBeginTransactionException(e.getMessage())
);
}

private static void action(
final ConnectionCallBack callBack,
final Function<SQLException, DataAccessException> exceptionSupplier
) {
try {
callBack.action();
} catch (SQLException e) {
throw exceptionSupplier.apply(e);
}
}

public static void commit(final DataSource dataSource) {
action(() -> {
if (connections.get().containsKey(dataSource)) {
connections.get().get(dataSource).commit();
clear(dataSource);
}
}, e -> new CannotCommitException(e.getMessage()));
}

public static void rollback() {
action(() -> {
for (final Connection value : connections.get().values()) {
value.rollback();
}
clear();
}, e -> new CannotRollbackException(e.getMessage()));
}

public static Connection getResource(DataSource key) {
return null;
private static void clear(final DataSource dataSource) {
action(() -> {
final Connection connection = connections.get().remove(dataSource);
clearConnection(connection);
}, e -> new DataAccessException(e.getMessage()));
isTransactionActive.set(false);
}

public static void bindResource(DataSource key, Connection value) {
private static void clearConnection(final Connection connection) {
action(connection::close, e -> new DataAccessException(e.getMessage()));
}

public static Connection unbindResource(DataSource key) {
return null;
private static void clear() {
connections.get()
.values()
.forEach(TransactionSynchronizationManager::clearConnection);
connections.get().clear();
isTransactionActive.set(false);
}
}
4 changes: 2 additions & 2 deletions jdbc/src/test/java/nextstep/jdbc/JdbcTemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import java.util.List;
import java.util.Optional;
import nextstep.testUtil.TestDataSourceConfig;
import nextstep.testUtil.TestDatabaseUtils;
import testUtil.TestDataSourceConfig;
import testUtil.TestDatabaseUtils;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayNameGeneration;
Expand Down
48 changes: 0 additions & 48 deletions jdbc/src/test/java/nextstep/support/TransactionManagerTest.java

This file was deleted.

Loading

0 comments on commit dcbf0e1

Please sign in to comment.