Skip to content

Commit 2de8005

Browse files
authored
[4단계 - Transaction synchronization 적용하기] 시소(이갱민) 미션 제출합니다. (#1236)
* feat: 동일한 Connection 객체 사용하여 트랜잭션 추가 * chore: Conflict 해결 * feat: TransactionSynchronizationManager 구현 - ThreadLocal을 기반으로 DataSource별 Connection 관리 * feat: UserService 인터페이스 분리 및 구현체 추가 * refactor: Connection 파라미터 제거 및 transaction synchronization 적용 * test: 테스트 코드 반영 * fix: setAutoCommit 위치 변경 * refactor: 트랜잭션 적용 메서드 분리 * style: 컨벤션 통일 * feat: JdbcTemplate 리소스 누수 문제 해결 * feat: unbindResource 트랜잭션 경계 검사 로직 추가 * refactor: 트랜잭션 로직 분리 * fix: 롤백 중 예외 발생 시 최상위 메시지가 고정되던 현상 수정 * refactor: TransactionTemplate.execute 메서드 분리 * refactor: Exception을 Early Throw 하여 else 키워드 제거
1 parent fe1ee5e commit 2de8005

File tree

12 files changed

+242
-135
lines changed

12 files changed

+242
-135
lines changed

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

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,6 @@ public void update(final User user) {
4343
);
4444
}
4545

46-
public void update(final Connection connection, final User user) {
47-
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
48-
jdbcTemplate.update(
49-
connection,
50-
sql,
51-
user.getAccount(),
52-
user.getPassword(),
53-
user.getEmail(),
54-
user.getId()
55-
);
56-
}
57-
5846
public List<User> findAll() {
5947
final var sql = "select id, account, password, email from users";
6048
return jdbcTemplate.query(sql, userRowMapper);
@@ -66,12 +54,6 @@ public Optional<User> findById(final Long id) {
6654
return Optional.ofNullable(user);
6755
}
6856

69-
public Optional<User> findById(final Connection connection, final Long id) {
70-
final var sql = "select id, account, password, email from users where id = ?";
71-
final User user = jdbcTemplate.queryForObject(connection, sql, userRowMapper, id);
72-
return Optional.ofNullable(user);
73-
}
74-
7557
public Optional<User> findByAccount(final String account) {
7658
final var sql = "select id, account, password, email from users where account = ?";
7759
final User user = jdbcTemplate.queryForObject(sql, userRowMapper, account);

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

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

33
import com.techcourse.domain.UserHistory;
44
import com.interface21.jdbc.core.JdbcTemplate;
5-
import java.sql.Connection;
65

76
public class UserHistoryDao {
87

@@ -25,17 +24,4 @@ public void log(final UserHistory userHistory) {
2524
userHistory.getCreateBy()
2625
);
2726
}
28-
29-
public void log(final Connection connection, final UserHistory userHistory) {
30-
jdbcTemplate.update(
31-
connection,
32-
LOG_SQL,
33-
userHistory.getUserId(),
34-
userHistory.getAccount(),
35-
userHistory.getPassword(),
36-
userHistory.getEmail(),
37-
userHistory.getCreatedAt(),
38-
userHistory.getCreateBy()
39-
);
40-
}
4127
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
import java.util.NoSuchElementException;
8+
9+
public class AppUserService implements UserService {
10+
11+
private final UserDao userDao;
12+
private final UserHistoryDao userHistoryDao;
13+
14+
public AppUserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
15+
this.userDao = userDao;
16+
this.userHistoryDao = userHistoryDao;
17+
}
18+
19+
@Override
20+
public void insert(final User user) {
21+
userDao.insert(user);
22+
}
23+
24+
@Override
25+
public User findById(final long id) {
26+
return userDao.findById(id)
27+
.orElseThrow(NoSuchElementException::new);
28+
}
29+
30+
@Override
31+
public void changePassword(final long id, final String newPassword, final String createdBy) {
32+
final var user = findById(id);
33+
user.changePassword(newPassword);
34+
userDao.update(user);
35+
userHistoryDao.log(new UserHistory(user, createdBy));
36+
}
37+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.techcourse.service;
2+
3+
import com.interface21.transaction.support.TransactionTemplate;
4+
import com.techcourse.domain.User;
5+
import javax.sql.DataSource;
6+
7+
public class TxUserService implements UserService {
8+
9+
private final UserService userService;
10+
private final TransactionTemplate transactionTemplate;
11+
12+
public TxUserService(final UserService userService, final DataSource dataSource) {
13+
this.userService = userService;
14+
this.transactionTemplate = new TransactionTemplate(dataSource);
15+
}
16+
17+
@Override
18+
public void insert(final User user) {
19+
userService.insert(user);
20+
}
21+
22+
@Override
23+
public User findById(final long id) {
24+
return userService.findById(id);
25+
}
26+
27+
@Override
28+
public void changePassword(final long id, final String newPassword, final String createdBy) {
29+
transactionTemplate.execute(() -> userService.changePassword(id, newPassword, createdBy));
30+
}
31+
}
Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,12 @@
11
package com.techcourse.service;
22

3-
import com.interface21.dao.DataAccessException;
4-
import com.techcourse.dao.UserDao;
5-
import com.techcourse.dao.UserHistoryDao;
63
import com.techcourse.domain.User;
7-
import com.techcourse.domain.UserHistory;
8-
import java.sql.Connection;
9-
import java.sql.SQLException;
10-
import java.util.NoSuchElementException;
11-
import javax.sql.DataSource;
12-
import org.slf4j.Logger;
13-
import org.slf4j.LoggerFactory;
144

15-
public class UserService {
5+
public interface UserService {
166

17-
private static final Logger log = LoggerFactory.getLogger(UserService.class);
7+
void insert(final User user);
188

19-
private final UserDao userDao;
20-
private final UserHistoryDao userHistoryDao;
21-
private final DataSource dataSource;
9+
User findById(final long id);
2210

23-
public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final DataSource dataSource) {
24-
this.userDao = userDao;
25-
this.userHistoryDao = userHistoryDao;
26-
this.dataSource = dataSource;
27-
}
28-
29-
public void insert(final User user) {
30-
userDao.insert(user);
31-
}
32-
33-
public User findById(final long id) {
34-
return userDao.findById(id)
35-
.orElseThrow(NoSuchElementException::new);
36-
}
37-
38-
public void changePassword(final long id, final String newPassword, final String createBy) {
39-
try (Connection connection = dataSource.getConnection()) {
40-
connection.setAutoCommit(false);
41-
42-
try {
43-
final var user = findById(connection, id);
44-
user.changePassword(newPassword);
45-
userDao.update(connection, user);
46-
userHistoryDao.log(connection, new UserHistory(user, createBy));
47-
48-
connection.commit();
49-
} catch (Exception e) {
50-
try {
51-
connection.rollback();
52-
} catch (SQLException rollbackEx) {
53-
e.addSuppressed(rollbackEx);
54-
}
55-
throw new DataAccessException("비밀번호 변경 실패", e);
56-
}
57-
} catch (SQLException e) {
58-
throw new DataAccessException("비밀번호 변경 실패", e);
59-
}
60-
}
61-
62-
private User findById(final Connection connection, final long id) {
63-
return userDao.findById(connection, id)
64-
.orElseThrow(NoSuchElementException::new);
65-
}
11+
void changePassword(final long id, final String newPassword, final String createdBy);
6612
}

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

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

98
public class MockUserHistoryDao extends UserHistoryDao {
109

@@ -16,9 +15,4 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
1615
public void log(final UserHistory userHistory) {
1716
throw new DataAccessException();
1817
}
19-
20-
@Override
21-
public void log(final Connection connection, final UserHistory userHistory) {
22-
throw new DataAccessException();
23-
}
2418
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ void setUp() {
3131
@Test
3232
void testChangePassword() {
3333
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
34-
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());
34+
final var appUserService = new AppUserService(userDao, userHistoryDao);
35+
final var userService = new TxUserService(appUserService, DataSourceConfig.getInstance());
3536

3637
final var newPassword = "qqqqq";
3738
final var createBy = "gugu";
@@ -46,7 +47,8 @@ void testChangePassword() {
4647
void testTransactionRollback() {
4748
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
4849
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
49-
final var userService = new UserService(userDao, userHistoryDao, DataSourceConfig.getInstance());
50+
final var appUserService = new AppUserService(userDao, userHistoryDao);
51+
final var userService = new TxUserService(appUserService, DataSourceConfig.getInstance());
5052

5153
final var newPassword = "newPassword";
5254
final var createBy = "gugu";

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

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.interface21.jdbc.core;
22

33
import com.interface21.dao.DataAccessException;
4+
import com.interface21.jdbc.datasource.DataSourceUtils;
5+
import com.interface21.transaction.support.TransactionSynchronizationManager;
46
import org.slf4j.Logger;
57
import org.slf4j.LoggerFactory;
68

79
import javax.sql.DataSource;
8-
import java.sql.Connection;
910
import java.sql.PreparedStatement;
1011
import java.sql.SQLException;
1112
import java.util.ArrayList;
@@ -25,18 +26,10 @@ public void update(final String sql, final Object... args) {
2526
update(sql, ps -> setParameters(ps, args));
2627
}
2728

28-
public void update(final Connection connection, final String sql, final Object... args) {
29-
update(connection, sql, ps -> setParameters(ps, args));
30-
}
31-
3229
public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... args) {
3330
return query(sql, rowMapper, ps -> setParameters(ps, args));
3431
}
3532

36-
public <T> List<T> query(final Connection connection, final String sql, final RowMapper<T> rowMapper, final Object... args) {
37-
return query(connection, sql, rowMapper, ps -> setParameters(ps, args));
38-
}
39-
4033
public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
4134
final List<T> results = query(sql, rowMapper, args);
4235
if (results.isEmpty()) {
@@ -45,47 +38,34 @@ public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, fina
4538
return results.getFirst();
4639
}
4740

48-
public <T> T queryForObject(final Connection connection, final String sql, final RowMapper<T> rowMapper, final Object... args) {
49-
final List<T> results = query(connection, sql, rowMapper, args);
50-
if (results.isEmpty()) {
51-
return null;
52-
}
53-
return results.getFirst();
54-
}
55-
5641
private void update(final String sql, final PreparedStatementSetter preparedStatementSetter) {
57-
try (final var connection = dataSource.getConnection();
58-
final var preparedStatement = connection.prepareStatement(sql)) {
59-
preparedStatementSetter.setValues(preparedStatement);
60-
preparedStatement.executeUpdate();
61-
} catch (final SQLException e) {
62-
throw new DataAccessException(e);
63-
}
64-
}
65-
66-
private void update(final Connection connection, final String sql, final PreparedStatementSetter preparedStatementSetter) {
42+
final boolean hadExistingConnection = DataSourceUtils.hasResource(dataSource);
43+
final var connection = DataSourceUtils.getConnection(dataSource);
6744
try (final var preparedStatement = connection.prepareStatement(sql)) {
6845
preparedStatementSetter.setValues(preparedStatement);
6946
preparedStatement.executeUpdate();
7047
} catch (final SQLException e) {
7148
throw new DataAccessException(e);
49+
} finally {
50+
if (!hadExistingConnection) {
51+
DataSourceUtils.releaseConnection(connection, dataSource);
52+
TransactionSynchronizationManager.unbindResource(dataSource);
53+
}
7254
}
7355
}
7456

7557
private <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final PreparedStatementSetter preparedStatementSetter) {
76-
try (final var connection = dataSource.getConnection();
77-
final var preparedStatement = connection.prepareStatement(sql)) {
78-
return executeQuery(preparedStatement, rowMapper, preparedStatementSetter);
79-
} catch (final SQLException e) {
80-
throw new DataAccessException(e);
81-
}
82-
}
83-
84-
private <T> List<T> query(final Connection connection, final String sql, final RowMapper<T> rowMapper, final PreparedStatementSetter preparedStatementSetter) {
58+
final boolean hadExistingConnection = DataSourceUtils.hasResource(dataSource);
59+
final var connection = DataSourceUtils.getConnection(dataSource);
8560
try (final var preparedStatement = connection.prepareStatement(sql)) {
8661
return executeQuery(preparedStatement, rowMapper, preparedStatementSetter);
8762
} catch (final SQLException e) {
8863
throw new DataAccessException(e);
64+
} finally {
65+
if (!hadExistingConnection) {
66+
DataSourceUtils.releaseConnection(connection, dataSource);
67+
TransactionSynchronizationManager.unbindResource(dataSource);
68+
}
8969
}
9070
}
9171

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public abstract class DataSourceUtils {
1212

1313
private DataSourceUtils() {}
1414

15+
public static boolean hasResource(DataSource dataSource) {
16+
return TransactionSynchronizationManager.getResource(dataSource) != null;
17+
}
18+
1519
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
1620
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
1721
if (connection != null) {

0 commit comments

Comments
 (0)