Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 3,4단계] 케로(김지현) 미션 제출합니다. (#606)
Browse files Browse the repository at this point in the history
* study

* feat: UserHistoryDao 로직 추가

* feat: UserService에서 changePassword를 한 connection에서 처리할 수 있도록 기능을 구현함

* feat: TransactionSynchronizationManager이정상 동작하도록 기능추가

* feat: 서비스와 DAO에서 DataSourceUtils를 사용하여 Connection을 가져오도록 함.

* feat: DataSourceUtils에서 오토 커밋일 경우 커넥션을 반납할 수 있도록 함.

* refactor: JdbcTemplate의 반복을 줄이고,dao에서 커넥션을 보내지 않도록 수정함

* feat: userService 에서 커넥션 관리를 하지 않도록 TransactionTemplate을 만들음
  • Loading branch information
jyeost authored Oct 12, 2023
1 parent 7a4f1f7 commit ccb2e17
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 149 deletions.
50 changes: 4 additions & 46 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,19 @@

import com.techcourse.domain.UserHistory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class UserHistoryDao {

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

private final DataSource dataSource;

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

public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.dataSource = null;
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 (?, ?, ?, ?, ?, ?)";
final Object[] parameters = {userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(), userHistory.getEmail(), userHistory.getCreatedAt(), userHistory.getCreateBy()};

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, parameters);
}
}
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 findUser = findById(id);
findUser.changePassword(newPassword);
userDao.update(findUser);
userHistoryDao.log(new UserHistory(findUser, createBy));
}
}
37 changes: 37 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,37 @@
package com.techcourse.service;

import com.techcourse.domain.User;
import org.springframework.transaction.support.TransactionTemplate;

public class TxUserService implements UserService {

private final AppUserService userService;

private final TransactionTemplate transactionTemplate;

public TxUserService(final AppUserService userService, final TransactionTemplate transactionTemplate) {
this.userService = userService;
this.transactionTemplate = transactionTemplate;
}

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

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

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionTemplate.execute(() -> {
userService.changePassword(id, newPassword, createBy);
return null;
});
}
}
29 changes: 4 additions & 25 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,11 @@
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 {
User findById(final long id);

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
void insert(final User user);

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) {
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);
}
14 changes: 8 additions & 6 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
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.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionTemplate;

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

@Disabled
class UserServiceTest {

private JdbcTemplate jdbcTemplate;
Expand All @@ -33,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 @@ -48,7 +47,10 @@ 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, new TransactionTemplate(DataSourceConfig.getInstance()));

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
48 changes: 19 additions & 29 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
Expand All @@ -23,48 +24,37 @@ public JdbcTemplate(final DataSource dataSource) {
}

public int update(final String sql, final Object... parameters) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = PreparedStateUtil.makeStatement(conn, sql, parameters)
) {
log.debug("Execute Update - query : {}", sql);
return pstmt.executeUpdate();
return executeQuery(sql, PreparedStatement::executeUpdate, parameters);
}

private <T> T executeQuery(final String sql, final PreparedStateExecutor<T> executor, final Object... parameters) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (PreparedStatement pstmt = PreparedStateUtil.makeStatement(conn, sql, parameters)) {
log.debug("Execute - query : {}", sql);
return executor.execute(pstmt);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
} finally {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}

public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... parameters) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = PreparedStateUtil.makeStatement(conn, sql, parameters);
ResultSet rs = pstmt.executeQuery()
) {
log.debug("Execute Query -query : {}", sql);
return SingleResult.makeSingleResultFrom(makeResults(rowMapper, rs));
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
return executeQuery(sql, preparedStatement -> SingleResult.makeSingleResultFrom(makeResults(rowMapper, preparedStatement)), parameters);
}

private <T> List<T> makeResults(final RowMapper<T> rowMapper, final ResultSet rs) throws SQLException {
private <T> List<T> makeResults(final RowMapper<T> rowMapper, final PreparedStatement preparedStatement) throws SQLException {
List<T> list = new ArrayList<>();
while (rs.next()) {
list.add(rowMapper.mapping(rs));
try (final ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
list.add(rowMapper.mapping(resultSet));
}
}
return list;
}

public <T> List<T> queryForList(final String sql, final RowMapper<T> rowMapper) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()
) {
log.debug("Execute Query - query : {}", sql);
return makeResults(rowMapper, rs);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
public <T> List<T> queryForList(final String sql, final RowMapper<T> rowMapper, final Object... parameters) {
return executeQuery(sql, preparedStatement -> makeResults(rowMapper, preparedStatement), parameters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.springframework.jdbc.core;

import java.sql.PreparedStatement;
import java.sql.SQLException;

public interface PreparedStateExecutor<T> {

T execute(final PreparedStatement preparedStatement) throws SQLException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd

public static void releaseConnection(Connection connection, DataSource dataSource) {
try {
connection.close();
if(connection.getAutoCommit()){
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,7 @@
package org.springframework.transaction.support;

@FunctionalInterface
public interface TransactionExecutor<T> {

T execute();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

import static java.lang.ThreadLocal.withInitial;

public abstract class TransactionSynchronizationManager {

private static final ThreadLocal<Map<DataSource, Connection>> resources = new ThreadLocal<>();
private static final ThreadLocal<Map<DataSource, Connection>> resources = withInitial(HashMap::new);

private TransactionSynchronizationManager() {}
private TransactionSynchronizationManager() {
}

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

public static void bindResource(DataSource key, Connection value) {
resources.get().put(key, value);
}

public static Connection unbindResource(DataSource key) {
return null;
return resources.get().remove(key);
}
}
Loading

0 comments on commit ccb2e17

Please sign in to comment.