Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 무민(박무현) 미션 제출합니다. (#533)
Browse files Browse the repository at this point in the history
* refactor: generator 삭제

* refactor: UserHistory에도 connection 추가

* docs: READEME 업데이트

* feat: Transaction synchronization 적용

* feat: 트랜잭션 서비스 추상화하기

* refactor: try catch 삭제

* chore: 패키지 이동

* refactor: 생성자 수정

* refactor: executor return 값 생성

* refactor: 필요없는 파라미터 제거

* refactor: null 처리 추가

* refactor: TransactionSynchronizationManager 테스트
  • Loading branch information
parkmuhyeun authored Oct 11, 2023
1 parent c97e190 commit dffba20
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 106 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
- [x] 2단계 - 리팩터링
- 중복 제거
- [x] 3단계 - Transaction 적용하기
- [x] 4단계 - Transaction synchronization 적용하기
- [x] Transaction synchronization 적용하기
- [x] 트랜잭션 서비스 추상화하기
7 changes: 0 additions & 7 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.dao.RowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;
import java.util.List;

public class UserDao {
Expand Down Expand Up @@ -34,12 +33,6 @@ public void update(final User user) {
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

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

jdbcTemplate.update(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public List<User> findAll() {
final var sql = "select * from users";

Expand Down
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(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));
}
}
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.TransactionalExecutor;

public class TxUserService implements UserService {

private final UserService userService;
private final TransactionalExecutor transactionalExecutor;

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

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

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

// override 대상인 메서드는 userService의 메서드를 그대로 위임(delegate)한다.
@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionalExecutor.execute(
() -> {
userService.changePassword(id, newPassword, createBy);
return null;
}
);
}
}
50 changes: 4 additions & 46 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,10 @@
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.dao.DataAccessException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public interface UserService {

public class UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
private final DataSource dataSource;

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final DataSource dataSource) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
this.dataSource = dataSource;
}

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) {
try (final Connection connection = dataSource.getConnection()) {
try {
connection.setAutoCommit(false);

final var user = findById(id);
user.changePassword(newPassword);
userDao.update(connection, user);
userHistoryDao.log(new UserHistory(user, createBy));

connection.commit();
} catch (Exception e) {
connection.rollback();
throw new DataAccessException(e);
}
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
User findById(final long id);
void insert(final User user);
void changePassword(final long id, final String newPassword, final String createBy);
}
14 changes: 9 additions & 5 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionalExecutor;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -31,13 +32,13 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());
final var appUserService = new AppUserService(userDao, userHistoryDao);

final var newPassword = "qqqqq";
final var createBy = "gugu";
userService.changePassword(1L, newPassword, createBy);
appUserService.changePassword(1L, newPassword, createBy);

final var actual = userService.findById(1L);
final var actual = appUserService.findById(1L);

assertThat(actual.getPassword()).isEqualTo(newPassword);
}
Expand All @@ -46,11 +47,14 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());
// 애플리케이션 서비스
final var appUserService = new AppUserService(userDao, userHistoryDao);
// 트랜잭션 서비스 추상화
final var userService = new TxUserService(appUserService,
new TransactionalExecutor(DataSourceConfig.getInstance()));

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

// 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다.
assertThrows(DataAccessException.class,
() -> userService.changePassword(1L, newPassword, createBy));
Expand Down
10 changes: 0 additions & 10 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.springframework.exception.WrongResultSizeException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand All @@ -30,15 +29,6 @@ public int update(final String sql, final Object... parameters) {
);
}

public int update(final Connection connection, final String sql, final Object... parameters) {
return preparedStatementExecutor.execute(
connection,
PreparedStatement::executeUpdate,
sql,
parameters
);
}

public <T> List<T> query(final RowMapper<T> rowMapper, final String sql, final Object... parameters) {
return preparedStatementExecutor.execute(
preparedStatement -> findQueryResults(rowMapper, preparedStatement),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
Expand All @@ -24,22 +25,7 @@ public <T> T execute(
final String sql,
final Object... parameters
) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement preparedStatement = generatePreparedStatement(connection, sql, parameters)
) {
return preparedStatementProcessor.process(preparedStatement);
} catch (final SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
}

public <T> T execute(
final Connection connection,
final PreparedStatementProcessor<T> preparedStatementProcessor,
final String sql,
final Object... parameters
) {
final Connection connection = DataSourceUtils.getConnection(dataSource);
try (
final PreparedStatement preparedStatement = generatePreparedStatement(connection, sql, parameters)
) {
Expand All @@ -54,16 +40,12 @@ private PreparedStatement generatePreparedStatement(
final Connection connection,
final String sql,
final Object... parameters
) {
try {
log.debug("query : {}", sql);
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
) throws SQLException {
log.debug("query : {}", sql);
final PreparedStatement preparedStatement = connection.prepareStatement(sql);

setParameters(preparedStatement, parameters);
return preparedStatement;
} catch (SQLException e) {
throw new DataAccessException(e);
}
setParameters(preparedStatement, parameters);
return preparedStatement;
}

private void setParameters(final PreparedStatement pstmt, final Object[] parameters) throws SQLException {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.springframework.transaction.support;

@FunctionalInterface
public interface BusinessLogicProcessor<T> {

T process();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

public abstract class TransactionSynchronizationManager {
Expand All @@ -11,13 +12,31 @@ public abstract class TransactionSynchronizationManager {
private TransactionSynchronizationManager() {}

public static Connection getResource(DataSource key) {
return null;
if (resources.get() == null) {
return null;
}
if (key == null) {
throw new IllegalArgumentException("Key must not be null");
}
return resources.get().get(key);
}

public static void bindResource(DataSource key, Connection value) {
if (value == null) {
throw new IllegalArgumentException("Value must not be null");
}
if (resources.get() == null) {
resources.set(new HashMap<>());
}
final Map<DataSource, Connection> map = resources.get();
map.put(key, value);
}

public static Connection unbindResource(DataSource key) {
return null;
if (resources.get() == null) {
throw new IllegalStateException("resource is Empty");
}
final Map<DataSource, Connection> map = resources.get();
return map.remove(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.springframework.transaction.support;

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 TransactionalExecutor {

private final DataSource dataSource;

public TransactionalExecutor(final DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> T execute(BusinessLogicProcessor<T> businessLogicProcessor) {
final Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.setAutoCommit(false);
final T result = businessLogicProcessor.process();
connection.commit();
return result;
} catch (Exception e) {
rollbackAndThrowException(connection);
throw new DataAccessException(e);
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
TransactionSynchronizationManager.unbindResource(dataSource);
}
}

private void rollbackAndThrowException(final Connection connection) {
try {
connection.rollback();
} catch (SQLException ex) {
throw new DataAccessException(ex);
}
}
}
Loading

0 comments on commit dffba20

Please sign in to comment.