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단계] 로건(정다빈) 미션 제출합니다. #595

Merged
merged 8 commits into from
Oct 10, 2023
1 change: 1 addition & 0 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.techcourse.dao;

import com.techcourse.domain.User;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
import org.springframework.jdbc.core.JdbcTemplate;
Expand Down
47 changes: 4 additions & 43 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import com.techcourse.domain.UserHistory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
Expand All @@ -13,51 +10,15 @@ public class UserHistoryDao {

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

private final DataSource dataSource;

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

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) {
}
}
jdbcTemplate.update(sql, userHistory.getUserId(), userHistory.getAccount(), userHistory.getPassword(),
70825 marked this conversation as resolved.
Show resolved Hide resolved
userHistory.getEmail(), userHistory.getCreatedAt(), userHistory.getCreateBy());
}
}
4 changes: 2 additions & 2 deletions app/src/main/java/com/techcourse/domain/UserHistory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class UserHistory {

private Long id;

private final long userId;
private final Long userId;
private final String account;
private final String password;
private final String email;
Expand All @@ -19,7 +19,7 @@ public UserHistory(final User user, final String createBy) {
this(null, user.getId(), user.getAccount(), user.getPassword(), user.getEmail(), createBy);
}

public UserHistory(final Long id, final long userId, final String account, final String password,
public UserHistory(final Long id, final Long userId, final String account, final String password,
final String email, final String createBy) {
this.id = id;
this.userId = userId;
Expand Down
37 changes: 37 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,37 @@
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 java.util.NoSuchElementException;

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)
.orElseThrow(NoSuchElementException::new);
}

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

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

public class TxUserService implements UserService {

private final TransactionTemplate transactionTemplate;
private final UserService userService;

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

@Override
public User findById(final Long id) {
return userService.findById(id);
}
Comment on lines +17 to +19

Choose a reason for hiding this comment

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

해당 메서드는 트랜잭션을 사용하지 않은 이유가 있을까요 ?

Copy link
Member Author

Choose a reason for hiding this comment

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

팀플처럼 JPA를 사용한다면 @Transactional(readOnly=true)를 적용 했을텐데, 현재 트랜잭션 기능은 읽기 모드가 없으니 데이터를 수정해도 예외가 발생하지 않아서 아예 트랜잭션을 걸어주지 않았어요
하디는 어떻게 생각하시나요??

Choose a reason for hiding this comment

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

단순히 userDaofindById만 호출하니 상관없을 것 같긴한것같아요

하지만 조회로직이여도, 예를들어 findById를 호출 하고, 다른 곳에서 User에 대한 정보를 수정한다음 다시 findById를 호출 하였을 때, 즉 로직하나에서 findById를 두 번 호출하였을 때 조회결과가 달라질 수도 있을 것같아요.

이런 부분을 막고자 격리수준을 설정해주기 위해서 트랜잭션으로 묶여야 된다는게 제 생각입니다 ! (물론 현재는 격리수준에 대한 내용은 없지만요 !)

Copy link
Member Author

Choose a reason for hiding this comment

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

아하 동시에 findByIdUser를 수정하고, 다시 User를 조회하는 로직이 같이 실행될 때 말하시는거군요??
그런 경우를 생각하면 하디 말이 맞는 것 같아요


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

@Override
public void changePassword(final Long id, final String newPassword, final String createBy) {
transactionTemplate.execute(() -> {
userService.changePassword(id, newPassword, createBy);
return null;
});
}
}
29 changes: 4 additions & 25 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,33 +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)
.orElseThrow(IllegalArgumentException::new);
}

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 @@ -2,6 +2,7 @@

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 Down
10 changes: 7 additions & 3 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionTemplate;

@Disabled
class UserServiceTest {

private JdbcTemplate jdbcTemplate;
Expand All @@ -33,7 +33,9 @@ void setUp() {
@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var transactionTemplate = new TransactionTemplate(DataSourceConfig.getInstance());
final var appUserService = new AppUserService(userDao, userHistoryDao);
final var userService = new TxUserService(transactionTemplate, appUserService);

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -48,7 +50,9 @@ void testChangePassword() {
void testTransactionRollback() {
// 트랜잭션 롤백 테스트를 위해 mock으로 교체
final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var transactionTemplate = new TransactionTemplate(DataSourceConfig.getInstance());
final var appUserService = new AppUserService(userDao, userHistoryDao);
final var userService = new TxUserService(transactionTemplate, appUserService);

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

public class JdbcTemplate {

Expand Down Expand Up @@ -39,8 +40,8 @@ private int executeUpdate(final PreparedStatement pstmt) {
}

private <T> T execute(final Function<PreparedStatement, T> function, final String query, final Object... args) {
try (final Connection conn = dataSource.getConnection();
final PreparedStatement pstmt = getPreparedStatement(conn, query, args)) {
final Connection conn = DataSourceUtils.getConnection(dataSource);
try (final PreparedStatement pstmt = getPreparedStatement(conn, query, args)) {
return function.apply(pstmt);
} catch (SQLException e) {
log.error(e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.springframework.transaction.support;

@FunctionalInterface
public interface TransactionExecutor<T> {

T execute();
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.springframework.transaction.support;

import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.sql.DataSource;

public abstract class TransactionSynchronizationManager {
Expand All @@ -12,13 +14,26 @@ private TransactionSynchronizationManager() {
}

public static Connection getResource(DataSource key) {
return null;
Map<DataSource, Connection> connections = resources.get();
if (Objects.isNull(connections)) {
return null;
}
return connections.get(key);
}

public static void bindResource(DataSource key, Connection value) {
Map<DataSource, Connection> connections = resources.get();
if (Objects.isNull(connections)) {
connections = new HashMap<>();
resources.set(connections);
}
connections.put(key, value);
}

public static Connection unbindResource(DataSource key) {
return null;
if (Objects.isNull(resources.get())) {
return null;
}
return resources.get().remove(key);
70825 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.springframework.transaction.support;

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

public class TransactionTemplate {

private final DataSource dataSource;

public TransactionTemplate(final DataSource dataSource) {
this.dataSource = dataSource;
}

public <T> T execute(final TransactionExecutor<T> executor) {
final Connection connection = beginTransaction();
try {
final T result = executor.execute();
commit(connection);
return result;
} catch (final DataAccessException e) {
rollback(connection);
throw e;
} finally {
DataSourceUtils.releaseConnection(connection, dataSource);
TransactionSynchronizationManager.unbindResource(dataSource);
}
}

private Connection beginTransaction() {
final Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.setAutoCommit(false);
} catch (final SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return connection;
}

private void commit(final Connection connection) {
try {
connection.commit();
connection.setAutoCommit(true);
} catch (final SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}

private void rollback(final Connection connection) {
try {
connection.rollback();
connection.setAutoCommit(true);
} catch (final SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
}
4 changes: 3 additions & 1 deletion study/src/main/java/transaction/DatabasePopulatorUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public static void execute(final DataSource dataSource) {
final var sql = Files.readString(file.toPath());
connection = dataSource.getConnection();
statement = connection.createStatement();
statement.execute(sql);
for (String query : sql.split(";")) {
statement.execute(query + ";");
}
} catch (NullPointerException | IOException | SQLException e) {
log.error(e.getMessage(), e.getCause());
} finally {
Expand Down
Loading
Loading