Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 로지(윤가영) 미션 제출합니다. (#561)
Browse files Browse the repository at this point in the history
* feat: UserService 인터페이스

* feat: DAO에서 connection 사용 하지 않도록 TransactionSynchronization 적용

* feat: AppUserService와 TxUserService 구현

* fix: TransactionSynchronizationManager 수정

* refactor: TransactionTemplate 적용

* ThreadLocal.withInitial 사용

* docs: 기능 명세 반영
  • Loading branch information
kyY00n authored Oct 12, 2023
1 parent 6d938b9 commit a39336b
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 138 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@

## 3단계

- [ ] User 비밀번호 변경 기능
- [ ] 비밀번호 변경 기능을 구현한다. (UserDao.changePassword())
- [ ] 누가, 언제, 어떤 비밀번호로 바꿨는지 이력을 남겨야한다.
- [ ] changePassword 원자성을 보장한다
- [x] User 비밀번호 변경 기능
- [x] 비밀번호 변경 기능을 구현한다. (UserDao.changePassword())
- [x] 누가, 언제, 어떤 비밀번호로 바꿨는지 이력을 남겨야한다.
- [x] changePassword 원자성을 보장한다
- 트랜잭션을 설정한다.
- userDao와 userHistoryDao를 한 트랜잭션으로 묶으려면 동일한 Connection 객체를 사용하도록 변경

## 4단계
- [x] UserService 인터페이스를 만든다.
- [x] DataSourceUtils 를 사용해서 Connection을 가져온다.
- [x] TransactionSynchronizationManager 에 보관된 Connection 객체를 가져온다.
- [x] TransactionSynchronizationManager를 구현한다.
- [x] 트랜잭션을 시작하기 위한 Connection 객체를 보관한다.
- [ ] AppUserService를 사용해도 Connection을 닫을 수 있도록 수정한다.
10 changes: 0 additions & 10 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ public void update(final User user) {
jdbcTemplate.update(sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId());
}

public void update(final Connection connection, final User user) {
final var sql = "update users set password = ?, email = ?, account = ? where id = ?";
jdbcTemplate.update(connection, sql, user.getPassword(), user.getEmail(), user.getAccount(), user.getId());
}

public List<User> findAll() {
final var sql = "select id, account, password, email from users";
return jdbcTemplate.query(sql, USER_ROW_MAPPER);
Expand All @@ -46,11 +41,6 @@ public User findById(final Long id) {
return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id);
}

public User findById(final Connection connection, final Long id) {
final var sql = "select id, account, password, email from users where id = ?";
return jdbcTemplate.queryForObject(connection, sql, USER_ROW_MAPPER, id);
}

public User findByAccount(final String account) {
final var sql = "select id, account, password, email from users where account = ?";
return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, account);
Expand Down
6 changes: 0 additions & 6 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void log(final Connection connection, final UserHistory userHistory) {
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(), userHistory.getPassword(),
userHistory.getEmail(), userHistory.getCreatedAt(), userHistory.getCreateBy());
}

public void log(final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(),
Expand Down
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));
}

}
38 changes: 38 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,38 @@
package com.techcourse.service;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;

import static org.springframework.transaction.support.TransactionTemplate.execute;

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 execute(DataSourceConfig.getInstance(), () -> userService.findById(id));
}

@Override
public void insert(final User user) {
execute(DataSourceConfig.getInstance(), () -> {
userService.insert(user);
return null;
});
}

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
final var dataSource = DataSourceConfig.getInstance();
execute(dataSource, () -> {
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.config.DataSourceConfig;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.dao.DataAccessException;

public class UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;

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

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

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

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

public void changePassword(final long id, final String newPassword, final String createBy) throws SQLException {
final var connection = DataSourceConfig.getInstance().getConnection();
try {
connection.setAutoCommit(false);
final var user = findById(connection, id);
user.changePassword(newPassword);
userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));

connection.commit();
} catch (final Exception e) {
connection.rollback();
throw new DataAccessException(e);
} finally {
connection.close();
}
}
public interface UserService {

User findById(final long id);
void insert(final User user);
void changePassword(final long id, final String newPassword, final String createBy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
import java.sql.SQLException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;

class UserServiceTest {
class AppUserServiceTest {

private JdbcTemplate jdbcTemplate;
private UserDao userDao;
Expand All @@ -33,7 +30,7 @@ void setUp() {
@Test
void testChangePassword() throws SQLException {
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 @@ -43,29 +40,4 @@ void testChangePassword() throws SQLException {

assertThat(actual.getPassword()).isEqualTo(newPassword);
}

@Test
void testTransactionRollback() {
// given
final var mockUserHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, mockUserHistoryDao);

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

// when
try {
userService.changePassword(1L, newPassword, createBy);
} catch (final Exception e) {
// do nothing
}

// then
final var actual = userService.findById(1L);
assertAll(
() -> assertThrows(DataAccessException.class,
() -> userService.changePassword(1L, newPassword, createBy)),
() -> assertThat(actual.getPassword()).isNotEqualTo(newPassword)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

Expand All @@ -13,7 +12,8 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
}

@Override
public void log(final Connection connection, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
throw new DataAccessException();
}

}
49 changes: 49 additions & 0 deletions app/src/test/java/com/techcourse/service/TxUserServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.techcourse.service;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.dao.UserDao;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

class TxUserServiceTest {

private JdbcTemplate jdbcTemplate;
private UserDao userDao;

@BeforeEach
void setUp() {
this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
this.userDao = new UserDao(jdbcTemplate);

DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
}
@Test
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
// 애플리케이션 서비스
final var appUserService = new AppUserService(userDao, userHistoryDao);
// 트랜잭션 서비스 추상화
final var userService = new TxUserService(appUserService);

final var newPassword = "newPassword";
final var createBy = "gugu";
// 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다.
assertThrows(DataAccessException.class,
() -> userService.changePassword(1L, newPassword, createBy));

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

assertThat(actual.getPassword()).isNotEqualTo(newPassword);
}

}
41 changes: 6 additions & 35 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class JdbcTemplate {

Expand All @@ -33,30 +34,8 @@ public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, fina
return results.get(0);
}

public <T> T queryForObject(final Connection connection, final String sql, final RowMapper<T> rowMapper, final Object... params) {
List<T> results = query(connection, sql, rowMapper, params);
if (results.size() > 1) {
throw new DataAccessException("too many result. expected 1 but was " + results.size());
}
if (results.isEmpty()) {
throw new DataAccessException("no result");
}
return results.get(0);
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... parameters) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement preparedStatement = conn.prepareStatement(sql);
final ResultSet resultSet = executeQuery(preparedStatement, parameters)) {
log.debug("query : {}", sql);
return mapResults(rowMapper, resultSet);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
}

public <T> List<T> query(final Connection conn, final String sql, final RowMapper<T> rowMapper, final Object... parameters) {
final var conn = getConnection();
try (final PreparedStatement preparedStatement = conn.prepareStatement(sql);
final ResultSet resultSet = executeQuery(preparedStatement, parameters)) {
log.debug("query : {}", sql);
Expand Down Expand Up @@ -89,8 +68,8 @@ private <T> List<T> mapResults(final RowMapper<T> rowMapper, final ResultSet res
}

public void update(final String sql, final Object... parameters) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
final Connection conn = getConnection();
try (final PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
log.debug("query : {}", sql);

setParameters(preparedStatement, parameters);
Expand All @@ -101,16 +80,8 @@ public void update(final String sql, final Object... parameters) {
}
}

public void update(final Connection conn, final String sql, final Object... parameters) {
try (final PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
log.debug("query : {}", sql);

setParameters(preparedStatement, parameters);
preparedStatement.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd

public static void releaseConnection(Connection connection, DataSource dataSource) {
try {
TransactionSynchronizationManager.unbindResource(dataSource);
connection.close();
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.springframework.transaction.support;

public interface TransactionExecution<T> {
T execute();
}
Loading

0 comments on commit a39336b

Please sign in to comment.