diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 090aaee899..22f22d0415 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -6,7 +6,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import java.sql.Connection; import java.sql.SQLException; import java.util.List; @@ -27,40 +26,40 @@ public UserDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public int insert(final Connection connection, final User user) throws SQLException { + public int insert(final User user) throws SQLException { final var sql = "insert into users (account, password, email) values (?, ?, ?)"; log.debug("query : {}", sql); - return updateQuery(connection, sql, user.getAccount(), user.getPassword(), user.getEmail()); + return updateQuery(sql, user.getAccount(), user.getPassword(), user.getEmail()); } - private int updateQuery(final Connection connection, final String sql, final Object... objects) throws SQLException { - final int updatedRows = jdbcTemplate.update(connection, sql, objects); + private int updateQuery(final String sql, final Object... objects) throws SQLException { + final int updatedRows = jdbcTemplate.update(sql, objects); if (updatedRows < 1) { throw new RuntimeException("저장된 데이터가 없습니다."); } return updatedRows; } - public int update(final Connection connection, final User user) throws SQLException { + public int update(final User user) throws SQLException { final var sql = "update users set (account, password, email) = (?, ?, ?)"; log.debug("query : {}", sql); - return updateQuery(connection, sql, user.getAccount(), user.getPassword(), user.getEmail()); + return updateQuery(sql, user.getAccount(), user.getPassword(), user.getEmail()); } - public List findAll(final Connection connection) throws SQLException { + public List findAll() throws SQLException { final var sql = "select id, account, password, email from users"; - final List users = jdbcTemplate.query(connection, sql, userRowMapper); + final List users = jdbcTemplate.query(sql, userRowMapper); log.debug("query : {}", sql); return users; } - public User findById(final Connection connection, final Long id) throws SQLException { + public User findById(final Long id) throws SQLException { final var sql = "select id, account, password, email from users where id = ?"; - final User user = jdbcTemplate.queryForObject(connection, sql, userRowMapper, id) + final User user = jdbcTemplate.queryForObject(sql, userRowMapper, id) .orElseThrow(() -> new RuntimeException("찾는 사용자가 존재하지 않습니다.")); log.debug("query : {}", sql); @@ -68,9 +67,9 @@ public User findById(final Connection connection, final Long id) throws SQLExcep return user; } - public User findByAccount(final Connection connection, final String account) throws SQLException { + public User findByAccount(final String account) throws SQLException { final var sql = "select id, account, password, email from users where account = ?"; - final User user = jdbcTemplate.queryForObject(connection, sql, userRowMapper, account) + final User user = jdbcTemplate.queryForObject(sql, userRowMapper, account) .orElseThrow(() -> new RuntimeException("찾는 사용자가 존재하지 않습니다.")); log.debug("query : {}", sql); diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java index 29834fe6fb..253528653b 100644 --- a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -5,7 +5,6 @@ import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; -import java.sql.Connection; import java.sql.SQLException; public class UserHistoryDao { @@ -18,10 +17,9 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public void log(final Connection connection, final UserHistory userHistory) throws SQLException { + public void log(final UserHistory userHistory) throws SQLException { final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)"; jdbcTemplate.update( - connection, sql, userHistory.getUserId(), userHistory.getAccount(), 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..e865f4735c --- /dev/null +++ b/app/src/main/java/com/techcourse/service/AppUserService.java @@ -0,0 +1,37 @@ +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 java.sql.SQLException; + +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) throws SQLException { + return userDao.findById(id); + } + + @Override + public void insert(final User user) throws SQLException { + userDao.insert(user); + } + + @Override + public void changePassword(long id, final String newPassword, final String createBy) throws SQLException { + final var 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/TransactionCallback.java b/app/src/main/java/com/techcourse/service/TransactionCallback.java new file mode 100644 index 0000000000..c160dac30b --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TransactionCallback.java @@ -0,0 +1,9 @@ +package com.techcourse.service; + +import java.sql.SQLException; + +@FunctionalInterface +public interface TransactionCallback { + + T execute() throws SQLException; +} diff --git a/app/src/main/java/com/techcourse/service/TransactionExecutor.java b/app/src/main/java/com/techcourse/service/TransactionExecutor.java deleted file mode 100644 index a5fad9b107..0000000000 --- a/app/src/main/java/com/techcourse/service/TransactionExecutor.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.techcourse.service; - -import java.sql.Connection; -import java.sql.SQLException; - -@FunctionalInterface -public interface TransactionExecutor { - - T execute(final Connection connection) throws SQLException; -} diff --git a/app/src/main/java/com/techcourse/service/TransactionManager.java b/app/src/main/java/com/techcourse/service/TransactionManager.java new file mode 100644 index 0000000000..9c28147150 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TransactionManager.java @@ -0,0 +1,65 @@ +package com.techcourse.service; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class TransactionManager { + + private final DataSource dataSource; + + public TransactionManager(final DataSource dataSource) { + this.dataSource = dataSource; + } + + public T execute(final TransactionCallback callback) { + try { + final Connection connection = TransactionSynchronizationManager.startNewTransaction(dataSource); + + final T result = callback.execute(); + + commitTransaction(connection); + TransactionSynchronizationManager.finishTransaction(dataSource); + + return result; + } catch (final SQLException ex) { + throw new RuntimeException("실행 중 예외가 발생했습니다."); + } + } + + public static void commitTransaction(final Connection connection) { + try { + connection.commit(); + + clear(connection); + } catch (final SQLException ex) { + rollback(connection); + + throw new DataAccessException("실행 중 예외가 발생했습니다."); + } + } + + public static void rollback(final Connection connection) { + try { + connection.rollback(); + + clear(connection); + } catch (final SQLException ex) { + throw new DataAccessException("트랜잭션 롤백 중 예외가 발생했습니다."); + } + } + + private static void clear(final Connection connection) { + try { + connection.setAutoCommit(true); + connection.close(); + DataSourceUtils.releaseConnection(connection); + } catch (final SQLException ex) { + throw new DataAccessException("커넥션 종료 중 예외가 발생했습니다."); + } + } +} 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..7e4a245a9b --- /dev/null +++ b/app/src/main/java/com/techcourse/service/TxUserService.java @@ -0,0 +1,35 @@ +package com.techcourse.service; + +import com.techcourse.domain.User; + +public class TxUserService implements UserService { + + private final AppUserService userService; + private final TransactionManager transactionManager; + + public TxUserService(final AppUserService userService, final TransactionManager transactionManager) { + this.userService = userService; + this.transactionManager = transactionManager; + } + + @Override + public User findById(final long id) { + return transactionManager.execute(() -> userService.findById(id)); + } + + @Override + public void insert(final User user) { + transactionManager.execute(() -> { + userService.insert(user); + return null; + }); + } + + @Override + public void changePassword(final long id, final String newPassword, final String createBy) { + transactionManager.execute(() -> { + userService.changePassword(id, newPassword, createBy); + return null; + }); + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java index ab1084af12..7f2430e9ed 100644 --- a/app/src/main/java/com/techcourse/service/UserService.java +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -1,71 +1,14 @@ 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.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.CannotGetJdbcConnectionException; -import javax.sql.DataSource; -import java.sql.Connection; import java.sql.SQLException; -public class UserService { +public interface UserService { - private static final Logger log = LoggerFactory.getLogger(UserService.class); + User findById(final long id) throws SQLException; - private final DataSource dataSource; - private final UserDao userDao; - private final UserHistoryDao userHistoryDao; + void insert(final User user) throws SQLException; - public UserService(final DataSource dataSource, final UserDao userDao, final UserHistoryDao userHistoryDao) { - this.dataSource = dataSource; - this.userDao = userDao; - this.userHistoryDao = userHistoryDao; - } - - public User findById(final long id) { - return startTransaction(connection -> userDao.findById(connection, id)); - } - - public void insert(final User user) { - startTransaction(connection -> userDao.insert(connection, user)); - } - - public void changePassword(long id, final String newPassword, final String createBy) { - startTransaction(connection -> { - final var user = findById(id); - user.changePassword(newPassword); - userHistoryDao.log(connection, new UserHistory(user, createBy)); - return userDao.update(connection, user); - }); - } - - public T startTransaction(final TransactionExecutor transactionExecutor) { - try (final Connection connection = dataSource.getConnection()) { - connection.setAutoCommit(false); - - return commitTransaction(transactionExecutor, connection); - } catch (SQLException e) { - throw new CannotGetJdbcConnectionException("jdbc 연결에 실패했습니다."); - } - } - - private T commitTransaction(final TransactionExecutor transactionExecutor, final Connection connection) throws SQLException { - try { - final T result = transactionExecutor.execute(connection); - - connection.commit(); - - return result; - } catch (Exception ex) { - connection.rollback(); - - log.error(ex.getMessage()); - throw new DataAccessException("실행 중 예외가 발생했습니다."); - } - } + void changePassword(final long id, final String newPassword, final String createBy) throws SQLException; } diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index 19606127bb..6941746015 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -25,19 +25,19 @@ void setup() throws SQLException { userDao = new UserDao(new JdbcTemplate(DataSourceConfig.getInstance())); final var user = new User("gugu", "password", "hkkang@woowahan.com"); - userDao.insert(dataSource.getConnection(), user); + userDao.insert(user); } @Test void findAll() throws SQLException { - final var users = userDao.findAll(dataSource.getConnection()); + final var users = userDao.findAll(); assertThat(users).isNotEmpty(); } @Test void findById() throws SQLException { - final var user = userDao.findById(dataSource.getConnection(), 1L); + final var user = userDao.findById(1L); assertThat(user.getAccount()).isEqualTo("gugu"); } @@ -45,7 +45,7 @@ void findById() throws SQLException { @Test void findByAccount() throws SQLException { final var account = "gugu"; - final var user = userDao.findByAccount(dataSource.getConnection(), account); + final var user = userDao.findByAccount(account); assertThat(user.getAccount()).isEqualTo(account); } @@ -55,9 +55,9 @@ void insert() throws SQLException { final var account = "insert-gugu"; final var user = new User(account, "password", "hkkang@woowahan.com"); final Connection connection = dataSource.getConnection(); - userDao.insert(connection, user); + userDao.insert(user); - final var actual = userDao.findById(connection, 2L); + final var actual = userDao.findById(2L); assertThat(actual.getAccount()).isEqualTo(account); } @@ -66,12 +66,12 @@ void insert() throws SQLException { void update() throws SQLException { final var newPassword = "password99"; final Connection connection = dataSource.getConnection(); - final var user = userDao.findById(connection, 1L); + final var user = userDao.findById(1L); user.changePassword(newPassword); - userDao.update(connection, user); + userDao.update(user); - final var actual = userDao.findById(connection, 1L); + final var actual = userDao.findById(1L); assertThat(actual.getPassword()).isEqualTo(newPassword); } diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/AppUserServiceTest.java similarity index 73% rename from app/src/test/java/com/techcourse/service/UserServiceTest.java rename to app/src/test/java/com/techcourse/service/AppUserServiceTest.java index 9a49c62846..98cce20653 100644 --- a/app/src/test/java/com/techcourse/service/UserServiceTest.java +++ b/app/src/test/java/com/techcourse/service/AppUserServiceTest.java @@ -10,12 +10,13 @@ import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; +import javax.sql.DataSource; import java.sql.SQLException; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -class UserServiceTest { +class AppUserServiceTest { private JdbcTemplate jdbcTemplate; private UserDao userDao; @@ -25,15 +26,16 @@ void setUp() throws SQLException { this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); this.userDao = new UserDao(jdbcTemplate); - DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); + final DataSource dataSource = DataSourceConfig.getInstance(); + DatabasePopulatorUtils.execute(dataSource); final var user = new User("gugu", "password", "hkkang@woowahan.com"); - userDao.insert(DataSourceConfig.getInstance().getConnection(), user); + userDao.insert(user); } @Test - void testChangePassword() { + void testChangePassword() throws SQLException { final var userHistoryDao = new UserHistoryDao(jdbcTemplate); - final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao); + final var userService = new AppUserService(userDao, userHistoryDao); final var newPassword = "qqqqq"; final var createBy = "gugu"; @@ -48,7 +50,11 @@ void testChangePassword() { void testTransactionRollback() { // 트랜잭션 롤백 테스트를 위해 mock으로 교체 final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); - final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao); + // 애플리케이션 서비스 + final var appUserService = new AppUserService(userDao, userHistoryDao); + // 트랜잭션 서비스 추상화 + final var transactionManager = new TransactionManager(DataSourceConfig.getInstance()); + final var userService = new TxUserService(appUserService, transactionManager); final var newPassword = "newPassword"; final var createBy = "gugu"; diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java index 9937b42bbc..2db229920f 100644 --- a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -14,7 +14,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { } @Override - public void log(final Connection connection, final UserHistory userHistory) { + public void log(final UserHistory userHistory) { throw new DataAccessException(); } } diff --git a/jdbc/src/main/java/org/springframework/jdbc/core/executeQueryCallback.java b/jdbc/src/main/java/org/springframework/jdbc/core/ExecuteQueryCallback.java similarity index 82% rename from jdbc/src/main/java/org/springframework/jdbc/core/executeQueryCallback.java rename to jdbc/src/main/java/org/springframework/jdbc/core/ExecuteQueryCallback.java index 2f97fc4e97..913c5f0e1c 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/executeQueryCallback.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/ExecuteQueryCallback.java @@ -4,7 +4,7 @@ import java.sql.SQLException; @FunctionalInterface -public interface executeQueryCallback { +public interface ExecuteQueryCallback { T execute(final PreparedStatement preparedStatement) throws SQLException; } 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 8c14ce2e83..714a1402d6 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -2,7 +2,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.dao.DataAccessException; +import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.sql.DataSource; import java.sql.Connection; @@ -24,21 +25,21 @@ public JdbcTemplate(final DataSource dataSource) { this.dataSource = dataSource; } - public Optional queryForObject(final Connection connection, final String sql, final RowMapper rowMapper, final Object... parameters) { - return execute(connection, sql, preparedStatement -> { + public Optional queryForObject(final String sql, final RowMapper rowMapper, final Object... parameters) throws SQLException { + return execute(sql, preparedStatement -> { final ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { return Optional.of(rowMapper.mapRow(resultSet)); } if (!resultSet.isLast()) { - throw new RuntimeException("단일 데이터가 아닙니다."); + throw new DataAccessException("단일 데이터가 아닙니다."); } return Optional.empty(); }, parameters); } - public List query(final Connection connection, final String sql, final RowMapper rowMapper) { - return execute(connection, sql, preparedStatement -> { + public List query(final String sql, final RowMapper rowMapper) throws SQLException { + return execute(sql, preparedStatement -> { final ResultSet resultSet = preparedStatement.executeQuery(); final List objects = new ArrayList<>(); while (resultSet.next()) { @@ -49,18 +50,24 @@ public List query(final Connection connection, final String sql, final Ro }); } - public int update(final Connection connection, final String sql, final Object... parameters) { - return execute(connection, sql, PreparedStatement::executeUpdate, parameters); + public int update(final String sql, final Object... parameters) throws SQLException { + return execute(sql, PreparedStatement::executeUpdate, parameters); } - public T execute(final Connection connection, final String sql, final executeQueryCallback callBack, final Object... objects) { - try (final PreparedStatement preparedStatement = connection.prepareStatement(sql)) { - setPreparedStatement(preparedStatement, objects); - return callBack.execute(preparedStatement); - } catch (SQLException ex) { - log.error(ex.getMessage()); - throw new CannotGetJdbcConnectionException("jdbc 연결에 실패했습니다."); - } + public T execute( + final String sql, + final ExecuteQueryCallback callBack, + final Object... objects + ) throws SQLException { + final PreparedStatement preparedStatement = createPreparedStatement(sql); + setPreparedStatement(preparedStatement, objects); + + return callBack.execute(preparedStatement); + } + + private PreparedStatement createPreparedStatement(final String sql) throws SQLException { + final Connection connection = TransactionSynchronizationManager.getResource(dataSource); + return connection.prepareStatement(sql); } private void setPreparedStatement(final PreparedStatement preparedStatement, final Object[] parameters) throws SQLException { diff --git a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java index 3c40bfec52..8d3d0c9a0e 100644 --- a/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -1,33 +1,25 @@ 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; -// 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; - } + private DataSourceUtils() { + } + public static Connection getConnection(final DataSource dataSource) throws CannotGetJdbcConnectionException, SQLException { try { - connection = dataSource.getConnection(); - TransactionSynchronizationManager.bindResource(dataSource, connection); - return connection; + return dataSource.getConnection(); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); } } - public static void releaseConnection(Connection connection, DataSource dataSource) { + public static void releaseConnection(final Connection connection) { try { connection.close(); } catch (SQLException ex) { diff --git a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 715557fc66..6be3352e5f 100644 --- a/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/jdbc/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -1,23 +1,51 @@ package org.springframework.transaction.support; +import org.springframework.jdbc.datasource.DataSourceUtils; + import javax.sql.DataSource; import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; import java.util.Map; public abstract class TransactionSynchronizationManager { - private static final ThreadLocal> resources = new ThreadLocal<>(); + private static final ThreadLocal> resources = ThreadLocal.withInitial(HashMap::new); + private static final ThreadLocal isActive = ThreadLocal.withInitial(() -> false); + + private TransactionSynchronizationManager() { + } + + public static Connection getResource(final DataSource dataSource) { + if (!resources.get().containsKey(dataSource)) { + final Connection connection = startNewTransaction(dataSource); + resources.get().put(dataSource, connection); + } + return resources.get().get(dataSource); + } - private TransactionSynchronizationManager() {} + public static Connection startNewTransaction(final DataSource dataSource) { + isActive.set(true); + return bindResource(dataSource); + } - public static Connection getResource(DataSource key) { - return null; + private static Connection bindResource(final DataSource dataSource) { + try { + final Connection connection = DataSourceUtils.getConnection(dataSource); + connection.setAutoCommit(false); + resources.get().put(dataSource, connection); + return connection; + } catch (final SQLException e) { + throw new RuntimeException(e); + } } - public static void bindResource(DataSource key, Connection value) { + public static void finishTransaction(final DataSource dataSource) { + isActive.set(false); + unbindResource(dataSource); } - public static Connection unbindResource(DataSource key) { - return null; + public static Connection unbindResource(final DataSource dataSource) { + return resources.get().remove(dataSource); } }