Skip to content

Commit cb4e4ea

Browse files
authored
[4단계 - Transaction synchronization 적용하기] 루키(김준하) 미션 제출합니다. (#1225)
* feat: 트랜잭션 동기화 매니저와 커넥션 릴리스 로직 추가 * refactor: 트랜잭션 동기화 매니저 적용 및 커넥션 관리 개선 * refactor: Connection 기반 로직 제거 및 트랜잭션 매니저 활용으로 코드 간소화 * fix: 중복 커넥션 바인딩 방지를 위한 예외 처리 추가 * refactor: 트랜잭션 커넥션 검사를 위한 메서드 추출 * refactor: 트랜잭션 로직 분리를 위한 UserService 인터페이스 도입 및 구현체 분리 * test: AOP 학습 테스트 * feat: AOP 기반 트랜잭션 관리 구현 및 @transactional 애노테이션 추가 * refactor: 중복된 커넥션 바인딩 로직 제거 * refactor: 예외 처리 로직 통합 및 커밋/롤백 처리 방식 개선 * refactor: 커넥션 정리 로직 추출 및 코드 간소화 * refactor: 변수명 오타 수정 * refactor: null 커넥션 체크 로직 추가
1 parent 1f4e3d5 commit cb4e4ea

File tree

22 files changed

+610
-190
lines changed

22 files changed

+610
-190
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.techcourse.aop;
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.aop.annotation.Transactional;
7+
import java.lang.reflect.InvocationHandler;
8+
import java.lang.reflect.InvocationTargetException;
9+
import java.lang.reflect.Method;
10+
import java.sql.Connection;
11+
import java.sql.SQLException;
12+
import javax.sql.DataSource;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
public class TransactionHandler implements InvocationHandler {
17+
18+
private static final Logger log = LoggerFactory.getLogger(TransactionHandler.class);
19+
20+
private final DataSource dataSource;
21+
private final Object target;
22+
23+
public TransactionHandler(final DataSource dataSource, final Object target) {
24+
this.dataSource = dataSource;
25+
this.target = target;
26+
}
27+
28+
@Override
29+
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
30+
final Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
31+
final Transactional transactional = targetMethod.getAnnotation(Transactional.class);
32+
33+
if (transactional == null) {
34+
return invokeWithoutTransaction(method, args);
35+
}
36+
37+
return invokeWithTransaction(method, args);
38+
}
39+
40+
private Object invokeWithoutTransaction(final Method method, final Object[] args) throws Throwable {
41+
try {
42+
return method.invoke(target, args);
43+
} catch (InvocationTargetException e) {
44+
throw e.getTargetException();
45+
}
46+
}
47+
48+
private Object invokeWithTransaction(final Method method, final Object[] args) throws Throwable {
49+
Connection conn = null;
50+
boolean originalAutoCommit = true;
51+
52+
try {
53+
conn = DataSourceUtils.getConnection(dataSource);
54+
TransactionSynchronizationManager.bindResource(dataSource, conn);
55+
originalAutoCommit = conn.getAutoCommit();
56+
conn.setAutoCommit(false);
57+
58+
final Object result = method.invoke(target, args);
59+
60+
conn.commit();
61+
return result;
62+
} catch (InvocationTargetException e) {
63+
throw handleException(conn, e);
64+
} finally {
65+
cleanUpConnection(conn, originalAutoCommit);
66+
}
67+
}
68+
69+
private Throwable handleException(final Connection conn, final InvocationTargetException e) {
70+
Throwable targetException = e.getTargetException();
71+
log.error(targetException.getMessage(), targetException);
72+
73+
if (targetException instanceof SQLException) {
74+
rollback(conn, e);
75+
return new DataAccessException(targetException.getMessage(), targetException);
76+
}
77+
78+
if (targetException instanceof RuntimeException || targetException instanceof Error) {
79+
rollback(conn, e);
80+
return targetException;
81+
}
82+
83+
commit(conn, e);
84+
return targetException;
85+
}
86+
87+
private void rollback(Connection conn, Exception originalException) {
88+
try {
89+
conn.rollback();
90+
} catch (SQLException rollbackEx) {
91+
log.error("Rollback failed", rollbackEx);
92+
originalException.addSuppressed(rollbackEx);
93+
}
94+
}
95+
96+
private void commit(Connection conn, Exception originalException) {
97+
try {
98+
conn.commit();
99+
} catch (SQLException commitEx) {
100+
log.error("Commit failed", commitEx);
101+
originalException.addSuppressed(commitEx);
102+
}
103+
}
104+
105+
private void cleanUpConnection(final Connection conn, final boolean originalAutoCommit) {
106+
if (conn == null) {
107+
return;
108+
}
109+
110+
TransactionSynchronizationManager.unbindResource(dataSource);
111+
restoreAutoCommit(conn, originalAutoCommit);
112+
closeConnection(conn);
113+
}
114+
115+
private void restoreAutoCommit(final Connection conn, final boolean originalAutoCommit) {
116+
try {
117+
conn.setAutoCommit(originalAutoCommit);
118+
} catch (SQLException e) {
119+
log.error(e.getMessage(), e);
120+
}
121+
}
122+
123+
private void closeConnection(final Connection conn) {
124+
try {
125+
conn.close();
126+
} catch (SQLException e) {
127+
log.error(e.getMessage(), e);
128+
}
129+
}
130+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.techcourse.aop.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Inherited;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target({ElementType.METHOD})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@Inherited
13+
@Documented
14+
public @interface Transactional {
15+
}

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

Lines changed: 0 additions & 21 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.ResultMapper;
55
import com.techcourse.domain.User;
6-
import java.sql.Connection;
76
import java.util.List;
87

98
public class UserDao {
@@ -40,33 +39,13 @@ public void update(final User user) {
4039
);
4140
}
4241

43-
public void update(Connection conn, final User user) {
44-
jdbcTemplate.update(
45-
conn,
46-
"update users set account = ?, password = ?, email = ? where id = ?",
47-
user.getAccount(),
48-
user.getPassword(),
49-
user.getEmail(),
50-
user.getId()
51-
);
52-
}
53-
5442
public List<User> findAll() {
5543
return jdbcTemplate.selectList(
5644
"select id, account, password, email from users",
5745
USER_MAPPER
5846
);
5947
}
6048

61-
public User findById(Connection conn, final Long id) {
62-
return jdbcTemplate.selectOne(
63-
conn,
64-
"select id, account, password, email from users where id = ?",
65-
USER_MAPPER,
66-
id
67-
);
68-
}
69-
7049
public User findById(final Long id) {
7150
return jdbcTemplate.selectOne(
7251
"select id, account, password, email from users where id = ?",

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.interface21.jdbc.core.JdbcTemplate;
44
import com.techcourse.domain.UserHistory;
5-
import java.sql.Connection;
65

76
public class UserHistoryDao {
87

@@ -23,17 +22,4 @@ public void log(final UserHistory userHistory) {
2322
userHistory.getCreateBy()
2423
);
2524
}
26-
27-
public void log(Connection conn, final UserHistory userHistory) {
28-
jdbcTemplate.update(
29-
conn,
30-
"insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)",
31-
userHistory.getUserId(),
32-
userHistory.getAccount(),
33-
userHistory.getPassword(),
34-
userHistory.getEmail(),
35-
userHistory.getCreatedAt(),
36-
userHistory.getCreateBy()
37-
);
38-
}
3925
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
public class AppUserService implements UserService {
11+
12+
private static final Logger log = LoggerFactory.getLogger(AppUserService.class);
13+
14+
private final UserDao userDao;
15+
private final UserHistoryDao userHistoryDao;
16+
17+
public AppUserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
18+
this.userDao = userDao;
19+
this.userHistoryDao = userHistoryDao;
20+
}
21+
22+
// @Transactional
23+
@Override
24+
public User findById(final long id) {
25+
return userDao.findById(id);
26+
}
27+
28+
// @Transactional
29+
@Override
30+
public void save(User user) {
31+
userDao.insert(user);
32+
}
33+
34+
// @Transactional
35+
@Override
36+
public void changePassword(final long id, final String newPassword, final String createdBy) {
37+
User user = userDao.findById(id);
38+
user.changePassword(newPassword);
39+
userDao.update(user);
40+
userHistoryDao.log(new UserHistory(user, createdBy));
41+
}
42+
}
43+
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 javax.sql.DataSource;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
13+
public class TxUserService implements UserService {
14+
15+
private static final Logger log = LoggerFactory.getLogger(TxUserService.class);
16+
17+
private final DataSource dataSource;
18+
private final UserService userService;
19+
20+
public TxUserService(DataSource dataSource, UserService userService) {
21+
this.dataSource = dataSource;
22+
this.userService = userService;
23+
}
24+
25+
@Override
26+
public User findById(long id) {
27+
return userService.findById(id);
28+
}
29+
30+
@Override
31+
public void save(User user) {
32+
userService.save(user);
33+
}
34+
35+
@Override
36+
public void changePassword(final long id, final String newPassword, final String createdBy) {
37+
Connection conn = null;
38+
try {
39+
conn = DataSourceUtils.getConnection(dataSource);
40+
TransactionSynchronizationManager.bindResource(dataSource, conn);
41+
conn.setAutoCommit(false);
42+
43+
userService.changePassword(id, newPassword, createdBy);
44+
45+
conn.commit();
46+
} catch (Exception e) {
47+
handleException(conn, e);
48+
} finally {
49+
cleanUpConnection(conn);
50+
}
51+
}
52+
53+
private void handleException(final Connection conn, final Exception exception) {
54+
log.error(exception.getMessage(), exception);
55+
56+
if (exception instanceof SQLException) {
57+
rollback(conn, exception);
58+
throw new DataAccessException(exception.getMessage(), exception);
59+
}
60+
61+
if (exception instanceof RuntimeException runtimeException) {
62+
rollback(conn, exception);
63+
throw runtimeException;
64+
}
65+
66+
commit(conn, exception);
67+
throw new RuntimeException(exception.getMessage(), exception);
68+
}
69+
70+
private void rollback(Connection conn, Throwable originalException) {
71+
if (conn == null) {
72+
return;
73+
}
74+
try {
75+
conn.rollback();
76+
} catch (SQLException rollbackEx) {
77+
log.error("Rollback failed", rollbackEx);
78+
originalException.addSuppressed(rollbackEx);
79+
}
80+
}
81+
82+
private void commit(Connection conn, Throwable originalException) {
83+
if (conn == null) {
84+
return;
85+
}
86+
try {
87+
conn.commit();
88+
} catch (SQLException commitEx) {
89+
log.error("Commit failed", commitEx);
90+
originalException.addSuppressed(commitEx);
91+
}
92+
}
93+
94+
private void cleanUpConnection(final Connection conn) {
95+
if (conn == null) {
96+
return;
97+
}
98+
99+
TransactionSynchronizationManager.unbindResource(dataSource);
100+
restoreAutoCommit(conn);
101+
closeConnection(conn);
102+
}
103+
104+
private void restoreAutoCommit(final Connection conn) {
105+
try {
106+
conn.setAutoCommit(true);
107+
} catch (SQLException e) {
108+
log.error(e.getMessage(), e);
109+
}
110+
}
111+
112+
private void closeConnection(final Connection conn) {
113+
try {
114+
conn.close();
115+
} catch (SQLException e) {
116+
log.error(e.getMessage(), e);
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)