Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
691e0d9
feat: TransactionSynchronizationManager 구현
eoehd1ek Oct 28, 2025
37eb953
feat: DataSourceUtils 구현
eoehd1ek Oct 28, 2025
582756d
refactor: 미존재 map 객체 생성 과정 메서드로 분리
eoehd1ek Oct 29, 2025
d8edf37
refactor: 예외 메시지 변경
eoehd1ek Oct 29, 2025
532b465
feat: UserService 인터페이스 분리
eoehd1ek Oct 29, 2025
2f6c9ca
feat: UserService 구현체 작성
eoehd1ek Oct 29, 2025
1277cd8
feat: TxUserService 추가
eoehd1ek Oct 29, 2025
b2cc317
refactor: connection 생성 -> DataSourceUtil 사용하도록 수정
eoehd1ek Oct 29, 2025
568c057
refactor: execute 메서드 네이밍 변경
eoehd1ek Oct 29, 2025
8ad1e61
refactor: connection을 DataSourceUtils이 관리하도록 변경
eoehd1ek Oct 29, 2025
1b20607
refactor: Service 계층 트랜잭션 메서드 미사용하도록 변경
eoehd1ek Oct 29, 2025
324114b
refactor: public 접근 제한자 수정
eoehd1ek Oct 29, 2025
32ad264
refactor: 트랜잭션을 위해 Connection을 받는 메서드 제거
eoehd1ek Oct 29, 2025
1d59adb
refactor: 미사용 필드값 제거
eoehd1ek Oct 29, 2025
f9f1654
refactor: 트랜잭션 로직 분리
eoehd1ek Oct 29, 2025
d993b64
refactor: 커넥션 등록 관리 책임 변경
eoehd1ek Oct 29, 2025
b71c8c6
feat: auto commit 복구 추가
eoehd1ek Oct 29, 2025
8ef702d
test: 트랜잭션 객체 사용하도록 변경
eoehd1ek Oct 29, 2025
14341da
refactor: 누락된 throws 예외 추가
eoehd1ek Oct 29, 2025
96f040c
refactor: 자원 반납 메서드에서 예외를 던지지 않도록 수정
eoehd1ek Oct 29, 2025
606f84d
refactor: 자원 반납 메서드에서 예외를 던지지 않도록 수정
eoehd1ek Oct 29, 2025
a9edd6a
refactor: 자원 반납 메서드에서 예외를 던지지 않도록 수정
eoehd1ek Oct 29, 2025
c3441b9
refactor: 자원 반납 메서드 순서 변경
eoehd1ek Oct 29, 2025
5704e9d
refactor: connection null 검증 추가
eoehd1ek Oct 29, 2025
406c889
refactor: CannotGetJdbcConnectionException 예외 래핑
eoehd1ek Oct 29, 2025
b4b352c
refactor: 삭제 가능한 변수 제거
eoehd1ek Oct 29, 2025
5162c91
refactor: 트랜잭션이 관리되는 커넥션 확인 로직 메서드 분리
eoehd1ek Oct 29, 2025
d8d9314
refactor: 커밋 상태 기록 후 복구하도록 변경
eoehd1ek Oct 30, 2025
cc62d5c
refactor: null 조건 검사 추가
eoehd1ek Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.interface21.jdbc.core.JdbcTemplate;
import com.interface21.jdbc.core.RowMapper;
import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
Expand Down Expand Up @@ -39,12 +38,6 @@ public void update(final User user) {
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public void transactionUpdate(final Connection connection, final User user) {
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
jdbcTemplate.update(connection, 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, USER_ROW_MAPPER);
Expand All @@ -55,11 +48,6 @@ public User findById(final Long id) {
return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id);
}

public User transactionFindById(final Connection connection, final Long id) {
final var sql = "select id, account, password, email from users where id = ?";
return jdbcTemplate.queryForObject(connection, sql, USER_ROW_MAPPER, id);
}

public User findByAccount(final String account) {
final var sql = "select id, account, password, email from users where account = ?";
return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, account);
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 @@ -2,7 +2,6 @@

import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public void log(final Connection connection, 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.update(
connection,
sql,
userHistory.getUserId(),
userHistory.getAccount(),
Expand Down
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 save(final User user) {
userDao.insert(user);
}

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
final User user = userDao.findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
}
}
36 changes: 36 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,36 @@
package com.techcourse.service;

import com.interface21.transaction.support.TransactionTemplate;
import com.techcourse.domain.User;
import javax.sql.DataSource;

public class TxUserService implements UserService {

private final UserService userService;
private final DataSource dataSource;
private final TransactionTemplate transactionTemplate;

public TxUserService(final UserService userService, final DataSource dataSource) {
this.userService = userService;
this.dataSource = dataSource;
this.transactionTemplate = new TransactionTemplate(dataSource);
}

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

@Override
public void save(final User user) {
userService.save(user);
}

@Override
public void changePassword(final long id, final String newPassword, final String createdBy) {
transactionTemplate.execute(() -> {
userService.changePassword(id, newPassword, createdBy);
return null;
});
}
}
47 changes: 4 additions & 43 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,51 +1,12 @@
package com.techcourse.service;

import com.interface21.dao.DataAccessException;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

public class UserService {
public interface UserService {

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

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final DataSource dataSource) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
this.dataSource = dataSource;
}
void save(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) {
try (final Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);

try {
final User user = userDao.transactionFindById(connection, id);
user.changePassword(newPassword);
userDao.transactionUpdate(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));

connection.commit();
} catch (Exception e) {
connection.rollback();
throw e;
}
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
void changePassword(final long id, final String newPassword, final String createdBy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.interface21.jdbc.core.JdbcTemplate;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;

public class MockUserHistoryDao extends UserHistoryDao {

Expand All @@ -13,7 +12,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();
}
}
8 changes: 5 additions & 3 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());

final var appUserService = new AppUserService(userDao, userHistoryDao);
final var userService = new TxUserService(appUserService, DataSourceConfig.getInstance());

final var newPassword = "qqqqq";
final var createBy = "gugu";
userService.changePassword(1L, newPassword, createBy);
Expand All @@ -46,7 +47,8 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());
final var appUserService = new AppUserService(userDao, userHistoryDao);
final var userService = new TxUserService(appUserService, DataSourceConfig.getInstance());

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
116 changes: 15 additions & 101 deletions jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.interface21.jdbc.core;

import com.interface21.dao.DataAccessException;
import com.interface21.jdbc.CannotGetJdbcConnectionException;
import com.interface21.jdbc.datasource.DataSourceUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
Expand Down Expand Up @@ -40,39 +42,14 @@ public int update(final String sql, final Object... parameters) throws DataAcces
*/
public int update(final String sql, final PreparedStatementSetter preparedStatementSetter)
throws DataAccessException {
return createNewConnectionAndExecute(sql, preparedStatementSetter,
return execute(sql, preparedStatementSetter,
preparedStatement -> executeUpdate(preparedStatement));
}

private int executeUpdate(final PreparedStatement preparedStatement) throws SQLException {
return preparedStatement.executeUpdate();
}

/**
* 외부에서 관리되는 Connection 객체를 사용하여 데이터베이스 작업을 수행합니다. parameters를 SQL의 물음표 마커(?) 순서대로 바인딩 후 SQL를 실행합니다.
*
* @param connection 외부에서 관리되는 Connection 객체
* @param sql 실행할 SQL
* @param parameters SQL의 물음표 마커(?)에 바인딩될 파라미터들
*/
public int update(final Connection connection, final String sql, final Object... parameters)
throws DataAccessException {
return update(connection, sql,
DEFAULT_PREPARED_STATEMENT_SETTER.getPreparedStatementSetter(parameters));
}

/**
* 외부에서 관리되는 Connection 객체를 사용하여 데이터베이스 작업을 수행합니다. preparedStatementSetter로 바인딩 후 SQL를 실행합니다.
*
* @param connection 외부에서 관리되는 Connection 객체
* @param sql 실행할 SQL
* @param preparedStatementSetter 파라미터를 바인딩할 PreparedStatementSetter 구현체
*/
public int update(final Connection connection, final String sql,
final PreparedStatementSetter preparedStatementSetter) throws DataAccessException {
return execute(connection, sql, preparedStatementSetter, preparedStatement -> executeUpdate(preparedStatement));
}

/**
* parameters를 SQL의 물음표 마커(?) 순서대로 바인딩 후 SQL 쿼리를 실행합니다. SQL 쿼리의 단일 결과를 RowMapper로 매핑하여 반환합니다.
*
Expand All @@ -96,40 +73,7 @@ public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, fina
*/
public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper,
final PreparedStatementSetter preparedStatementSetter) throws DataAccessException {
return createNewConnectionAndExecute(sql, preparedStatementSetter,
preparedStatement -> executeQueryAndMapSingleResult(preparedStatement, rowMapper));
}

/**
* 외부에서 관리되는 Connection 객체를 사용하여 데이터베이스 작업을 수행합니다. parameters를 SQL의 물음표 마커(?) 순서대로 바인딩 후 SQL 쿼리를 실행합니다. SQL 쿼리의 단일
* 결과를 RowMapper로 매핑하여 반환합니다.
*
* @param connection 외부에서 관리되는 Connection 객체
* @param sql 실행할 SQL 쿼리
* @param rowMapper 결과 행(Row)을 매핑하는 RowMapper 구현체
* @param parameters SQL의 물음표 마커(?)에 바인딩될 파라미터들
* @param <T> 매핑된 결과 객체의 타입
*/
public <T> T queryForObject(final Connection connection, final String sql, final RowMapper<T> rowMapper,
final Object... parameters) throws DataAccessException {
return queryForObject(connection, sql, rowMapper,
DEFAULT_PREPARED_STATEMENT_SETTER.getPreparedStatementSetter(parameters));
}

/**
* 외부에서 관리되는 Connection 객체를 사용하여 데이터베이스 작업을 수행합니다. preparedStatementSetter로 바인딩 후 SQL 쿼리를 실행합니다. SQL 쿼리의 단일 결과를
* RowMapper로 매핑하여 반환합니다.
*
* @param connection 외부에서 관리되는 Connection 객체
* @param sql 실행할 SQL
* @param rowMapper 결과 행(Row)을 매핑하는 RowMapper 구현체
* @param preparedStatementSetter 파라미터를 바인딩할 PreparedStatementSetter 구현체
* @param <T> 매핑된 결과 객체의 타입
*/
public <T> T queryForObject(final Connection connection, final String sql, final RowMapper<T> rowMapper,
final PreparedStatementSetter preparedStatementSetter)
throws DataAccessException {
return execute(connection, sql, preparedStatementSetter,
return execute(sql, preparedStatementSetter,
preparedStatement -> executeQueryAndMapSingleResult(preparedStatement, rowMapper));
}

Expand Down Expand Up @@ -174,40 +118,7 @@ public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final O
*/
public <T> List<T> query(final String sql, final RowMapper<T> rowMapper,
final PreparedStatementSetter preparedStatementSetter) throws DataAccessException {
return createNewConnectionAndExecute(sql, preparedStatementSetter,
preparedStatement -> executeQueryAndMapResults(preparedStatement, rowMapper));
}

/**
* 외부에서 관리되는 Connection 객체를 사용하여 데이터베이스 작업을 수행합니다. parameters를 SQL의 물음표 마커(?) 순서대로 바인딩 후 SQL 쿼리를 실행합니다. SQL 쿼리의 결과를
* RowMapper로 매핑하여 List로 반환합니다.
*
* @param connection 외부에서 관리되는 Connection 객체
* @param sql 실행할 SQL 쿼리
* @param rowMapper 결과 행(Row)을 매핑하는 RowMapper 구현체
* @param parameters SQL의 물음표 마커(?)에 바인딩될 파라미터들
* @param <T> 매핑된 결과 객체의 타입
*/
public <T> List<T> query(final Connection connection, final String sql, final RowMapper<T> rowMapper,
final Object... parameters) throws DataAccessException {
return query(connection, sql, rowMapper,
DEFAULT_PREPARED_STATEMENT_SETTER.getPreparedStatementSetter(parameters));
}

/**
* 외부에서 관리되는 Connection 객체를 사용하여 데이터베이스 작업을 수행합니다. preparedStatementSetter로 바인딩 후 SQL 쿼리를 실행합니다. SQL 쿼리의 결과를
* RowMapper로 매핑하여 List로 반환합니다.
*
* @param connection 외부에서 관리되는 Connection 객체
* @param sql 실행할 SQL
* @param rowMapper 결과 행(Row)을 매핑하는 RowMapper 구현체
* @param preparedStatementSetter 파라미터를 바인딩할 PreparedStatementSetter 구현체
* @param <T> 매핑된 결과 객체의 타입
*/
public <T> List<T> query(final Connection connection, final String sql, final RowMapper<T> rowMapper,
final PreparedStatementSetter preparedStatementSetter)
throws DataAccessException {
return execute(connection, sql, preparedStatementSetter,
return execute(sql, preparedStatementSetter,
preparedStatement -> executeQueryAndMapResults(preparedStatement, rowMapper));
}

Expand All @@ -227,20 +138,23 @@ private <T> List<T> mapResults(final ResultSet resultSet, final RowMapper<T> row
return results;
}

private <T> T createNewConnectionAndExecute(
private <T> T execute(
final String sql,
final PreparedStatementSetter preparedStatementSetter,
final PreparedStatementCallback<T> preparedStatementCallback
) throws DataAccessException {
try (final Connection connection = dataSource.getConnection()) {
return execute(connection, sql, preparedStatementSetter, preparedStatementCallback);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
Connection connection = null;
try {
connection = DataSourceUtils.getConnection(dataSource);
return doExecuteWithConnection(connection, sql, preparedStatementSetter, preparedStatementCallback);
} catch (CannotGetJdbcConnectionException ex) {
throw new DataAccessException(ex);
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
}
}

private <T> T execute(
private <T> T doExecuteWithConnection(
final Connection connection,
final String sql,
final PreparedStatementSetter preparedStatementSetter,
Expand Down
Loading