Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 4단계] 디노(신종화) 미션 제출합니다. (#543)
Browse files Browse the repository at this point in the history
* refactor: Transaction synchronization 적용

* refactor: Transaction Service 추상화

* refactor: 패키지 구조 변경 및 Executor 추상화

* refactor: null 처리 일관성있게 변경

* refactor: resource 미리 초기화 및 unbind시 remove 하도록 변경

* refactor: query와 queryForObject에서도 동일한 connection을 사용하도록 변경

* refactor: 네이밍 변경
  • Loading branch information
jjongwa authored Oct 10, 2023
1 parent e7816c9 commit 1cd5e3a
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 97 deletions.
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
@@ -1,7 +1,6 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
Expand Down Expand Up @@ -42,11 +41,6 @@ public void update(final User user) {
jdbcTemplate.execute(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.execute(conn, sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public List<User> findAll() {
final var sql = "select id, account, password, email from users";
return jdbcTemplate.query(sql, USER_ROW_MAPPER);
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.techcourse.dao;

import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -21,7 +20,7 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void log(final Connection conn, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
final var sql = "insert into user_history ("
+ "user_id, "
+ "account, "
Expand All @@ -33,7 +32,7 @@ public void log(final Connection conn, final UserHistory userHistory) {

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

jdbcTemplate.execute(conn, sql,
jdbcTemplate.execute(sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
Expand Down
55 changes: 4 additions & 51 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,59 +1,12 @@
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 java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

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 DataSource dataSource = DataSourceConfig.getInstance();
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);

final User user = findById(id);
user.changePassword(newPassword);

userDao.update(conn, user);
userHistoryDao.log(conn, new UserHistory(user, createBy));
conn.commit();
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException exception) {
throw new DataAccessException(exception);
}
} finally {
try {
conn.close();
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
}
void changePassword(final long id, final String newPassword, final String createBy);
}
36 changes: 36 additions & 0 deletions app/src/main/java/com/techcourse/service/impl/AppUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.techcourse.service.impl;

import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import com.techcourse.service.UserService;

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(long id) {
return userDao.findById(id);
}

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

@Override
public void changePassword(long id, String newPassword, String createBy) {
final var user = findById(id);
user.changePassword(newPassword);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
}
}
35 changes: 35 additions & 0 deletions app/src/main/java/com/techcourse/service/impl/TxUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.techcourse.service.impl;

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import com.techcourse.service.UserService;
import org.springframework.transaction.support.ServiceExecutor;

public class TxUserService implements UserService {

private final UserService userService;
private final ServiceExecutor serviceExecutor;

public TxUserService(final UserService userService) {
this.userService = userService;
this.serviceExecutor = new ServiceExecutor(DataSourceConfig.getInstance());
}

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

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

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
serviceExecutor.execute(() -> {
userService.changePassword(id, newPassword, createBy);
return null;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.techcourse.dao.UserHistoryDao;
import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

Expand All @@ -13,7 +12,7 @@ public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
}

@Override
public void log(final Connection conn, final UserHistory userHistory) {
public void log(final UserHistory userHistory) {
throw new DataAccessException();
}
}
20 changes: 12 additions & 8 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
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.service.impl.AppUserService;
import com.techcourse.service.impl.TxUserService;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

class UserServiceTest {

Expand All @@ -32,7 +33,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 +48,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);

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

abstract class AutoClosableTemplate {

Expand All @@ -22,16 +23,7 @@ abstract class AutoClosableTemplate {
}

public void execute(final String sql, final Object... objects) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = preparedStatementAndSetValue(conn, sql, objects)
) {
commandQuery(pstmt);
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

public void execute(final Connection conn, final String sql, final Object... objects) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement pstmt = preparedStatementAndSetValue(conn, sql, objects)) {
commandQuery(pstmt);
} catch (SQLException e) {
Expand All @@ -40,9 +32,8 @@ public void execute(final Connection conn, final String sql, final Object... obj
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final Object... objects) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = preparedStatementAndSetValue(conn, sql, objects)
) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement pstmt = preparedStatementAndSetValue(conn, sql, objects)) {
final ResultSet rs = pstmt.executeQuery();
return queryAll(rs, rowMapper);
} catch (SQLException e) {
Expand All @@ -52,9 +43,8 @@ public <T> List<T> query(final String sql, final RowMapper<T> rowMapper, final O

@Nullable
public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper, final Object... objects) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = preparedStatementAndSetValue(conn, sql, objects)
) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement pstmt = preparedStatementAndSetValue(conn, sql, objects)) {
final ResultSet rs = pstmt.executeQuery();
return queryForOne(rs, rowMapper);
} catch (SQLException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package org.springframework.jdbc.datasource;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.transaction.support.TransactionSynchronizationManager;

// 4단계 미션에서 사용할 것
public abstract class DataSourceUtils {

private DataSourceUtils() {}
private DataSourceUtils() {
}

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
Connection connection = TransactionSynchronizationManager.getResource(dataSource);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.springframework.transaction.support;

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.DataSourceUtils;

public class ServiceExecutor {

private final DataSource dataSource;

public ServiceExecutor(DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> T execute(TransactionTemplate<T> executor) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try {
conn.setAutoCommit(false);
final T result = executor.execute();
conn.commit();
return result;
} catch (Exception e) {
rollback(conn);
throw new DataAccessException(e);
} finally {
DataSourceUtils.releaseConnection(conn, dataSource);
TransactionSynchronizationManager.unbindResource(dataSource);
}
}

private void rollback(final Connection conn) {
try {
conn.rollback();
} catch (SQLException exception) {
throw new DataAccessException(exception);
}
}
}
Loading

0 comments on commit 1cd5e3a

Please sign in to comment.