Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.interface21.jdbc.core.JdbcTemplate;
import com.interface21.jdbc.core.RowMapper;
import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;

Expand All @@ -20,11 +19,6 @@ public void insert(final User user) {
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

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 void update(final User user) {
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
Expand Down
13 changes: 0 additions & 13 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;

public class UserHistoryDao {

Expand All @@ -12,18 +11,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,
Expand Down
33 changes: 33 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,33 @@
package com.techcourse.service;

import com.interface21.dao.DataAccessException;
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;
}

public User findById(final long id) {
return userDao.findById(id).orElseThrow(() -> new DataAccessException("존재하지 않는 유저입니다."));
}

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

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

import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.datasource.DataSourceUtils;
import com.interface21.transaction.support.TransactionSynchronizationManager;
import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

public class TxUserService implements UserService {

private final UserService userService;

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

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

public void save(final User user) {
userService.save(user);
}

public void changePassword(final long id, final String newPassword, final String createBy) {
final DataSource dataSource = DataSourceConfig.getInstance();
final Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.setAutoCommit(false);
userService.changePassword(id, newPassword, createBy);
connection.commit();
} catch (Exception e) {
try {
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
Comment on lines +36 to +40

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: 이 부분에서 rollback 시 예외가 발생한다면 원본 예외(Exception e)가 덮어 씌워지는데 어떤가요? 만약 changePassword에서 NPE가 발생하고, 롤백 시 예외가 발생한다면 NPE는 알 수 없게 될 것 같아요.

throw new DataAccessException(e);
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
TransactionSynchronizationManager.unbindResource(dataSource);
}
}
Comment on lines +27 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P5: 이 코드의 종착지는 아시죠? AOP도 배워봤으니, 시간 되실때 어노테이션으로 구현해보는것도 추천드립니다. 이번주는 바쁘니 넘어가시죠 ㅎㅎ

}
47 changes: 4 additions & 43 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,51 +1,12 @@
package com.techcourse.service;

import com.interface21.dao.DataAccessException;
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 javax.sql.DataSource;

public class UserService {
public interface UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
private final DataSource dataSource;
User findById(final long id);

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

public User findById(final long id) {
return userDao.findById(id).orElseThrow(() -> new DataAccessException("존재하지 않는 유저입니다."));
}

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

public void changePassword(final long id, final String newPassword, final String createBy) {
final var user = findById(id);
user.changePassword(newPassword);

try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);
try {
userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));

connection.commit();
} catch (Exception e) {
connection.rollback();
throw new DataAccessException(e);
}
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
void changePassword(final long id, final String newPassword, final String createdBy);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.techcourse.service;

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

public class MockUserHistoryDao extends UserHistoryDao {

Expand All @@ -13,7 +12,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();
}
}
10 changes: 5 additions & 5 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());
final var userService = new AppUserService(userDao, userHistoryDao);

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -44,15 +44,15 @@ void testChangePassword() {

@Test
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);

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

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

Expand Down
67 changes: 54 additions & 13 deletions jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.interface21.jdbc.core;

import com.interface21.jdbc.datasource.DataSourceUtils;
import com.interface21.transaction.support.TransactionSynchronizationManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
Expand All @@ -23,30 +25,50 @@ public JdbcTemplate(final DataSource dataSource) {
}

public void update(final String sql, final Object... params) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement statement = connection.prepareStatement(sql);
) {
setParams(statement, params);
statement.executeUpdate();
try {
update(getConnection(), sql, params);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... params) {
try {
return query(getConnection(), sql, rowMapper, params);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public <T> Optional<T> queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... params) {
try {
return queryForObject(getConnection(), sql, rowMapper, params);
} catch (SQLException e) {
log.error("SQL 실행 실패: {} 파라미터: {}", sql, List.of(params), e);
throw new RuntimeException(e);
}
}

public void update(final Connection connection, final String sql, final Object... params) {
public void update(final Connection connection, final String sql, final Object... params) throws SQLException {
try (final PreparedStatement statement = connection.prepareStatement(sql)) {
setParams(statement, params);
statement.executeUpdate();
} catch (SQLException e) {
log.error("SQL 실행 실패: {} 파라미터: {}", sql, List.of(params), e);
throw new RuntimeException(e);
} finally {
if (!TransactionSynchronizationManager.hasConnection(dataSource)) {
connection.close();
}
}
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... params) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement statement = connection.prepareStatement(sql);
public <T> List<T> query(
final Connection connection,
final String sql,
final RowMapper<T> rowMapper,
final Object... params
) throws SQLException {
try (final PreparedStatement statement = connection.prepareStatement(sql);
final ResultSet resultSet = executeQuery(statement, params);
) {
final List<T> result = new ArrayList<>();
Expand All @@ -57,12 +79,20 @@ public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final O
} catch (SQLException e) {
log.error("SQL 실행 실패: {} 파라미터: {}", sql, List.of(params), e);
throw new RuntimeException(e);
} finally {
if (!TransactionSynchronizationManager.hasConnection(dataSource)) {
connection.close();
}
}
}

public <T> Optional<T> queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... params) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement statement = connection.prepareStatement(sql);
public <T> Optional<T> queryForObject(
final Connection connection,
final String sql,
final RowMapper<T> rowMapper,
final Object... params
) throws SQLException {
try (final PreparedStatement statement = connection.prepareStatement(sql);
final ResultSet resultSet = executeQuery(statement, params);
) {
if (resultSet.next()) {
Expand All @@ -72,6 +102,10 @@ public <T> Optional<T> queryForObject(final String sql, final RowMapper<T> rowMa
} catch (SQLException e) {
log.error("SQL 실행 실패: {} 파라미터: {}", sql, List.of(params), e);
throw new RuntimeException(e);
} finally {
if (!TransactionSynchronizationManager.hasConnection(dataSource)) {
connection.close();
}
}
}

Expand All @@ -85,4 +119,11 @@ private ResultSet executeQuery(final PreparedStatement statement, final Object..
setParams(statement, params);
return statement.executeQuery();
}

private Connection getConnection() throws SQLException {
if (TransactionSynchronizationManager.hasConnection(dataSource)) {
return DataSourceUtils.getConnection(dataSource);
}
return dataSource.getConnection();
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
package com.interface21.transaction.support;

import javax.sql.DataSource;
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 TransactionSynchronizationManager() {}

public static boolean hasConnection(DataSource dataSource) {
if (resources.get() == null) {
return false;
}
return resources.get().containsKey(dataSource);
}

public static Connection getResource(DataSource key) {
return null;
if (resources.get() == null) {
return null;
}
return resources.get().get(key);
}

public static void bindResource(DataSource key, Connection value) {
if (resources.get() == null) {
resources.set(new HashMap<>());
}
Comment on lines +29 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P5: LocalThread에는 최초 선언시 값을 초기화 할 수 있는 함수가 있는 것 같아요. 대신 사용해보면 어떨까요??

resources.get().put(key, value);
}

public static Connection unbindResource(DataSource key) {
return null;
if (resources.get() == null) {
throw new IllegalStateException("unbind할 리소스가 존재하지 않습니다.");
}
return resources.get().remove(key);
}
}