Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 가비 미션 제출합니다. (#523)
Browse files Browse the repository at this point in the history
* refactor: 메서드명 수정

* fix: DataSourceUtil과 TransactionSynchronizationManager를 이용하여 Connection을 사용하도록 변경

* refactor: findById의 반환 타입에서 Optional 제거

* feat: DataSourceUtils 내 TransactionSynchronizationManger 자원 해제 추가

* feat: TransactionManager 구현

* feat: UserService 추상화

* feat: AppUserService, TxUserService 구현

* test: testTransactionRollback() 메서드를 LMS에 있는 내용으로 변경

* fix : jdbctemplate에서 connection 해제하는 부분 제거

* refactor: Connection 선언부를 try-catch 밖으로 분리

* refactor: TranactionManager의 메서드 반환값을 void로 변경

* chore: final 키워드 통일

* fix: TransactionSynchronizationManager.unbindResource(dataSource) 중복 호출 제거

* fix: 불필요한 static 제거

* feat: DatabasePopulatorUtils도 DataSoureUtils를 이용해 connection을 가지고 오도록 변경

* refactor: service에서 TransactionManager를 주입받도록 변경

* refactor: 템플릿 콜백 패턴 적용

* refactor: 함수형 인터페이스 실행 메서드 분리

* refactor: DatasourcePopulatorUtils connection 해제 추가
  • Loading branch information
iamjooon2 authored Oct 11, 2023
1 parent 7019612 commit 4897a4f
Show file tree
Hide file tree
Showing 14 changed files with 180 additions and 118 deletions.
9 changes: 0 additions & 9 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.techcourse.dao;

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

Expand Down Expand Up @@ -42,14 +41,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 String sql = "update users set account = ?, password = ? , email = ? where id = ?";

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

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

public Optional<User> findById(final Long id) {
final String sql = "select id, account, password, email from users where id = ?";

Expand Down
17 changes: 0 additions & 17 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.techcourse.dao;

import java.sql.Connection;

import com.techcourse.domain.UserHistory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -32,19 +30,4 @@ public void log(final UserHistory userHistory) {
log.debug("query : {}", sql);
}

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()
);

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

}
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).orElseThrow();
}

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

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
final User user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
}

}
31 changes: 31 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,31 @@
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(final UserService userService, final TransactionManager transactionManager) {
this.userService = userService;
this.transactionManager = transactionManager;
}

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

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

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionManager.run(() -> userService.changePassword(id, newPassword, createBy));
}

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

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Optional;

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 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 Optional<User> findById(final long id) {
return userDao.findById(id);
}

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

public void changePassword(final long id, final String newPassword, final String createBy) {
Connection connection = null;
try {
connection = DataSourceConfig.getInstance().getConnection();
connection.setAutoCommit(false);
User findById(final long id);

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

connection.commit();
} catch (final SQLException e) {
try {
connection.rollback();
} catch (final SQLException rollbackException) {
throw new DataAccessException(rollbackException);
}
throw new DataAccessException(e);
} finally {
try {
connection.close();
} catch (final SQLException e) {
throw new DataAccessException(e);
}
}
}
void changePassword(final long id, final String newPassword, final String createBy);

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.slf4j.LoggerFactory;

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

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -15,8 +17,11 @@ public class DatabasePopulatorUtils {

private static final Logger log = LoggerFactory.getLogger(DatabasePopulatorUtils.class);

private DatabasePopulatorUtils() {
}

public static void execute(final DataSource dataSource) {
Connection connection = null;
Connection connection = DataSourceUtils.getConnection(dataSource);
Statement statement = null;
try {
final var url = DatabasePopulatorUtils.class.getClassLoader().getResource("schema.sql");
Expand All @@ -39,8 +44,7 @@ public static void execute(final DataSource dataSource) {
connection.close();
}
} catch (SQLException ignored) {}
DataSourceUtils.releaseConnection(connection, dataSource);
}
}

private DatabasePopulatorUtils() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
23 changes: 15 additions & 8 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@
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;

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,31 +34,35 @@ void setUp() {
@Test
void testChangePassword() {
final UserHistoryDao userHistoryDao = new UserHistoryDao(jdbcTemplate);
final UserService userService = new UserService(userDao, userHistoryDao);
final UserService userService = new AppUserService(userDao, userHistoryDao);

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

final User actual = userService.findById(1L).orElseThrow();
final User actual = userService.findById(1L);

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

@Test
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final UserHistoryDao userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final UserService userService = new UserService(userDao, userHistoryDao);
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
// 애플리케이션 서비스
final UserService appUserService = new AppUserService(userDao, userHistoryDao);
// 트랜잭션 서비스 추상화
final UserService txUserService = new TxUserService(appUserService, transactionManager);

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

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

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

}
26 changes: 3 additions & 23 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class JdbcTemplate {

Expand All @@ -23,26 +24,6 @@ public JdbcTemplate(final DataSource dataSource) {
this.dataSource = dataSource;
}

public void update(final Connection connection, final String sql, final Object... elements) {
execute(connection, sql, PreparedStatement::executeUpdate, elements);
}

private <T> T execute(
final Connection connection,
final String sql,
final PreparedStatementExecutor<T> executor,
final Object... elements
) {
try (final PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
setElements(elements, preparedStatement);

return executor.action(preparedStatement);
} catch (final SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
}

public void update(final String sql, final Object... elements) {
execute(sql, PreparedStatement::executeUpdate, elements);
}
Expand All @@ -52,9 +33,8 @@ private <T> T execute(
final PreparedStatementExecutor<T> executor,
final Object... elements
) {
try (final Connection connection = dataSource.getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(sql)
) {
final Connection connection = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
setElements(elements, preparedStatement);

return executor.action(preparedStatement);
Expand Down
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
Loading

0 comments on commit 4897a4f

Please sign in to comment.