Skip to content

Commit fedbb80

Browse files
authored
[4단계 - Transaction synchronization 적용하기] 피글렛(김은수) 미션 제출합니다. (#1194)
* feat: TransactionSynchronizationManager 메서드 구현 * feat: 트랜잭션 동기화 매니저 및 유틸 클래스 사용으로 수정 * feat: DataSourceUtils에서 매니저 사용한 resource 언바인딩 * feat: UserService 추상화 및 구체 클래스 구현 * feat: 트랜잭션에 포함된 dataSource 가져오도록 JdbcTemplate 수정 * test: 트랜잭션 롤백 테스트 수정 * refactor: 불필요한 오버로딩 메서드 제거 * fix: 이중 close 제거 및 setAutoCommit 예외 처리 포함 * fix: 쿼리 실행 시 커넥션 close 추가 * fix: 커넥션 release 시 unbind 순서 수정 * fix: 트랜잭션 존재 여부 판단을 DataSourceUtils에서 하도록 위임 * fix: 트랜잭션에 바인딩 되지 않은 커넥션만 즉시 close 하도록 오류 해결
1 parent 5db4981 commit fedbb80

File tree

10 files changed

+146
-145
lines changed

10 files changed

+146
-145
lines changed

app/src/main/java/com/techcourse/dao/UserDao.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.interface21.jdbc.core.JdbcTemplate;
44
import com.interface21.jdbc.core.RowMapper;
55
import com.techcourse.domain.User;
6-
import java.sql.Connection;
76
import java.util.List;
87
import org.slf4j.Logger;
98
import org.slf4j.LoggerFactory;
@@ -35,14 +34,6 @@ public void update(final User user) {
3534
jdbcTemplate.update(sql, user.getPassword(), user.getEmail(), user.getId());
3635
}
3736

38-
/*
39-
Connection 공유 버전 overloading
40-
*/
41-
public void update(final Connection connection, final User user) {
42-
final var sql = "update users set password = ?, email = ? where id = ?";
43-
jdbcTemplate.update(connection, sql, user.getPassword(), user.getEmail(), user.getId());
44-
}
45-
4637
public List<User> findAll() {
4738
final var sql = "select id, account, password, email from users";
4839
return jdbcTemplate.query(sql, USER_ROW_MAPPER);

app/src/main/java/com/techcourse/dao/UserHistoryDao.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.interface21.jdbc.core.JdbcTemplate;
44
import com.techcourse.domain.UserHistory;
5-
import java.sql.Connection;
65
import org.slf4j.Logger;
76
import org.slf4j.LoggerFactory;
87

@@ -27,20 +26,4 @@ public void log(final UserHistory userHistory) {
2726
userHistory.getCreateBy()
2827
);
2928
}
30-
31-
/*
32-
Connection 공유 버전 overloading
33-
*/
34-
public void log(final Connection connection, final UserHistory userHistory) {
35-
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";
36-
jdbcTemplate.update(
37-
connection, sql,
38-
userHistory.getUserId(),
39-
userHistory.getAccount(),
40-
userHistory.getPassword(),
41-
userHistory.getEmail(),
42-
userHistory.getCreatedAt(),
43-
userHistory.getCreateBy()
44-
);
45-
}
4629
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.techcourse.service;
2+
3+
import com.techcourse.dao.UserDao;
4+
import com.techcourse.dao.UserHistoryDao;
5+
import com.techcourse.domain.User;
6+
import com.techcourse.domain.UserHistory;
7+
8+
public class AppUserService implements UserService {
9+
10+
private final UserDao userDao;
11+
private final UserHistoryDao userHistoryDao;
12+
13+
public AppUserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
14+
this.userDao = userDao;
15+
this.userHistoryDao = userHistoryDao;
16+
}
17+
18+
@Override
19+
public User findById(final long id) {
20+
return userDao.findById(id);
21+
}
22+
23+
@Override
24+
public void save(final User user) {
25+
userDao.insert(user);
26+
}
27+
28+
@Override
29+
public void changePassword(final long id, final String newPassword, final String createBy) {
30+
final var user = findById(id);
31+
user.changePassword(newPassword);
32+
userDao.update(user);
33+
userHistoryDao.log(new UserHistory(user, createBy));
34+
}
35+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.techcourse.service;
2+
3+
import com.interface21.dao.DataAccessException;
4+
import com.interface21.jdbc.datasource.DataSourceUtils;
5+
import com.interface21.transaction.support.TransactionSynchronizationManager;
6+
import com.techcourse.domain.User;
7+
import java.sql.Connection;
8+
import java.sql.SQLException;
9+
import java.util.function.Consumer;
10+
import javax.sql.DataSource;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
public class TxUserService implements UserService {
15+
16+
private static final Logger log = LoggerFactory.getLogger(TxUserService.class);
17+
18+
private final UserService userService;
19+
private final DataSource dataSource;
20+
21+
public TxUserService(UserService userService, DataSource dataSource) {
22+
this.userService = userService;
23+
this.dataSource = dataSource;
24+
}
25+
26+
@Override
27+
public User findById(long id) {
28+
return userService.findById(id);
29+
}
30+
31+
@Override
32+
public void save(User user) {
33+
userService.save(user);
34+
}
35+
36+
@Override
37+
public void changePassword(final long id, final String newPassword, final String createdBy){
38+
try {
39+
doInTransaction(connection -> {
40+
userService.changePassword(id, newPassword, createdBy);
41+
});
42+
} catch (SQLException e) {
43+
throw new DataAccessException(e);
44+
}
45+
}
46+
47+
private void doInTransaction(Consumer<Connection> consumer) throws SQLException {
48+
Connection connection = DataSourceUtils.getConnection(dataSource);
49+
try {
50+
connection.setAutoCommit(false);
51+
consumer.accept(connection);
52+
connection.commit();
53+
} catch (Exception e) {
54+
log.error("트랜잭션 수행 중 예외 발생");
55+
handleTransactionException(e, connection);
56+
} finally {
57+
closeConnection(connection);
58+
}
59+
}
60+
61+
private void handleTransactionException(Exception e, Connection connection) {
62+
if(connection != null) {
63+
try { // 커넥션이 존재할 경우 롤백 시도
64+
connection.rollback();
65+
log.info("Rollback 완료");
66+
} catch (SQLException ex) { // 롤백 도중 예외 발생
67+
log.error("Rollback 실패", ex);
68+
}
69+
}
70+
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
71+
}
72+
73+
private void closeConnection(Connection connection) {
74+
TransactionSynchronizationManager.unbindResource(dataSource);
75+
DataSourceUtils.releaseConnection(connection, dataSource);
76+
}
77+
}
Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,10 @@
11
package com.techcourse.service;
22

3-
import com.techcourse.dao.UserDao;
4-
import com.techcourse.dao.UserHistoryDao;
53
import com.techcourse.domain.User;
6-
import com.techcourse.domain.UserHistory;
7-
import java.sql.Connection;
8-
import java.sql.SQLException;
9-
import java.util.function.Consumer;
10-
import javax.sql.DataSource;
11-
import org.slf4j.Logger;
12-
import org.slf4j.LoggerFactory;
134

14-
public class UserService {
5+
public interface UserService {
156

16-
private static final Logger log = LoggerFactory.getLogger(UserService.class);
17-
18-
private final DataSource dataSource;
19-
private final UserDao userDao;
20-
private final UserHistoryDao userHistoryDao;
21-
22-
public UserService(final DataSource dataSource, final UserDao userDao, final UserHistoryDao userHistoryDao) {
23-
this.dataSource = dataSource;
24-
this.userDao = userDao;
25-
this.userHistoryDao = userHistoryDao;
26-
}
27-
28-
public User findById(final long id) {
29-
return userDao.findById(id);
30-
}
31-
32-
public void insert(final User user) {
33-
userDao.insert(user);
34-
}
35-
36-
public void changePassword(final long id, final String newPassword, final String createBy) {
37-
doInTransaction(connection -> {
38-
final var user = findById(id);
39-
user.changePassword(newPassword);
40-
userDao.update(connection, user);
41-
userHistoryDao.log(connection, new UserHistory(user, createBy));
42-
});
43-
}
44-
45-
private void doInTransaction(Consumer<Connection> consumer){
46-
Connection connection = null;
47-
try {
48-
connection = dataSource.getConnection();
49-
connection.setAutoCommit(false);
50-
consumer.accept(connection);
51-
connection.commit();
52-
} catch (Exception e) {
53-
log.error("비밀번호 변경 중 예외 발생");
54-
handleTransactionException(e, connection);
55-
} finally {
56-
closeConnection(connection);
57-
}
58-
}
59-
60-
private void handleTransactionException(Exception e, Connection connection) {
61-
if(connection != null) {
62-
try { // 커넥션이 존재할 경우 롤백 시도
63-
connection.rollback();
64-
log.info("Rollback 완료");
65-
} catch (SQLException ex) { // 롤백 도중 예외 발생
66-
log.error("Rollback 실패", ex);
67-
}
68-
}
69-
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
70-
}
71-
72-
private void closeConnection(Connection connection) {
73-
if(connection != null){ // 커넥션이 존재할 경우 close
74-
try {
75-
connection.close();
76-
} catch (SQLException ignored) {}
77-
}
78-
}
7+
User findById(final long id);
8+
void save(final User user);
9+
void changePassword(final long id, final String newPassword, final String createdBy);
7910
}

app/src/test/java/com/techcourse/service/MockUserHistoryDao.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.interface21.jdbc.core.JdbcTemplate;
55
import com.techcourse.dao.UserHistoryDao;
66
import com.techcourse.domain.UserHistory;
7-
import java.sql.Connection;
87

98
public class MockUserHistoryDao extends UserHistoryDao {
109

@@ -13,7 +12,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
1312
}
1413

1514
@Override
16-
public void log(final Connection connection, final UserHistory userHistory) {
15+
public void log(final UserHistory userHistory) {
1716
throw new DataAccessException();
1817
}
1918
}

app/src/test/java/com/techcourse/service/UserServiceTest.java

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.interface21.jdbc.core.JdbcTemplate;
88
import com.techcourse.config.DataSourceConfig;
99
import com.techcourse.dao.UserDao;
10-
import com.techcourse.dao.UserHistoryDao;
1110
import com.techcourse.domain.User;
1211
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
1312
import javax.sql.DataSource;
@@ -31,31 +30,20 @@ void setUp() {
3130
userDao.insert(user);
3231
}
3332

34-
@Test
35-
void testChangePassword() {
36-
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
37-
final var userService = new UserService(dataSource, userDao, userHistoryDao);
38-
39-
final var newPassword = "qqqqq";
40-
final var createBy = "gugu";
41-
userService.changePassword(1L, newPassword, createBy);
42-
43-
final var actual = userService.findById(1L);
44-
45-
assertThat(actual.getPassword()).isEqualTo(newPassword);
46-
}
47-
4833
@Test
4934
void testTransactionRollback() {
5035
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
5136
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
52-
final var userService = new UserService(dataSource, userDao, userHistoryDao);
37+
// 애플리케이션 서비스
38+
final var appUserService = new AppUserService(userDao, userHistoryDao);
39+
// 트랜잭션 서비스 추상화
40+
final var userService = new TxUserService(appUserService, dataSource);
5341

5442
final var newPassword = "newPassword";
55-
final var createBy = "gugu";
43+
final var createdBy = "gugu";
5644
// 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다.
5745
assertThrows(DataAccessException.class,
58-
() -> userService.changePassword(1L, newPassword, createBy));
46+
() -> userService.changePassword(1L, newPassword, createdBy));
5947

6048
final var actual = userService.findById(1L);
6149

jdbc/src/main/java/com/interface21/jdbc/core/JdbcTemplate.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.interface21.dao.DataAccessException;
44
import com.interface21.jdbc.IncorrectResultSizeException;
5+
import com.interface21.jdbc.datasource.DataSourceUtils;
56
import java.sql.Connection;
67
import java.sql.PreparedStatement;
78
import java.sql.ResultSet;
@@ -23,41 +24,30 @@ public JdbcTemplate(final DataSource dataSource) {
2324
}
2425

2526
public void update(String sql, Object... params){
26-
try (Connection conn = dataSource.getConnection();
27-
PreparedStatement pstmt = conn.prepareStatement(sql)){
28-
log.debug("query : {}", sql);
29-
setParameters(pstmt, params);
30-
pstmt.executeUpdate();
31-
} catch (SQLException e) {
32-
log.error(e.getMessage(), e);
33-
throw new DataAccessException(e.getMessage(), e);
34-
}
35-
}
36-
37-
/*
38-
Connection 공유 버전 오버로딩
39-
*/
40-
public void update(Connection connection, String sql, Object... params){
41-
try (PreparedStatement pstmt = connection.prepareStatement(sql)){
27+
Connection conn = DataSourceUtils.getConnection(dataSource);
28+
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
4229
log.debug("query : {}", sql);
4330
setParameters(pstmt, params);
4431
pstmt.executeUpdate();
4532
} catch (SQLException e) {
4633
log.error(e.getMessage(), e);
4734
throw new DataAccessException(e.getMessage(), e);
35+
} finally {
36+
DataSourceUtils.releaseConnection(conn, dataSource);
4837
}
4938
}
5039

5140
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... params){
52-
try (Connection conn = dataSource.getConnection();
53-
PreparedStatement pstmt = conn.prepareStatement(sql)){
54-
41+
Connection conn = DataSourceUtils.getConnection(dataSource);
42+
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
5543
log.debug("query : {}", sql);
5644
setParameters(pstmt, params);
5745
return executionResult(rowMapper, pstmt);
5846
} catch (SQLException e) {
5947
log.error(e.getMessage(), e);
6048
throw new DataAccessException(e.getMessage(), e);
49+
} finally {
50+
DataSourceUtils.releaseConnection(conn, dataSource);
6151
}
6252
}
6353

jdbc/src/main/java/com/interface21/jdbc/datasource/DataSourceUtils.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
import com.interface21.jdbc.CannotGetJdbcConnectionException;
44
import com.interface21.transaction.support.TransactionSynchronizationManager;
5-
6-
import javax.sql.DataSource;
75
import java.sql.Connection;
86
import java.sql.SQLException;
7+
import javax.sql.DataSource;
98

109
// 4단계 미션에서 사용할 것
1110
public abstract class DataSourceUtils {
@@ -29,7 +28,9 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd
2928

3029
public static void releaseConnection(Connection connection, DataSource dataSource) {
3130
try {
32-
connection.close();
31+
if(!TransactionSynchronizationManager.existsResource(dataSource)) {
32+
connection.close();
33+
}
3334
} catch (SQLException ex) {
3435
throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");
3536
}

0 commit comments

Comments
 (0)