Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 3,4단계] 애쉬(정설희) 미션 제출합니다. (#589)
Browse files Browse the repository at this point in the history
* feat: Transaction 적용하기

* refactor: TxSynchronizationManager 활용

* refactor: 트랜잭션 서비스 추상화

* refactor: transaction 일괄 적용

* refactor: 트랜잭션 종료 후 setAutoCommit(true) 설정
  • Loading branch information
xxeol2 authored Oct 11, 2023
1 parent bb001fc commit b694b1e
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 101 deletions.
3 changes: 0 additions & 3 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
import com.techcourse.domain.User;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class UserDao {

private static final Logger log = LoggerFactory.getLogger(UserDao.class);
private static final RowMapper<User> userRowMapper = rs ->
new User(
rs.getLong("id"),
Expand Down
53 changes: 5 additions & 48 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,63 +1,20 @@
package com.techcourse.dao;

import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

public class UserHistoryDao {

private static final Logger log = LoggerFactory.getLogger(UserHistoryDao.class);
private final JdbcTemplate jdbcTemplate;

private final DataSource dataSource;

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

public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.dataSource = null;
public UserHistoryDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void log(final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";

Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);

log.debug("query : {}", sql);

pstmt.setLong(1, userHistory.getUserId());
pstmt.setString(2, userHistory.getAccount());
pstmt.setString(3, userHistory.getPassword());
pstmt.setString(4, userHistory.getEmail());
pstmt.setObject(5, userHistory.getCreatedAt());
pstmt.setString(6, userHistory.getCreateBy());
pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {
}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {
}
}
jdbcTemplate.update(sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(),
userHistory.getEmail(), userHistory.getCreatedAt(), userHistory.getCreateBy());
}
}
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(UserDao userDao, UserHistoryDao userHistoryDao) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
}

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

@Override
public void insert(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));
}
}
30 changes: 30 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,30 @@
package com.techcourse.service;

import com.techcourse.domain.User;
import org.springframework.transaction.TransactionManager;

public class TxUserService implements UserService {

private final UserService userService;
private final TransactionManager transactionManager;

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

@Override
public User findById(long id) {
return transactionManager.doTransaction(() -> userService.findById(id));
}

@Override
public void insert(User user) {
transactionManager.doTransaction(() -> userService.insert(user));
}

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionManager.doTransaction(() -> userService.changePassword(id, newPassword, createBy));
}
}
28 changes: 4 additions & 24 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,12 @@
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 UserService {
public interface UserService {

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

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

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) {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
}
void changePassword(final long id, final String newPassword, final String createBy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
}

@Override
public void log(final UserHistory userHistory) {
public void log(UserHistory userHistory) {
throw new DataAccessException();
}
}
27 changes: 16 additions & 11 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
package com.techcourse.service;

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

import com.techcourse.config.DataSourceConfig;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.SQLException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionManager;

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

@Disabled
class UserServiceTest {

private JdbcTemplate jdbcTemplate;
private TransactionManager transactionManager;
private UserDao userDao;

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

DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
Expand All @@ -31,9 +33,9 @@ void setUp() {
}

@Test
void testChangePassword() {
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 @@ -48,13 +50,16 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
// 애플리케이션 서비스
final var appUserService = new AppUserService(userDao, userHistoryDao);
// 트랜잭션 서비스 추상화
final var userService = new TxUserService(appUserService, transactionManager);

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class QueryTemplate {

Expand All @@ -18,14 +19,22 @@ public QueryTemplate(DataSource dataSource) {
}

public <T> T service(final String sql, final QueryCallback<T> callback, final Object... args) {
try (
final Connection conn = dataSource.getConnection();
final PreparedStatement prepareStatement = setUpPreparedStatement(conn, sql, args);
) {
return callback.execute(prepareStatement);
PreparedStatement preparedStatement = null;
try {
final Connection conn = DataSourceUtils.getConnection(dataSource);
preparedStatement = setUpPreparedStatement(conn, sql, args);
return callback.execute(preparedStatement);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
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;
import javax.sql.DataSource;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.transaction.support.TransactionSynchronizationManager;

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

private DataSourceUtils() {}
private DataSourceUtils() {
}

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.springframework.transaction;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Supplier;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class TransactionManager {

private final DataSource dataSource;

public TransactionManager(DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> T doTransaction(Supplier<T> transactionExecutor) {
return executeTransaction(transactionExecutor);
}

public void doTransaction(Runnable transactionExecutor) {
executeTransaction(() -> {
transactionExecutor.run();
return null;
});
}

private <T> T executeTransaction(Supplier<T> transactionExecutor) {
T result = null;
try (final var conn = DataSourceUtils.getConnection(dataSource)) {
TransactionSynchronizationManager.bindResource(dataSource, conn);
conn.setAutoCommit(false);
result = transactionExecutor.get();
conn.commit();
conn.setAutoCommit(true);
} catch (final SQLException e) {
rollBack(e, TransactionSynchronizationManager.getResource(dataSource));
} finally {
TransactionSynchronizationManager.unbindResource(dataSource);
}
return result;
}

private void rollBack(final SQLException e, final Connection conn) {
try {
conn.rollback();
conn.setAutoCommit(true);
throw new DataAccessException(e);
} catch (final SQLException exception) {
throw new DataAccessException(exception);
}
}
}
Loading

0 comments on commit b694b1e

Please sign in to comment.