Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 3단계] 베베(최원용) 미션 제출합니다. (#483)
Browse files Browse the repository at this point in the history
* refactor: MySqlDao -> JdbcDao

* feat: 트랜잭션 구현

* refactor: PreparedStatement 메모리 누수 방지

* feat: 트랜잭션 commit, rollback 후처리 메서드 구현

* refactor: Transactional과 TransactionManager 통합
  • Loading branch information
wonyongChoi05 authored Oct 8, 2023
1 parent 99f5548 commit 48d2d9c
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.List;
import java.util.Optional;

public class MySqlUserDao implements UserDao {
public class JdbcUserDao implements UserDao {

private static final RowMapper<User> USER_ROW_MAPPER = resultSet -> {
final long userId = resultSet.getLong("id");
Expand All @@ -19,7 +19,7 @@ public class MySqlUserDao implements UserDao {

private final JdbcTemplate jdbcTemplate;

public MySqlUserDao(final JdbcTemplate jdbcTemplate) {
public JdbcUserDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

Expand Down
34 changes: 34 additions & 0 deletions app/src/main/java/com/techcourse/dao/JdbcUserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.techcourse.dao;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JdbcUserHistoryDao implements UserHistoryDao {

private final JdbcTemplate jdbcTemplate;

public JdbcUserHistoryDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

@Override
public void log(final UserHistory userHistory) {
final var sql = "insert into user_history (user_id, account, password, email, created_at, created_by) values (?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
userHistory.getEmail(),
userHistory.getCreatedAt(),
userHistory.getCreateBy()
);
}

}
57 changes: 2 additions & 55 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,9 @@
package com.techcourse.dao;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface UserHistoryDao {

public class UserHistoryDao {
void log(final UserHistory userHistory);

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

private final DataSource dataSource;

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

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

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) {}
}
}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/techcourse/domain/UserHistory.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.techcourse.domain;

import java.sql.Connection;
import java.time.LocalDateTime;

public class UserHistory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.techcourse.dao.UserDao;
import com.techcourse.domain.User;

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

public class UserRepository {
Expand Down
24 changes: 16 additions & 8 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package com.techcourse.service;

import com.techcourse.dao.UserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.dao.JdbcUserHistoryDao;
import com.techcourse.domain.User;
import com.techcourse.domain.UserHistory;
import com.techcourse.repository.UserRepository;
import org.springframework.jdbc.transaction.TransactionManager;

import javax.sql.DataSource;

public class UserService {

private final TransactionManager transactionManager;
private final UserRepository userRepository;
private final UserHistoryDao userHistoryDao;
private final JdbcUserHistoryDao jdbcUserHistoryDao;

public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) {
public UserService(final DataSource dataSource, final UserDao userDao, final JdbcUserHistoryDao jdbcUserHistoryDao) {
this.transactionManager = new TransactionManager(dataSource);
this.userRepository = new UserRepository(userDao);
this.userHistoryDao = userHistoryDao;
this.jdbcUserHistoryDao = jdbcUserHistoryDao;
}

public User findById(final long id) {
Expand All @@ -25,9 +30,12 @@ public void insert(final User user) {
}

public void changePassword(final long id, final String newPassword, final String createBy) {
final var user = findById(id);
user.changePassword(newPassword);
userRepository.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
transactionManager.execute(connection -> {
final var user = findById(id);
user.changePassword(newPassword);
userRepository.update(user);
jdbcUserHistoryDao.log(new UserHistory(user, createBy));
return null;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

import static org.assertj.core.api.Assertions.assertThat;

class MySqlUserDaoTest {
class JdbcUserDaoTest {

private UserDao userDao;

@BeforeEach
void setup() {
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
userDao = new MySqlUserDao(jdbcTemplate);
userDao = new JdbcUserDao(jdbcTemplate);
final User user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.techcourse.service;

import com.techcourse.dao.UserHistoryDao;
import com.techcourse.dao.JdbcUserHistoryDao;
import com.techcourse.domain.UserHistory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

public class MockUserHistoryDao extends UserHistoryDao {
public class MockJdbcUserHistoryDao extends JdbcUserHistoryDao {

public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) {
public MockJdbcUserHistoryDao(final JdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
}

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

import com.techcourse.config.DataSourceConfig;
import com.techcourse.dao.MySqlUserDao;
import com.techcourse.dao.UserHistoryDao;
import com.techcourse.dao.JdbcUserDao;
import com.techcourse.dao.JdbcUserHistoryDao;
import com.techcourse.dao.UserDao;
import com.techcourse.domain.User;
import com.techcourse.support.jdbc.init.DatabasePopulatorUtils;
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;
private MySqlUserDao userDao;
private UserDao userDao;

@BeforeEach
void setUp() {
this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
this.userDao = new MySqlUserDao(jdbcTemplate);
this.userDao = new JdbcUserDao(jdbcTemplate);

DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
final var user = new User("gugu", "password", "[email protected]");
Expand All @@ -32,8 +31,8 @@ void setUp() {

@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userHistoryDao = new JdbcUserHistoryDao(jdbcTemplate);
final var userService = new UserService(DataSourceConfig.getInstance(), userDao, userHistoryDao);

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

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.springframework.jdbc.core;

import org.springframework.jdbc.transaction.TransactionManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
Expand All @@ -12,10 +14,10 @@

public class JdbcTemplate {

private final DataSource dataSource;
private final TransactionManager transactionManager;

public JdbcTemplate(final DataSource dataSource) {
this.dataSource = dataSource;
this.transactionManager = new TransactionManager(dataSource);
}

public int update(final String sql, final Object... conditions) {
Expand All @@ -41,10 +43,10 @@ private <T> T getRowByQuery(final PreparedStatement preparedStatement, final Row
}

private <T> T getResult(final PreparedStatementExecutor<T> executor, final String sql, final Object... conditions) {
PreparedStatement preparedStatement = null;
try {
Connection connection = dataSource.getConnection();
preparedStatement = connection.prepareStatement(sql);
Connection connection = transactionManager.getConnection();
try (
PreparedStatement preparedStatement = connection.prepareStatement(sql);
) {
setConditions(preparedStatement, conditions);
return executor.query(preparedStatement);
} catch (SQLException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.springframework.jdbc.transaction;

import java.sql.Connection;

public interface TransactionExecutor<T> {

T execute(final Connection connection);

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class TransactionManager {

private static final ThreadLocal<Connection> connectionInThread = new ThreadLocal<>();

private final DataSource dataSource;

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

public <T> T execute(final TransactionExecutor<T> transactionExecutor) {
try {
Connection connection = begin();
T response = transactionExecutor.execute(connection);
commit();
return response;
} catch (RuntimeException e) {
rollback();
throw e;
}
}

public Connection begin() {
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new RuntimeException(e);
}
connectionInThread.set(connection);
return connection;
}

public void commit() {
Connection connection = getConnection();
try {
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
init(connection);
}

public void rollback() {
Connection connection = getConnection();
try {
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
init(connection);
}

private void init(final Connection connection) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
connectionInThread.remove();
}

public Connection getConnection() {
if (connectionInThread.get() == null) {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return connectionInThread.get();
}

}

0 comments on commit 48d2d9c

Please sign in to comment.