-
Notifications
You must be signed in to change notification settings - Fork 379
[4단계 - Transaction synchronization 적용하기] 피글렛(김은수) 미션 제출합니다. #1194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
865eeb9
f264b1c
b190ab3
5a8aef4
131245d
6f13cd5
4ad8d14
cc99149
776ceca
8c312a2
8f53bd9
8563bf0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 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,80 @@ | ||
| package com.techcourse.service; | ||
|
|
||
| import com.interface21.dao.DataAccessException; | ||
| import com.interface21.jdbc.datasource.DataSourceUtils; | ||
| import com.techcourse.domain.User; | ||
| import java.sql.Connection; | ||
| import java.sql.SQLException; | ||
| import java.util.function.Consumer; | ||
| import javax.sql.DataSource; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class TxUserService implements UserService { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(TxUserService.class); | ||
|
|
||
| private final UserService userService; | ||
| private final DataSource dataSource; | ||
|
|
||
| public TxUserService(UserService userService, DataSource dataSource) { | ||
| this.userService = userService; | ||
| this.dataSource = dataSource; | ||
| } | ||
|
|
||
| @Override | ||
| public User findById(long id) { | ||
| return userService.findById(id); | ||
| } | ||
|
|
||
| @Override | ||
| public void save(User user) { | ||
| userService.save(user); | ||
| } | ||
|
|
||
| @Override | ||
| public void changePassword(final long id, final String newPassword, final String createdBy){ | ||
| try { | ||
| doInTransaction(connection -> { | ||
| userService.changePassword(id, newPassword, createdBy); | ||
| }); | ||
| } catch (SQLException e) { | ||
| throw new DataAccessException(e); | ||
| } | ||
| } | ||
|
|
||
| private void doInTransaction(Consumer<Connection> consumer) throws SQLException { | ||
| Connection connection = DataSourceUtils.getConnection(dataSource); | ||
| connection.setAutoCommit(false); | ||
| try { | ||
| consumer.accept(connection); | ||
| connection.commit(); | ||
| } catch (Exception e) { | ||
| log.error("트랜잭션 수행 중 예외 발생"); | ||
| handleTransactionException(e, connection); | ||
| } finally { | ||
| closeConnection(connection); | ||
| } | ||
rladmstn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private void handleTransactionException(Exception e, Connection connection) { | ||
| if(connection != null) { | ||
| try { // 커넥션이 존재할 경우 롤백 시도 | ||
| connection.rollback(); | ||
| log.info("Rollback 완료"); | ||
| } catch (SQLException ex) { // 롤백 도중 예외 발생 | ||
| log.error("Rollback 실패", ex); | ||
| } | ||
| } | ||
| throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); | ||
| } | ||
rladmstn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| private void closeConnection(Connection connection) { | ||
| if(connection != null){ | ||
| try { | ||
| connection.close(); | ||
| DataSourceUtils.releaseConnection(connection, dataSource); | ||
| } catch (SQLException ignored) {} | ||
| } | ||
| } | ||
rladmstn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,79 +1,10 @@ | ||
| 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.Connection; | ||
| import java.sql.SQLException; | ||
| import java.util.function.Consumer; | ||
| import javax.sql.DataSource; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class UserService { | ||
| public interface UserService { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(UserService.class); | ||
|
|
||
| private final DataSource dataSource; | ||
| private final UserDao userDao; | ||
| private final UserHistoryDao userHistoryDao; | ||
|
|
||
| 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 userDao.findById(id); | ||
| } | ||
|
|
||
| public void insert(final User user) { | ||
| userDao.insert(user); | ||
| } | ||
|
|
||
| public void changePassword(final long id, final String newPassword, final String createBy) { | ||
| doInTransaction(connection -> { | ||
| final var user = findById(id); | ||
| user.changePassword(newPassword); | ||
| userDao.update(connection, user); | ||
| userHistoryDao.log(connection, new UserHistory(user, createBy)); | ||
| }); | ||
| } | ||
|
|
||
| private void doInTransaction(Consumer<Connection> consumer){ | ||
| Connection connection = null; | ||
| try { | ||
| connection = dataSource.getConnection(); | ||
| connection.setAutoCommit(false); | ||
| consumer.accept(connection); | ||
| connection.commit(); | ||
| } catch (Exception e) { | ||
| log.error("비밀번호 변경 중 예외 발생"); | ||
| handleTransactionException(e, connection); | ||
| } finally { | ||
| closeConnection(connection); | ||
| } | ||
| } | ||
|
|
||
| private void handleTransactionException(Exception e, Connection connection) { | ||
| if(connection != null) { | ||
| try { // 커넥션이 존재할 경우 롤백 시도 | ||
| connection.rollback(); | ||
| log.info("Rollback 완료"); | ||
| } catch (SQLException ex) { // 롤백 도중 예외 발생 | ||
| log.error("Rollback 실패", ex); | ||
| } | ||
| } | ||
| throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); | ||
| } | ||
|
|
||
| private void closeConnection(Connection connection) { | ||
| if(connection != null){ // 커넥션이 존재할 경우 close | ||
| try { | ||
| connection.close(); | ||
| } catch (SQLException ignored) {} | ||
| } | ||
| } | ||
| User findById(final long id); | ||
| void save(final User user); | ||
| void changePassword(final long id, final String newPassword, final String createdBy); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import com.interface21.dao.DataAccessException; | ||
| import com.interface21.jdbc.IncorrectResultSizeException; | ||
| import com.interface21.jdbc.datasource.DataSourceUtils; | ||
| import java.sql.Connection; | ||
| import java.sql.PreparedStatement; | ||
| import java.sql.ResultSet; | ||
|
|
@@ -23,22 +24,8 @@ public JdbcTemplate(final DataSource dataSource) { | |
| } | ||
|
|
||
| public void update(String sql, Object... params){ | ||
| try (Connection conn = dataSource.getConnection(); | ||
| PreparedStatement pstmt = conn.prepareStatement(sql)){ | ||
| log.debug("query : {}", sql); | ||
| setParameters(pstmt, params); | ||
| pstmt.executeUpdate(); | ||
| } catch (SQLException e) { | ||
| log.error(e.getMessage(), e); | ||
| throw new DataAccessException(e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| Connection 공유 버전 오버로딩 | ||
| */ | ||
| public void update(Connection connection, String sql, Object... params){ | ||
| try (PreparedStatement pstmt = connection.prepareStatement(sql)){ | ||
| Connection conn = DataSourceUtils.getConnection(dataSource); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 트랜잭션 내부에서 실행되는 쿼리가 아닐 경우에는 커넥션을 닫아주지 못할 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇네요!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋습니다 👍 |
||
| try (PreparedStatement pstmt = conn.prepareStatement(sql)){ | ||
| log.debug("query : {}", sql); | ||
| setParameters(pstmt, params); | ||
| pstmt.executeUpdate(); | ||
|
|
@@ -49,9 +36,8 @@ public void update(Connection connection, String sql, Object... params){ | |
| } | ||
|
|
||
| public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... params){ | ||
| try (Connection conn = dataSource.getConnection(); | ||
| PreparedStatement pstmt = conn.prepareStatement(sql)){ | ||
|
|
||
| Connection conn = DataSourceUtils.getConnection(dataSource); | ||
| try (PreparedStatement pstmt = conn.prepareStatement(sql)){ | ||
| log.debug("query : {}", sql); | ||
| setParameters(pstmt, params); | ||
| return executionResult(rowMapper, pstmt); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,9 @@ | |
|
|
||
| import com.interface21.jdbc.CannotGetJdbcConnectionException; | ||
| import com.interface21.transaction.support.TransactionSynchronizationManager; | ||
|
|
||
| import javax.sql.DataSource; | ||
| import java.sql.Connection; | ||
| import java.sql.SQLException; | ||
| import javax.sql.DataSource; | ||
|
|
||
| // 4단계 미션에서 사용할 것 | ||
| public abstract class DataSourceUtils { | ||
|
|
@@ -30,6 +29,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"); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
예외 전파/전환 정책 불일치 — 현재 SQLException 전환 경로가 사실상 작동하지 않음
doInTransaction 내부에서 모든 예외를 잡아 RuntimeException으로 재던지므로, changePassword의 catch(SQLException)은 도달하지 않습니다. 예외 전환 정책을 한 곳으로 모아 일관되게 유지하세요. 선택지는 대략 두 가지입니다:
현재 구조에 맞는 하나의 정책을 정하고 전역으로 일치시켜보세요. As per coding guidelines.
Also applies to: 46-58
예외 처리 정책 일관성을 검증하기 위해 저장소를 탐색하겠습니다.
Length of output: 1262
🏁 Script executed:
Length of output: 114
🏁 Script executed:
Length of output: 2147
🏁 Script executed:
Length of output: 7096
Length of output: 2415
🏁 Script executed:
Length of output: 1377
🏁 Script executed:
Length of output: 1444
예외 전파 정책 불일치 확인됨 — changePassword의 SQLException 캐치 블록은 도달 불가능
현재 코드의 문제점:
handleTransactionException (60줄)에서 모든 예외를 RuntimeException으로 변환하여 던짐
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);doInTransaction은 "throws SQLException" 선언하지만 실제로는 RuntimeException만 발생
changePassword의 catch(SQLException e) 블록 (41줄)은 실행되지 않음
선택할 수 있는 일관된 정책:
하나의 정책을 선택하여 doInTransaction, handleTransactionException, changePassword 간 예외 처리 흐름을 일관되게 재설계하세요.
🤖 Prompt for AI Agents