Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 썬샷(오진택) 미션 제출합니다. (#601)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ohjintaek authored Oct 18, 2023
1 parent a656c3a commit 5084210
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 138 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@
## 2단계
- try-with-resources 활용하기
- 예외 처리 더 신경쓰기
- 람다 활용해서 코드량 줄이기
- 람다 활용해서 코드량 줄이기

## 4단계
- Dao의 메서드에서 connection 객체를 파라미터로 전달받지 않기
- Connection을 관리하는 기능을 가지는 객체 구현
- `UserService`에서 비즈니스 로직과 데이터 액세스 로직 분리하기
12 changes: 3 additions & 9 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.techcourse.dao;

import com.techcourse.domain.User;

import java.sql.Connection;
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;

import javax.sql.DataSource;
import java.util.List;

public class UserDao {

private static final Logger log = LoggerFactory.getLogger(UserDao.class);
Expand All @@ -34,11 +33,6 @@ public void update(final User user) {
jdbcTemplate.execute(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

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

public List<User> findAll() {
final var sql = "select id, account, password, email from users";
return jdbcTemplate.query(sql, rowMapper);
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.springframework.jdbc.core.JdbcTemplate;

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

public class UserHistoryDao {

Expand All @@ -22,9 +21,9 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void log(final Connection conn, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";
jdbcTemplate.execute(conn, sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(),
jdbcTemplate.execute(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(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));
}
}
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;
import org.springframework.transaction.support.JdbcTransactionManager;
import org.springframework.transaction.support.TransactionManager;

import javax.sql.DataSource;

public class TxUserService implements UserService {

private final TransactionManager transactionManager = new JdbcTransactionManager();
private final DataSource dataSource;
private final UserService userService;

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

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

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

@Override
public void changePassword(long id, String newPassword, String createBy) {
transactionManager.execute(dataSource, () -> userService.changePassword(id, newPassword, createBy)
);
}
}
36 changes: 4 additions & 32 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,38 +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 org.springframework.transaction.support.TransactionExecutor;
import org.springframework.transaction.support.TransactionManager;

public class UserService {
public interface UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
private final TransactionManager transactionManager = new TransactionExecutor();

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 void insert(final User user) {
userDao.insert(user);
}

public void changePassword(final long id, final String newPassword, final String createBy) {
transactionManager.execute(DataSourceConfig.getInstance(), connection -> {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));
});
}
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 @@ -5,16 +5,14 @@
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;

public class MockUserHistoryDao extends UserHistoryDao {

public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
}

@Override
public void log(final Connection connection, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
throw new DataAccessException();
}
}
17 changes: 5 additions & 12 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@
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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.support.TransactionManager;

import java.sql.Connection;
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;
Expand All @@ -36,7 +32,7 @@ void setUp() {
@Test
void testChangePassword() {
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 @@ -51,7 +47,8 @@ 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(DataSourceConfig.getInstance(), appUserService);

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand All @@ -63,8 +60,4 @@ void testTransactionRollback() {

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

private Connection getConnection() {
return TransactionManager.getConnection(DataSourceConfig.getInstance());
}
}
49 changes: 14 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 @@ -2,6 +2,7 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.exception.IncorrectQueryArgumentException;
import org.springframework.jdbc.exception.IncorrectResultSizeDataAccessException;

Expand All @@ -27,10 +28,6 @@ public int execute(final String sql, final Object... args) {
return manageData(PreparedStatement::executeUpdate, sql, args);
}

public int execute(final Connection conn, final String sql, final Object... args) {
return manageData(conn, PreparedStatement::executeUpdate, sql, args);
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... args) {
return manageData(pstmt -> {
try (ResultSet rs = pstmt.executeQuery()) {
Expand All @@ -43,18 +40,6 @@ public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final O
}, sql, args);
}

public <T> List<T> query(final Connection conn, final String sql, final RowMapper<T> rowMapper, final Object... args) {
return manageData(conn, pstmt -> {
try (ResultSet rs = pstmt.executeQuery()) {
final List<T> result = new ArrayList<>();
while (rs.next()) {
result.add(rowMapper.mapRow(rs));
}
return result;
}
}, sql, args);
}

public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
return manageData(pstmt -> {
try (ResultSet rs = pstmt.executeQuery()) {
Expand All @@ -63,32 +48,16 @@ public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, fina
}, sql, args);
}

public <T> T queryForObject(final Connection conn, final String sql, final RowMapper<T> rowMapper, final Object... args) {
return manageData(conn, pstmt -> {
try (ResultSet rs = pstmt.executeQuery()) {
return getOneResult(rs, rowMapper);
}
}, sql, args);
}

private <T> T manageData(final PreparedStatementImpl<T> qm, final String sql, final Object... args) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = setPreparedStatement(conn, sql, args)) {
log.debug("query : {}", sql);
return qm.callback(pstmt);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}

private <T> T manageData(final Connection conn, final PreparedStatementImpl<T> qm, final String sql, final Object... args) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (PreparedStatement pstmt = setPreparedStatement(conn, sql, args)) {
log.debug("query : {}", sql);
return qm.callback(pstmt);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
closeConnectionIfAutoCommitted(conn);
}
}

Expand Down Expand Up @@ -126,4 +95,14 @@ private <T> T getOneResult(final ResultSet rs, final RowMapper<T> rowMapper) thr
}
throw new IncorrectResultSizeDataAccessException("No result to return");
}

private void closeConnectionIfAutoCommitted(Connection conn) {
try {
if (conn.getAutoCommit()) {
DataSourceUtils.releaseConnection(conn, dataSource);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd
public static void releaseConnection(Connection connection, DataSource dataSource) {
try {
connection.close();
TransactionSynchronizationManager.unbindResource(dataSource);
} 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,55 @@
package org.springframework.transaction.support;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

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

public class JdbcTransactionManager implements TransactionManager {

@Override
public void execute(DataSource dataSource, TransactionExecutor method) {
final var connection = TransactionManager.getConnection(dataSource);
try {
connection.setAutoCommit(false);
method.execute();
connection.commit();
} catch (RuntimeException | SQLException e) {
handleTransactionException(connection, e);
} finally {
cleanUpTransaction(dataSource, connection);
}
}

@Override
public <T> T executeAndReturn(DataSource dataSource, TransactionSupplier<T> method) {
final var connection = TransactionManager.getConnection(dataSource);
try {
connection.setAutoCommit(false);
T result = method.get();
connection.commit();
return result;
} catch (RuntimeException | SQLException e) {
handleTransactionException(connection, e);
return null;
} finally {
cleanUpTransaction(dataSource, connection);
}
}

private static void handleTransactionException(Connection connection, Exception e) {
try {
connection.rollback();
throw new DataAccessException(e);
} catch (SQLException rollbackException) {
throw new RuntimeException(rollbackException);
}
}

private void cleanUpTransaction(DataSource dataSource, Connection connection) {
DataSourceUtils.releaseConnection(connection, dataSource);
TransactionSynchronizationManager.unbindResource(dataSource);
}
}
Loading

0 comments on commit 5084210

Please sign in to comment.