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단계] 이레(이다형) 미션 제출합니다. #553

Merged
merged 19 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f82dc21
feat: TransactionSynchronizationManager 리소스 bind, unbind, get 기능 추가
zillionme Oct 8, 2023
be773f8
feat: 커넥션 해제 시, 쓰레드 로컬에 저장된 커넥션 제거
zillionme Oct 8, 2023
db74c8e
refactor: 쓰레드 로컬을 통한 커넥션 동기화 적용
zillionme Oct 8, 2023
d6af471
refactor: UserService 인터페이스 생성
zillionme Oct 8, 2023
a308b22
feat: 결과 있는 트랜잭션 실행 기능 추가
zillionme Oct 8, 2023
cbeda5a
feat: AppUserService의 트랜잭션 프록시 클래스 추가
zillionme Oct 8, 2023
df0bddc
test: 테스트 수정
zillionme Oct 8, 2023
b8bcbe5
chore: 예외 클래스 패키지 이동
zillionme Oct 8, 2023
3b7869f
test: aop 테스트
zillionme Oct 9, 2023
9470118
chore: final 키워드 수정 및 필요없는 메서드 지우기
zillionme Oct 9, 2023
a7b16bb
chore: 필요없는 클래스 삭제
zillionme Oct 10, 2023
046b3c8
refactor: 커넥션의 트랜잭션 active 상태에 따른 커넥션 해제 방법 수정
zillionme Oct 10, 2023
8cf6bda
refactor: TransactionManager 트랜잭션 시작과 종료 관리 기능 추가
zillionme Oct 10, 2023
2bfef4b
refactor: JdbcTemplate 커넥션 해제 기능 추가
zillionme Oct 10, 2023
8cf2002
fix: 트랜잭션 동기화 매니저 unbind에 스레드 로컬에 대한 자원 해제 기능 추가
zillionme Oct 11, 2023
237a3a3
refactor: 트랜잭션 매니저에서 발생하는 SQL예외 처리방법 수정
zillionme Oct 11, 2023
6d05798
refactor: 중복 코드 제거
zillionme Oct 11, 2023
b2a6103
chore: 주석 제거
zillionme Oct 11, 2023
22da3a4
refaco: 트랜잭션 동기화 매니저 unbind에 스레드 로컬에 대한 자원 해제 기능 추가
zillionme Oct 11, 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
6 changes: 0 additions & 6 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.Connection;
import java.util.List;

public class UserDao {
Expand All @@ -31,11 +30,6 @@ public void update(final User user) {
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public void update(final Connection conn, final User user) {
final var sql = "update users set account = ?, password = ?, email = ? where id = ?";
jdbcTemplate.update(conn, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public List<User> findAll() {
final var sql = "select * from users";
return jdbcTemplate.query(sql, USER_MAPPER);
Expand Down
16 changes: 0 additions & 16 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import com.techcourse.domain.UserHistory;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;

public class UserHistoryDao {

private final JdbcTemplate jdbcTemplate;
Expand All @@ -25,18 +23,4 @@ public void log(final UserHistory userHistory) {
);
}


public void log(final Connection conn, final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(
conn,
sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
userHistory.getEmail(),
userHistory.getCreatedAt(),
userHistory.getCreateBy()
);
}
}
32 changes: 32 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,32 @@
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);
}

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));
}
}
31 changes: 31 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,31 @@
package com.techcourse.service;

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

public class TxUserService implements UserService {

private final TransactionManager transactionManager;
private final UserService userService;

public TxUserService(final TransactionManager transactionManager, final UserService userService) {
this.transactionManager = transactionManager;
this.userService = userService;
}

@Override
public User findById(final long id) {
return transactionManager.executeWithResult(() -> userService.findById(id));
}

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


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

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.TransactionManager;

import java.sql.Connection;
public interface UserService {

public class UserService {
User findById(final long id);

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
void insert(final User user);

private final TransactionManager transactionManager;

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao, final TransactionManager transactionManager) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
this.transactionManager = transactionManager;
}

public User findById(final long id) {
return userDao.findById(id);
}

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

public void changePassword(final long id, final String newPassword, final String createBy) {
transactionManager.execute((conn) -> changePasswordInTransaction(id, newPassword, createBy, conn));
}

private void changePasswordInTransaction(final long id, final String newPassword, final String createBy, final Connection conn) {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(conn, user);
userHistoryDao.log(conn, new UserHistory(user, createBy));
}
void changePassword(final long id, final String newPassword, final String createBy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.techcourse.domain.UserHistory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.Connection;

public class MockUserHistoryDao extends UserHistoryDao {

Expand All @@ -16,9 +15,4 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
public void log(final UserHistory userHistory) {
throw new DataAccessException();
}

@Override
public void log(final Connection conn, final UserHistory userHistory) {
throw new DataAccessException();
}
}
9 changes: 7 additions & 2 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao, new TransactionManager(dataSource));
final var userService = new TxUserService(new TransactionManager(dataSource), new AppUserService(userDao, userHistoryDao));

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

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
26 changes: 5 additions & 21 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.exception.EmptyResultDataAccessException;
import org.springframework.jdbc.exception.IncorrectResultSizeDataAccessException;

Expand All @@ -28,10 +29,6 @@ public void update(final String sql, final Object... args) {
execute(sql, args, PreparedStatement::executeUpdate);
}

public void update(final Connection conn, final String sql, final Object... args) {
execute(conn, sql, args, PreparedStatement::executeUpdate);
}

public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... args) {
List<T> result = query(sql, rowMapper, args);
if (result.size() > 1) {
Expand All @@ -55,23 +52,8 @@ public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final O
}

private <T> T execute(final String sql, final Object[] args, final PreparedStatementFunction<T> preparedStatementExecutor) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = conn.prepareStatement(sql)) {
log.debug("query : {}", sql);

for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
return preparedStatementExecutor.execute(pstmt);
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
}
}

private <T> T execute(final Connection conn, final String sql, final Object[] args, final PreparedStatementFunction<T> preparedStatementExecutor) {
try {
final PreparedStatement pstmt = conn.prepareStatement(sql);
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement pstmt = conn.prepareStatement(sql)) {
log.debug("query : {}", sql);

for (int i = 0; i < args.length; i++) {
Expand All @@ -81,6 +63,8 @@ private <T> T execute(final Connection conn, final String sql, final Object[] ar
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new DataAccessException(e);
} finally {
DataSourceUtils.releaseConnection(conn, dataSource);
Comment on lines +66 to +67
Copy link

Choose a reason for hiding this comment

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

여기서 커넥션을 close하는 로직을 추가해주셨는데 이유가 있을까요?
제가 생각했을 때 JdbcTemplate에서는 커넥션을 가져오기는 하지만,
close할 때는 TransactionManager클래스의 executeWithResult메서드가 끝나는 시점에 해준다고 이해를 했어요.
따라서 결국 비즈니스 로직이 실행되고 마지막에 단 한번만 호출되면 된다고 생각했습니다.

Copy link
Author

@zillionme zillionme Oct 11, 2023

Choose a reason for hiding this comment

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

만약 트랜잭션이 없는 커넥션의 경우에는 (= 서비스에서 트랜잭션 매니저 클래스를 사용하지 않고, 매번 새로운 커넥션을 생성하는 방식으로 쿼리작업이 이뤄지는 경우)
커넥션이 해제되지 않기 때문입니다.

위와 같이 작업할 경우,
JdbcTemplate에서 커넥션을 생성하는 것(트랜잭션이 없는 커넥션 = 커넥션 홀더 내의 커넥션의 트랜잭션활성화 상태가 false)과
TransactionManager에서 커넥션을 생성하는 것(트랜잭션이 있는 커넥션 = 커넥션 홀더 내의 커넥션의 트랜잭션활성화 상태가 true)이 분리됩니다.

}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.springframework.jdbc.datasource;

import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.exception.CannotGetJdbcConnectionException;
import org.springframework.transaction.support.ConnectionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;
Expand All @@ -12,23 +13,32 @@ public abstract class DataSourceUtils {

private DataSourceUtils() {}

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

try {
connection = dataSource.getConnection();
TransactionSynchronizationManager.bindResource(dataSource, connection);
Connection connection = dataSource.getConnection();
TransactionSynchronizationManager.bindResource(dataSource, new ConnectionHolder(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(final Connection connection, final DataSource dataSource) {
if(connection == null) {
return;
}
ConnectionHolder connectionHolder = TransactionSynchronizationManager.getResource(dataSource);
boolean isConnectionActive = connectionHolder.isConnectionTransactionActive();
if(isConnectionActive) {
return;
}
try {
TransactionSynchronizationManager.unbindResource(dataSource);
connection.close();
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to close JDBC Connection");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.springframework.jdbc;
package org.springframework.jdbc.exception;

import java.sql.SQLException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.springframework.transaction.support;

import java.sql.Connection;

public class ConnectionHolder {

private final Connection connection;

private boolean isConnectionTransactionActive;
Copy link

Choose a reason for hiding this comment

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

생성자에서도 따로 값을 받지 않고 있는데, default값을 설정해주는 것이 어떨까요?
지금과 같은 경우 setter를 사용하지 않으면 값이 할당되지 않을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

boolean은 기본형이기 때문에 false가 기본값으로 설정되는 것으로 알고 있습니다!

Copy link

Choose a reason for hiding this comment

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

엇 그러네요.. 죄송합니다..ㅎㅎ

제안드리고 싶었던 점은 기본 값을 false로 명시해두고,
setter(false)를 이용하지 않는 방법이 더 좋아보여서 말씀드렸습니다!

Copy link
Author

Choose a reason for hiding this comment

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

앗 그렇게 수정하겠숩니다!


public ConnectionHolder(final Connection connection) {
this.connection = connection;
}

public void setConnectionTransactionActive(boolean isActive) {
this.isConnectionTransactionActive = isActive;
}

public Connection getConnection() {
return connection;
}

public boolean isConnectionTransactionActive() {
return isConnectionTransactionActive;
}
}

This file was deleted.

Loading
Loading