Skip to content

Commit 9c9e7a4

Browse files
authored
JDBC Step4 띠용 (#1235)
* feat: UserService 인터페이스 분리 및 트랜잭션 관리 개선 - UserService를 인터페이스로 분리하여 책임 분리 - AppUserService와 TxUserService 구현체 추가 - TxUserService에 TransactionManager 적용으로 트랜잭션 처리 캡슐화 - 코드 가독성과 유지보수성 향상 * refactor: 트랜잭션 관리 개선 및 중복 코드 제거 - TransactionSynchronizationManager에 자원 바인딩 및 해제 로직 추가 - DataSourceUtils의 releaseConnection 개선으로 안전한 리소스 해제 - JdbcTemplate 및 TransactionManager에서 중복 코드 제거 및 자원 관리 개선 - UserDao 및 UserHistoryDao의 Connection 기반 메서드 제거로 사용되지 않는 메서드 정리 - AppUserService 및 TxUserService로 트랜잭션 분리 및 예외 메시지 명확화 * refactor: releaseConnection 메서드의 트랜잭션 바인딩 검증 로직 개선 - TransactionSynchronizationManager의 isBound 메서드 활용으로 코드 간결화 - 불필요한 null 체크 로직 제거 및 리소스 해제 처리 안정 * refactor: releaseConnection 호출 순서 수정 - 트랜잭션 리소스 해제 순서 명확화 및 안정성 강화 * refactor: 트랜잭션 자원 해제 로직 개선 및 불필요한 import 제거 - TransactionSynchronizationManager에 releaseThreadLocalMap 메서드 추가로 자원 해제 처리 간결화 - 트랜잭션 리소스 해제 시 ThreadLocal에서 빈 맵 제거로 메모리 낭비 방지 - AppUserService 및 UserService에서 사용되지 않는 Connection import 제거로 코드 정리 및 가독성 향상 * refactor: 트랜잭션 롤백 및 autoCommit 복원 로직 개선 - 트랜잭션 롤백 시 발생한 SQLException을 suppressed로 추가하여 디버깅 정보 강화 - 트랜잭션 종료 후 원래 autoCommit 상태로 복원하도록 로직 수정 - DataAccessException 메시지 구체화 및 명확성 향상
1 parent cde2601 commit 9c9e7a4

File tree

11 files changed

+135
-89
lines changed

11 files changed

+135
-89
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
@@ -30,11 +30,6 @@ public void update(final User user) {
3030
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
3131
}
3232

33-
public void update(Connection connection, final User user) {
34-
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
35-
jdbcTemplate.update(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
36-
}
37-
3833
public List<User> findAll() {
3934
final var sql = "select id, account, password, email from users";
4035
return jdbcTemplate.query(sql, new UserRowMapper());
@@ -49,8 +44,4 @@ public User findByAccount(final String account) {
4944
final var sql = "select id, account, password, email from users where account = ?";
5045
return jdbcTemplate.executeQueryForObject(sql, new UserRowMapper(), account);
5146
}
52-
53-
public DataSource getDataSource() {
54-
return jdbcTemplate.getDataSource();
55-
}
5647
}

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,4 @@ public void log(final UserHistory userHistory) {
2727
userHistory.getCreateBy()
2828
);
2929
}
30-
31-
public void log(final Connection conn, final UserHistory userHistory) {
32-
jdbcTemplate.update(conn,
33-
INSERT_USER_HISTORY_SQL,
34-
userHistory.getUserId(),
35-
userHistory.getAccount(),
36-
userHistory.getPassword(),
37-
userHistory.getEmail(),
38-
userHistory.getCreatedAt(),
39-
userHistory.getCreateBy()
40-
);
41-
}
4230
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
public User findById(final long id) {
19+
return userDao.findById(id);
20+
}
21+
22+
public void save(User user) {
23+
userDao.insert(user);
24+
}
25+
26+
public void changePassword(final long id, final String newPassword, final String createdBy) {
27+
final var user = findById(id);
28+
user.changePassword(newPassword);
29+
userDao.update(user);
30+
userHistoryDao.log(new UserHistory(user, createdBy));
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.techcourse.service;
2+
3+
import com.interface21.transaction.TransactionManager;
4+
import com.techcourse.config.DataSourceConfig;
5+
import com.techcourse.domain.User;
6+
7+
public class TxUserService implements UserService {
8+
9+
private final TransactionManager transactionManager;
10+
private final UserService userService;
11+
12+
public TxUserService(UserService userService) {
13+
this.userService = userService;
14+
transactionManager = new TransactionManager(DataSourceConfig.getInstance());
15+
}
16+
17+
@Override
18+
public User findById(long id) {
19+
return userService.findById(id);
20+
}
21+
22+
@Override
23+
public void save(User user) {
24+
userService.save(user);
25+
}
26+
27+
@Override
28+
public void changePassword(long id, String newPassword, String createdBy) {
29+
transactionManager.executeTransaction(connection -> {
30+
userService.changePassword(id, newPassword, createdBy);
31+
});
32+
}
33+
}
Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,10 @@
11
package com.techcourse.service;
22

3-
import com.interface21.dao.DataAccessException;
4-
import com.interface21.jdbc.JdbcExecutionException;
5-
import com.interface21.transaction.TransactionManager;
6-
import com.techcourse.config.DataSourceConfig;
7-
import com.techcourse.dao.UserDao;
8-
import com.techcourse.dao.UserHistoryDao;
93
import com.techcourse.domain.User;
10-
import com.techcourse.domain.UserHistory;
11-
import java.sql.Connection;
12-
import java.sql.SQLException;
13-
import javax.sql.DataSource;
14-
import org.slf4j.Logger;
15-
import org.slf4j.LoggerFactory;
164

17-
public class UserService {
5+
public interface UserService {
186

19-
private static final Logger log = LoggerFactory.getLogger(UserService.class);
20-
private final UserDao userDao;
21-
private final UserHistoryDao userHistoryDao;
22-
private final TransactionManager transactionManager;
23-
24-
public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
25-
this.userDao = userDao;
26-
this.userHistoryDao = userHistoryDao;
27-
DataSource dataSource = DataSourceConfig.getInstance();
28-
transactionManager = new TransactionManager(dataSource);
29-
}
30-
31-
public User findById(final long id) {
32-
return userDao.findById(id);
33-
}
34-
35-
public void insert(final User user) {
36-
userDao.insert(user);
37-
}
38-
39-
public void changePassword(final long id, final String newPassword, final String createBy) {
40-
final var user = findById(id);
41-
user.changePassword(newPassword);
42-
transactionManager.executeTransaction(connection -> {
43-
userDao.update(connection, user);
44-
userHistoryDao.log(connection, new UserHistory(user, createBy));
45-
});
46-
}
7+
User findById(final long id);
8+
void save(final User user);
9+
void changePassword(final long id, final String newPassword, final String createdBy);
4710
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,4 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
1616
public void log(final UserHistory userHistory) {
1717
throw new DataAccessException();
1818
}
19-
20-
@Override
21-
public void log(final Connection connection, final UserHistory userHistory) {
22-
throw new DataAccessException();
23-
}
2419
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import com.interface21.dao.DataAccessException;
99
import com.interface21.jdbc.core.JdbcTemplate;
1010
import org.junit.jupiter.api.BeforeEach;
11-
import org.junit.jupiter.api.Disabled;
1211
import org.junit.jupiter.api.Test;
1312

1413
import static org.assertj.core.api.Assertions.assertThat;
@@ -32,7 +31,7 @@ void setUp() {
3231
@Test
3332
void testChangePassword() {
3433
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
35-
final var userService = new UserService(userDao, userHistoryDao);
34+
final var userService = new AppUserService(userDao, userHistoryDao);
3635

3736
final var newPassword = "qqqqq";
3837
final var createBy = "gugu";
@@ -47,13 +46,16 @@ void testChangePassword() {
4746
void testTransactionRollback() {
4847
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
4948
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
50-
final var userService = new UserService(userDao, userHistoryDao);
49+
// 애플리케이션 서비스
50+
final var appUserService = new AppUserService(userDao, userHistoryDao);
51+
// 트랜잭션 서비스 추상화
52+
final var userService = new TxUserService(appUserService);
5153

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

5860
final var actual = userService.findById(1L);
5961

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.interface21.jdbc.core;
22

33
import com.interface21.jdbc.JdbcExecutionException;
4+
import com.interface21.jdbc.datasource.DataSourceUtils;
45
import java.sql.Connection;
56
import java.sql.PreparedStatement;
67
import java.sql.ResultSet;
@@ -27,14 +28,16 @@ public int update(final String sql, final Object... parameters) {
2728
}
2829

2930
private <T> T execute(String sql, PreparedStatementExecutor<T> executor, Object[] parameters) {
30-
try (Connection conn = dataSource.getConnection();
31-
PreparedStatement pstmt = conn.prepareStatement(sql)) {
31+
Connection conn = DataSourceUtils.getConnection(dataSource);
32+
try (PreparedStatement pstmt = conn.prepareStatement(sql);) {
3233
setStatementParameters(pstmt, parameters);
3334
log.debug("query : {}, params : {}", sql, Arrays.toString(parameters));
3435
return executor.execute(pstmt);
3536
} catch (SQLException e) {
3637
log.error("executeUpdate failed. sql={}, params={}", sql, Arrays.toString(parameters), e);
3738
throw new JdbcExecutionException("쿼리 실행 실패 : " + e.getMessage());
39+
} finally {
40+
DataSourceUtils.releaseConnection(conn, dataSource);
3841
}
3942
}
4043

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ public static Connection getConnection(DataSource dataSource) throws CannotGetJd
2020

2121
try {
2222
connection = dataSource.getConnection();
23-
TransactionSynchronizationManager.bindResource(dataSource, connection);
2423
return connection;
2524
} catch (SQLException ex) {
2625
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
2726
}
2827
}
2928

3029
public static void releaseConnection(Connection connection, DataSource dataSource) {
30+
if(TransactionSynchronizationManager.isBound(dataSource)){
31+
return;
32+
}
3133
try {
3234
connection.close();
3335
} catch (SQLException ex) {
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.interface21.transaction;
22

33
import com.interface21.dao.DataAccessException;
4+
import com.interface21.jdbc.datasource.DataSourceUtils;
5+
import com.interface21.transaction.support.TransactionSynchronizationManager;
46
import java.sql.Connection;
57
import java.sql.SQLException;
68
import javax.sql.DataSource;
@@ -14,19 +16,29 @@ public TransactionManager(DataSource dataSource) {
1416
}
1517

1618
public void executeTransaction(final TransactionalAction action) {
17-
try (Connection connection = dataSource.getConnection()) {
18-
// transaction start
19+
Connection connection = DataSourceUtils.getConnection(dataSource);
20+
boolean originAutoCommit = false;
21+
try {
22+
originAutoCommit = connection.getAutoCommit();
1923
connection.setAutoCommit(false);
24+
TransactionSynchronizationManager.bindResource(dataSource, connection);
25+
action.execute(connection);
26+
connection.commit();
27+
} catch (Exception e) {
2028
try {
21-
action.execute(connection);
22-
connection.commit();
23-
} catch (Exception e) {
2429
connection.rollback();
25-
throw new DataAccessException("트랜잭션 오류발생으로 롤백", e);
30+
} catch (SQLException ex) {
31+
e.addSuppressed(ex);
2632
}
27-
connection.setAutoCommit(true);
28-
} catch (SQLException e) {
29-
throw new RuntimeException("커넥션 오류 발생", e);
33+
throw new DataAccessException("트랜잭션 수행 중 오류 발생으로 롤백 시도", e);
34+
} finally {
35+
try {
36+
connection.setAutoCommit(originAutoCommit);
37+
} catch (SQLException e) {
38+
throw new RuntimeException(e);
39+
}
40+
TransactionSynchronizationManager.unbindResource(dataSource);
41+
DataSourceUtils.releaseConnection(connection, dataSource);
3042
}
3143
}
3244
}

0 commit comments

Comments
 (0)