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 라이브러리 구현하기 - 3,4단계] 스플릿(박상현) 미션 제출합니다. #537

Merged
merged 7 commits into from
Oct 17, 2023
56 changes: 12 additions & 44 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,30 @@
package com.techcourse.dao;

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

import java.time.LocalDateTime;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;

public class UserHistoryDao {

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

private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;

public UserHistoryDao(final DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.dataSource = null;
this.jdbcTemplate = jdbcTemplate;
}

public void log(final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";

Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);

log.debug("query : {}", sql);

pstmt.setLong(1, userHistory.getUserId());
pstmt.setString(2, userHistory.getAccount());
pstmt.setString(3, userHistory.getPassword());
pstmt.setString(4, userHistory.getEmail());
pstmt.setObject(5, userHistory.getCreatedAt());
pstmt.setString(6, userHistory.getCreateBy());
pstmt.executeUpdate();
} catch (SQLException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException ignored) {}

try {
if (conn != null) {
conn.close();
}
} catch (SQLException ignored) {}
}
final long userId = userHistory.getUserId();
final String account = userHistory.getAccount();
final String password = userHistory.getPassword();
final String email = userHistory.getEmail();
final LocalDateTime createdAt = userHistory.getCreatedAt();
final String createBy = userHistory.getCreateBy();
jdbcTemplate.update(sql, userId, account, password, email, createdAt, createBy);
}
}
35 changes: 35 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,35 @@
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;
}

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

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

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

import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.jdbc.core.error.SqlExceptionConverter;

public abstract class TransactionService<T> {
Copy link

Choose a reason for hiding this comment

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

이 친구가 transaction 시작과 끝 관련 책임을 가지는 것도 좋아보이네요!! 어떻게 생각하시나요?


protected T appService;

protected TransactionService(final T appService) {
this.appService = appService;
}

protected void rollback(final Connection connection) {
Copy link

Choose a reason for hiding this comment

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

rollback() 만 정의하신 이유가 있을까요??

Copy link
Author

Choose a reason for hiding this comment

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

try 문안에 try 문이 있는게 개인적으로 보기 불편하더라구요..ㅎㅎ
이 메서드도 TransactionTemplate 객체로 옮겨주었습니다!!😊

try {
connection.rollback();
} catch (SQLException e) {
throw SqlExceptionConverter.convert(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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 java.sql.Connection;
import java.sql.SQLException;
import org.springframework.jdbc.core.error.SqlExceptionConverter;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class TransactionUserService extends TransactionService<UserService> implements UserService {

public TransactionUserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
super(new AppUserService(userDao, userHistoryDao));
}

public User findById(final long id) {
final Connection connection = DataSourceUtils.getConnection(DataSourceConfig.getInstance());
final User user = appService.findById(id);
DataSourceUtils.releaseConnection(connection, DataSourceConfig.getInstance());
return user;
}

public void insert(final User user) {
final Connection connection = DataSourceUtils.getConnection(DataSourceConfig.getInstance());
try {
connection.setAutoCommit(false);
appService.insert(user);
Copy link

Choose a reason for hiding this comment

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

appService 호출 부분을 제외하고 중복이라 이 부분 제거하면 매우 깔끔해지겠네요!

TransactionSynchronizationManager 에서 startTransaction() 메서드를 만든다거나, 혹은 템플릿 콜백 or 메서드 패턴을 활용해봐도 좋을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

좋은 것 같습니다!! 반영했습니다!!👍

connection.commit();
} catch (SQLException e) {
rollback(connection);
throw SqlExceptionConverter.convert(e);
} catch (Exception e) {
rollback(connection);
throw e;
} finally {
DataSourceUtils.releaseConnection(connection, DataSourceConfig.getInstance());
}
}

public void changePassword(final long id, final String newPassword, final String createBy) {
final Connection connection = DataSourceUtils.getConnection(DataSourceConfig.getInstance());
try {
connection.setAutoCommit(false);
appService.changePassword(id, newPassword, createBy);
connection.commit();
} catch (SQLException e) {
rollback(connection);
throw SqlExceptionConverter.convert(e);
} catch (Exception e) {
rollback(connection);
throw e;
} finally {
DataSourceUtils.releaseConnection(connection, DataSourceConfig.getInstance());
}
}
}
28 changes: 4 additions & 24 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,32 +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;

public class UserService {
public interface UserService {

private final UserDao userDao;
private final UserHistoryDao userHistoryDao;
User findById(final long id);

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
}
void insert(final User user);

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));
}
void changePassword(final long id, final String newPassword, final String createBy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.JdbcTemplateBase;
import org.springframework.jdbc.core.error.TableNotFoundSqlRuntimeException;
import org.springframework.jdbc.core.error.exception.ColumnSqlRuntimeException;
import org.springframework.jdbc.core.error.exception.DataConversionSqlRuntimeException;
import org.springframework.jdbc.core.error.exception.MethodNotAllowedSqlRuntimeException;
import org.springframework.jdbc.core.error.exception.SyntaxSqlRuntimeException;
import org.springframework.jdbc.core.error.exception.TableNotFoundSqlRuntimeException;
import org.springframework.jdbc.core.mapper.ResultSetObjectMapper;

class SqlRuntimeExceptionTest {
Expand Down Expand Up @@ -61,7 +61,7 @@ void executeQuery_insert() {
//when
//then
assertThatThrownBy(
() -> jdbcTemplateExecutionBase.executionBaseWithNonReturn(sql, PreparedStatement::executeQuery, true)
() -> jdbcTemplateExecutionBase.executionBaseWithNonReturn(sql, PreparedStatement::executeQuery)
).isInstanceOf(MethodNotAllowedSqlRuntimeException.ExecuteQuerySqlRuntimeException.class);
}

Expand Down
18 changes: 8 additions & 10 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package com.techcourse.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

@Disabled
class UserServiceTest {

private JdbcTemplate jdbcTemplate;
Expand All @@ -33,7 +31,7 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new TransactionUserService(userDao, userHistoryDao);

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -48,13 +46,13 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new TransactionUserService(userDao, userHistoryDao);

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

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

Expand Down
41 changes: 6 additions & 35 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,30 @@ public JdbcTemplate(final DataSource dataSource) {
}

public <T> T executeQueryForObject(final String sql, final ResultSetObjectMapper<T> mapper) {
return super.executeQueryForObjectBase(sql, mapper, new Object[]{}, TRANSACTION_ENABLE);
return super.executeQueryForObjectBase(sql, mapper, new Object[]{});
}

public <T> T executeQueryForObject(final String sql,
final ResultSetObjectMapper<T> mapper,
final Object... params) {
return super.executeQueryForObjectBase(sql, mapper, params, TRANSACTION_ENABLE);
}

public <T> T executeQueryForObjectWithoutTransaction(final String sql, final ResultSetObjectMapper<T> mapper) {
return super.executeQueryForObjectBase(sql, mapper, new Object[]{}, TRANSACTION_ENABLE);
}

public <T> T executeQueryForObjectWithoutTransaction(final String sql,
final ResultSetObjectMapper<T> mapper,
final Object... params) {
return super.executeQueryForObjectBase(sql, mapper, params, TRANSACTION_DISABLE);
return super.executeQueryForObjectBase(sql, mapper, params);
}

public <T> List<T> executeQueryForObjects(final String sql, final ResultSetObjectMapper<T> mapper) {
return super.executeQueryForObjectsBase(sql, mapper, new Object[]{}, TRANSACTION_ENABLE);
return super.executeQueryForObjectsBase(sql, mapper, new Object[]{});
}

public <T> List<T> executeQueryForObjects(final String sql,
final ResultSetObjectMapper<T> mapper,
final Object... params) {
return super.executeQueryForObjectsBase(sql, mapper, params, TRANSACTION_ENABLE);
}

public <T> List<T> executeQueryForObjectsWithoutTransaction(final String sql,
final ResultSetObjectMapper<T> mapper) {
return super.executeQueryForObjectsBase(sql, mapper, new Object[]{}, TRANSACTION_DISABLE);
}

public <T> List<T> executeQueryForObjectsWithoutTransaction(final String sql,
final ResultSetObjectMapper<T> mapper,
final Object... params) {
return super.executeQueryForObjectsBase(sql, mapper, params, TRANSACTION_DISABLE);
return super.executeQueryForObjectsBase(sql, mapper, params);
}

public void update(final String sql) {
updateBase(sql, new Object[]{}, TRANSACTION_ENABLE);
updateBase(sql, new Object[]{});
}

public void update(final String sql, Object... params) {
updateBase(sql, params, TRANSACTION_ENABLE);
}

public void updateWithoutTransaction(final String sql) {
updateBase(sql, new Object[]{}, TRANSACTION_DISABLE);
}

public void updateWithoutTransaction(final String sql, Object... params) {
updateBase(sql, params, TRANSACTION_DISABLE);
updateBase(sql, params);
}
}
Loading
Loading