Skip to content
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

[JDBC 4단계] 민트(유재민) 미션 제출합니다. #458

Merged
merged 21 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
23f8a48
패키지 위치 변경 및 코드 정리
kang-hyungu Sep 21, 2023
fd3b51e
docs: 3단계 3차 리뷰 내용 정리
yujamint Oct 4, 2023
161d0af
chore: EOF 해결
yujamint Oct 4, 2023
9ce3a83
test: 트랜잭션 학습 테스트
yujamint Oct 5, 2023
696a787
refactor: TransactionContext 삭제 및 TransactionSynchronizationManager 적용
yujamint Oct 5, 2023
93261f4
refactor: DataSourceUtils 적용
yujamint Oct 5, 2023
94d40f9
refactor: UserService 추상화
yujamint Oct 5, 2023
055eb1b
refactor: TransactionCallback의 반환 타입을 제네릭으로 수정
yujamint Oct 5, 2023
34ffba0
feat: Nullable 어노테이션 적용
yujamint Oct 5, 2023
3c484ec
docs: 4단계 리뷰 내용 정리
yujamint Oct 6, 2023
e205b8f
style: 불필요한 공백 제거
yujamint Oct 6, 2023
df7e776
refactor: TxUserService의 TransactionTemplate 생성자 주입받도록 수정
yujamint Oct 6, 2023
b33cfd1
feat(TransactionTemplate): 반환값이 없는 메서드 구현
yujamint Oct 6, 2023
36fbd06
refactor: 중복되는 resources.get() 제거
yujamint Oct 6, 2023
7e30cef
style: import 와일드카드 제거
yujamint Oct 6, 2023
3438d8d
refactor: ConnectionManager 제거 및 DataSourceUtils로 책임 이전
yujamint Oct 9, 2023
5ad575e
test: 학습 테스트 로깅 추가
yujamint Oct 9, 2023
13f7d03
refactor: 불필요한 어노테이션 제거
yujamint Oct 9, 2023
d0e93ca
docs: 4단계 2차 리뷰 내용 정리
yujamint Oct 10, 2023
c11f4f4
refactor: TransactionCallback 제거 후 표준 함수형 인터페이스 사용
yujamint Oct 10, 2023
c2efb84
fix: autoCommit 모드에서 Connection이 닫히지 않는 문제 해결
yujamint Oct 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,30 @@
- [x] 공유자원 Singleton vs static
- [x] JdbcTemplate 테스트 작성
- [x] JdbcTemplate의 executeQuery 중복 제거
- [x] TransactionTemplate catch 절 final
- [x] TransactionTemplate catch 절 final

### 3차
- [x] EOF 해결(TransactionTemplateTest, PreparedStatementCreatorTest)

## 4단계 리뷰
- [x] AppUserService 생성자 빈칸 제거
- [x] TxUserService TransactionTemplate 생성자로 받도록 수정
- [x] TransactionTemplate 반환값 없는 메서드 만들기
- [x] Nullable TypeQualifierNickname 어떤 효과?
- [x] TransactionSyncrhonizationManager 중복되는 get() 제거
- [x] ConnectionManager 제거 리팩토링
- 현재 흐름
- Transaction
1. TransactionTemplate에게 요청
2. DataSourceUtils -> Connection 생성 및 ThreadLocal 등록
3. TransactionTemplate 트랜잭션 시작하며 로직 수행 -> JdbcTemplate 호출
4. JdbcTemplate은 ConnectionManager에게 Connection 요청
5. ConnectionManager는 ThreadLocal의 Connection 반환
6. JdbcTemplate DB 접근 끝
7. TransactionTemplate 로직 끝 -> 커밋
8. DataSourceUtils 통해 Connection 자원 반환, ThreadLocal에서도 제거
- Transaction X
1. JdbcTemplate은 ConnectionManager에게 Connection 요청
2. ConnectionManager는 Connection 새로 생성해서 반환
3. JdbcTemplate DB 접근 끝
4. ConnectionManager가 Connection 자원 반환
4 changes: 4 additions & 0 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

Expand All @@ -18,6 +20,8 @@ public class UserDao {
return new User(id, account, password, email);
};

private static final Logger log = LoggerFactory.getLogger(UserDao.class);

private final JdbcTemplate jdbcTemplate;

public UserDao(final DataSource dataSource) {
Expand Down
33 changes: 33 additions & 0 deletions app/src/main/java/com/techcourse/service/AppUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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;
}

public User findById(final long id) {
return userDao.findById(id)
.orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다."));
}

public void insert(final User user) {
userDao.insert(user);
}

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));
}
}
30 changes: 30 additions & 0 deletions app/src/main/java/com/techcourse/service/TxUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.techcourse.service;

import com.techcourse.domain.User;
import org.springframework.transaction.support.TransactionTemplate;

public class TxUserService implements UserService {

private final UserService userService;
private final TransactionTemplate transactionTemplate;

public TxUserService(final UserService userService, final TransactionTemplate transactionTemplate) {
this.userService = userService;
this.transactionTemplate = transactionTemplate;
}

@Override
public User findById(final long id) {
return transactionTemplate.executeWithTransaction(() -> userService.findById(id));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크.. 나머지 메서드들도 트랜잭션 다 깔끔하게 적용해주셨네요 👍

}

@Override
public void insert(final User user) {
transactionTemplate.executeWithoutResult(() -> userService.insert(user));
}

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionTemplate.executeWithoutResult(() -> userService.changePassword(id, newPassword, createBy));
}
}
40 changes: 4 additions & 36 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,10 @@
package com.techcourse.service;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
public interface UserService {

public class UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
private final TransactionTemplate transactionTemplate;

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
final DataSource dataSource = DataSourceConfig.getInstance();
this.transactionTemplate = new TransactionTemplate(dataSource);
}

public User findById(final long id) {
return userDao.findById(id)
.orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다."));
}

public void insert(final User user) {
userDao.insert(user);
}

public void changePassword(final long id, final String newPassword, final String createBy) {
transactionTemplate.executeWithTransaction(() -> {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
});
}
User findById(final long id);
void insert(final User user);
void changePassword(final long id, final String newPassword, final String createBy);
}
15 changes: 12 additions & 3 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
Expand All @@ -18,11 +21,14 @@ class UserServiceTest {

private JdbcTemplate jdbcTemplate;
private UserDao userDao;
private TransactionTemplate transactionTemplate;

@BeforeEach
void setUp() {
this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
DataSource dataSource = DataSourceConfig.getInstance();
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.userDao = new UserDao(jdbcTemplate);
this.transactionTemplate = new TransactionTemplate(dataSource);

DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
final var user = new User("gugu", "password", "[email protected]");
Expand All @@ -32,7 +38,7 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new AppUserService(userDao, userHistoryDao);

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -47,7 +53,10 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
// 애플리케이션 서비스
final var appUserService = new AppUserService(userDao, userHistoryDao);
// 트랜잭션 서비스 추상화
final var userService = new TxUserService(appUserService, transactionTemplate);

final User user = userDao.findById(1L).get();
final var oldPassword = user.getPassword();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
Expand All @@ -17,25 +18,25 @@ public class JdbcTemplate {
private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);

private final PreparedStatementCreator preparedStatementCreator = new PreparedStatementCreator();
private final ConnectionManager connectionManager;
private final DataSource dataSource;

public JdbcTemplate(final DataSource dataSource) {
this.connectionManager = new ConnectionManager(dataSource);
this.dataSource = dataSource;
}

public <T> Optional<T> queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
return executeQuery(sql, preparedStatement -> SingleResult.convert(getQueryResult(rowMapper, preparedStatement)), args);
}

private <T> T executeQuery(final String sql, final SqlExecutor<T> executor, final Object... args) {
final Connection connection = connectionManager.getConnection();
final Connection connection = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement preparedStatement = preparedStatementCreator.createPreparedStatement(connection, sql, args)) {
return executor.execute(preparedStatement);
} catch (final SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
connectionManager.closeNotTransactional(connection);
DataSourceUtils.closeNotTransactional(dataSource, connection);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,39 @@ public abstract class DataSourceUtils {
private DataSourceUtils() {}

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
if (connection != null) {
return connection;
if (TransactionSynchronizationManager.hasResource(dataSource)) {
return TransactionSynchronizationManager.getResource(dataSource);
}

try {
connection = dataSource.getConnection();
Connection connection = dataSource.getConnection();
TransactionSynchronizationManager.bindResource(dataSource, connection);
return connection;
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
}
}

public static void releaseConnection(Connection connection, DataSource dataSource) {
public static void releaseConnection(Connection connection) {
try {
connection.close();
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");
}
}

public static void closeNotTransactional(final DataSource dataSource, final Connection connection) {
if (isTransactional(dataSource, connection)) {
return;
}
TransactionSynchronizationManager.unbindResource(dataSource);
releaseConnection(connection);
}

private static boolean isTransactional(final DataSource dataSource, final Connection connection) {
if (TransactionSynchronizationManager.hasResource(dataSource)) {
return TransactionSynchronizationManager.getResource(dataSource) == connection;
}
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

R: 매번 true를 반환하는 것 같아요! TransactionTemplate을 사용하지 않는다면 커넥션을 영원히 종료하지 않는 문제가 생길 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connection.getAutoCommit()을 통해 확인 후, close 하도록 수정했습니다!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.springframework.transaction.support;

import javax.annotation.Nonnull;
import javax.annotation.meta.When;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Nonnull(when = When.MAYBE)
public @interface Nullable {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.springframework.transaction.support;

@FunctionalInterface
public interface TransactionCallback {
public interface TransactionCallback<T> {

void execute();
@Nullable
T execute();
}
Loading
Loading