Skip to content

Commit

Permalink
[JDBC 라이브러리 구현 4단계] 메리(최승원) 미션 제출합니다 (#584)
Browse files Browse the repository at this point in the history
* 패키지 위치 변경 및 코드 정리

* 패키지 위치 변경 및 코드 정리

* 패키지 위치 변경 및 코드 정리

* feat: jdbc를 이용한 모든 유저 정보 수정 기능 추가

* feat: jdbc를 이용한 모든 유저 정보 조회 기능 추가

* feat: jdbc를 이용한 유저 account로 정보 조회하는 기능 추가

* feat: 단일 조회 객체 RowMapper 생성

* refactor: 아이디를 통한 유저 조회 중복 제거

* feat: 계정을 통한 유저 조회 기능 구현

* feat: 복수 조회를 위한 메서드 생성 및 findAll 중복 제거

* feat: update 중복 제거

* feat: insert 중복 제거

* refactor: 단일 조회 try-with-resources 사용하도록 수정

* refactor: 단일 조회 아닌 경우에 대한 예외 처리 추가

* fix: 단일 조회 누락된 로직 추가

* refactor: 복수 조회 메서드 try-with-resources 사용하도록 변경

* refactor: 수정 메서드 try-with-resources 사용하도록 변경

* refactor: 불필요한 제네릭 삭제

* refactor: 데이터가 존재하지 않는 경우 예외 발생이 아닌 Optional을 반환하도록 변경

* refactor: 불필요한 필드 제거

* refactor: 조회하려는 사용자가 없는 경우 DAO에서 예외 발생하도록 변경

* feat: 로그 출력

* refactor: 단일 조회 시 파라미터를 가변인자로 받도록 변경

* refactor: 중복되는 로직 메서드로 추출

* refactor: 복수 조회 메서드 네이밍 변경

* refactor: 중복되는 자원 열고 닫는 로직 콜백 패턴 사용하여 제거

* refactor: 콜백에 사용되는 람다 함수 재정의

* refactor: jdbc 연결 실패 시 커스텀 unchecked 예외 던지도록 수정

* test: 실습

* refactor: userHistoryDao에서 jdbcTemplate 사용하도록 수정

* refactor: 서비스 레이어에서 connection 생성해 같은 트랜잭션에서 재사용하도록 수정

* test: 변경된 코드에 맞춰 테스트 코드 수정

* refactor: 중복되는 자원 열고 닫는 로직 제거

* refactor: 메서드 네이밍 수정

* feat: 트랜잭션 종료 로직 추가

* refactor: 커스텀 예외를 던지도록 변경

* refactor: preparedStatement try-catch 내부에서 생성하도록 변경

* fix: 첫 번째 파라미터만 추가되는 로직 수정

* test: UserHistoryDao 메서드 시그니처 변경에 따라 mock 메서드 시그니처 변경

* test: UserDao 메서드 시그니처 변경에 따라 테스트 메서드 시그니처 변경

* refactor: 함수형 인터페이스 대소문자 변경

* refactor: 커넥션을 가져오는 부분을 DataSourceUtils에서 대신하도록 수정

* test: 요구사항에 맞춰 테스트 수정

* test: 커넥션 생성하는 로직 TransactionSynchronizationManager로 이동

* refactor: 트랜잭션 관리 로직 TransactionSynchronizationManager로 이동

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

* refactor: 불필요한 함수형 인터페이스 제거

* fix: 트랜잭션 종료되지 않은 경우 commit하지 않도록 변경

* fix: 트랜잭셔 매니저의 트랜잭션 시작과 종료가 정상적으로 동작하도록 수정

* refactor: 트랜잭션 매니저 생성 및 TxUserService에서 트랜잭션 중복 로직 제거

* refactor: 트랜잭션 매니에서 커밋, 롤백 수행하도록 수정

* refactor: 예외 처리 로직을 TransactionManager에서 수행하도록 변경

---------

Co-authored-by: kang-hyungu <[email protected]>
  • Loading branch information
swonny and kang-hyungu authored Oct 15, 2023
1 parent a9fc19b commit 09b1aa6
Show file tree
Hide file tree
Showing 15 changed files with 249 additions and 140 deletions.
25 changes: 12 additions & 13 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

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

Expand All @@ -27,50 +26,50 @@ public UserDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public int insert(final Connection connection, final User user) throws SQLException {
public int insert(final User user) throws SQLException {
final var sql = "insert into users (account, password, email) values (?, ?, ?)";
log.debug("query : {}", sql);

return updateQuery(connection, sql, user.getAccount(), user.getPassword(), user.getEmail());
return updateQuery(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

private int updateQuery(final Connection connection, final String sql, final Object... objects) throws SQLException {
final int updatedRows = jdbcTemplate.update(connection, sql, objects);
private int updateQuery(final String sql, final Object... objects) throws SQLException {
final int updatedRows = jdbcTemplate.update(sql, objects);
if (updatedRows < 1) {
throw new RuntimeException("저장된 데이터가 없습니다.");
}
return updatedRows;
}

public int update(final Connection connection, final User user) throws SQLException {
public int update(final User user) throws SQLException {
final var sql = "update users set (account, password, email) = (?, ?, ?)";
log.debug("query : {}", sql);

return updateQuery(connection, sql, user.getAccount(), user.getPassword(), user.getEmail());
return updateQuery(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public List<User> findAll(final Connection connection) throws SQLException {
public List<User> findAll() throws SQLException {
final var sql = "select id, account, password, email from users";
final List<User> users = jdbcTemplate.query(connection, sql, userRowMapper);
final List<User> users = jdbcTemplate.query(sql, userRowMapper);

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

return users;
}

public User findById(final Connection connection, final Long id) throws SQLException {
public User findById(final Long id) throws SQLException {
final var sql = "select id, account, password, email from users where id = ?";
final User user = jdbcTemplate.queryForObject(connection, sql, userRowMapper, id)
final User user = jdbcTemplate.queryForObject(sql, userRowMapper, id)
.orElseThrow(() -> new RuntimeException("찾는 사용자가 존재하지 않습니다."));

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

return user;
}

public User findByAccount(final Connection connection, final String account) throws SQLException {
public User findByAccount(final String account) throws SQLException {
final var sql = "select id, account, password, email from users where account = ?";
final User user = jdbcTemplate.queryForObject(connection, sql, userRowMapper, account)
final User user = jdbcTemplate.queryForObject(sql, userRowMapper, account)
.orElseThrow(() -> new RuntimeException("찾는 사용자가 존재하지 않습니다."));

log.debug("query : {}", sql);
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;
import java.sql.SQLException;

public class UserHistoryDao {
Expand All @@ -18,10 +17,9 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void log(final Connection connection, final UserHistory userHistory) throws SQLException {
public void log(final UserHistory userHistory) throws SQLException {
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(),
Expand Down
37 changes: 37 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,37 @@
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 java.sql.SQLException;

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) throws SQLException {
return userDao.findById(id);
}

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

@Override
public void changePassword(long id, final String newPassword, final String createBy) throws SQLException {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.techcourse.service;

import java.sql.SQLException;

@FunctionalInterface
public interface TransactionCallback<T> {

T execute() throws SQLException;
}
10 changes: 0 additions & 10 deletions app/src/main/java/com/techcourse/service/TransactionExecutor.java

This file was deleted.

65 changes: 65 additions & 0 deletions app/src/main/java/com/techcourse/service/TransactionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.techcourse.service;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class TransactionManager {

private final DataSource dataSource;

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

public <T> T execute(final TransactionCallback<T> callback) {
try {
final Connection connection = TransactionSynchronizationManager.startNewTransaction(dataSource);

final T result = callback.execute();

commitTransaction(connection);
TransactionSynchronizationManager.finishTransaction(dataSource);

return result;
} catch (final SQLException ex) {
throw new RuntimeException("실행 중 예외가 발생했습니다.");
}
}

public static void commitTransaction(final Connection connection) {
try {
connection.commit();

clear(connection);
} catch (final SQLException ex) {
rollback(connection);

throw new DataAccessException("실행 중 예외가 발생했습니다.");
}
}

public static void rollback(final Connection connection) {
try {
connection.rollback();

clear(connection);
} catch (final SQLException ex) {
throw new DataAccessException("트랜잭션 롤백 중 예외가 발생했습니다.");
}
}

private static void clear(final Connection connection) {
try {
connection.setAutoCommit(true);
connection.close();
DataSourceUtils.releaseConnection(connection);
} catch (final SQLException ex) {
throw new DataAccessException("커넥션 종료 중 예외가 발생했습니다.");
}
}
}
35 changes: 35 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,35 @@
package com.techcourse.service;

import com.techcourse.domain.User;

public class TxUserService implements UserService {

private final AppUserService userService;
private final TransactionManager transactionManager;

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

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

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

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionManager.execute(() -> {
userService.changePassword(id, newPassword, createBy);
return null;
});
}
}
65 changes: 4 additions & 61 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,71 +1,14 @@
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class UserService {
public interface UserService {

private static final Logger log = LoggerFactory.getLogger(UserService.class);
User findById(final long id) throws SQLException;

private final DataSource dataSource;
private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
void insert(final User user) throws SQLException;

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

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

public void insert(final User user) {
startTransaction(connection -> userDao.insert(connection, user));
}

public void changePassword(long id, final String newPassword, final String createBy) {
startTransaction(connection -> {
final var user = findById(id);
user.changePassword(newPassword);
userHistoryDao.log(connection, new UserHistory(user, createBy));
return userDao.update(connection, user);
});
}

public <T> T startTransaction(final TransactionExecutor<T> transactionExecutor) {
try (final Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);

return commitTransaction(transactionExecutor, connection);
} catch (SQLException e) {
throw new CannotGetJdbcConnectionException("jdbc 연결에 실패했습니다.");
}
}

private <T> T commitTransaction(final TransactionExecutor<T> transactionExecutor, final Connection connection) throws SQLException {
try {
final T result = transactionExecutor.execute(connection);

connection.commit();

return result;
} catch (Exception ex) {
connection.rollback();

log.error(ex.getMessage());
throw new DataAccessException("실행 중 예외가 발생했습니다.");
}
}
void changePassword(final long id, final String newPassword, final String createBy) throws SQLException;
}
18 changes: 9 additions & 9 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ void setup() throws SQLException {

userDao = new UserDao(new JdbcTemplate(DataSourceConfig.getInstance()));
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(dataSource.getConnection(), user);
userDao.insert(user);
}

@Test
void findAll() throws SQLException {
final var users = userDao.findAll(dataSource.getConnection());
final var users = userDao.findAll();

assertThat(users).isNotEmpty();
}

@Test
void findById() throws SQLException {
final var user = userDao.findById(dataSource.getConnection(), 1L);
final var user = userDao.findById(1L);

assertThat(user.getAccount()).isEqualTo("gugu");
}

@Test
void findByAccount() throws SQLException {
final var account = "gugu";
final var user = userDao.findByAccount(dataSource.getConnection(), account);
final var user = userDao.findByAccount(account);

assertThat(user.getAccount()).isEqualTo(account);
}
Expand All @@ -55,9 +55,9 @@ void insert() throws SQLException {
final var account = "insert-gugu";
final var user = new User(account, "password", "[email protected]");
final Connection connection = dataSource.getConnection();
userDao.insert(connection, user);
userDao.insert(user);

final var actual = userDao.findById(connection, 2L);
final var actual = userDao.findById(2L);

assertThat(actual.getAccount()).isEqualTo(account);
}
Expand All @@ -66,12 +66,12 @@ void insert() throws SQLException {
void update() throws SQLException {
final var newPassword = "password99";
final Connection connection = dataSource.getConnection();
final var user = userDao.findById(connection, 1L);
final var user = userDao.findById(1L);
user.changePassword(newPassword);

userDao.update(connection, user);
userDao.update(user);

final var actual = userDao.findById(connection, 1L);
final var actual = userDao.findById(1L);

assertThat(actual.getPassword()).isEqualTo(newPassword);
}
Expand Down
Loading

0 comments on commit 09b1aa6

Please sign in to comment.