Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 3,4단계] 도치(김동흠) 미션 제출합니다. (#529)
Browse files Browse the repository at this point in the history
* feat:비밀번호 변경과 로깅에 대한 트랜잭션 구현

* feat:Transaction synchronization 적용

* refactor:dataSourceUtils로 conection받아 트랜잭션 처리하도록 수정

* refactor:TxUserService로 트랜잭션 로직 분리

* feat:deleteAll과 insert한 터플id 리턴하는 메서드 구현

* refactor:중복된 커넥션 close삭제

* feat:유저의 findById,insert메서드에 트랜잭션 적용

* refactor(TransactionSynchronizationManager):key에 맞는 값 없을 시 null반환함을 명시하도록 수정

* refactor:unbind시 connection close확인 후 unbind하도록 수정
  • Loading branch information
hum02 authored Oct 12, 2023
1 parent f0bdd7a commit 6a5bbff
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 100 deletions.
14 changes: 10 additions & 4 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public UserDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void insert(final User user) {
public long insert(final User user) {
final String sql = "insert into users (account, password, email) values (?, ?, ?)";

jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail());
return jdbcTemplate.updateAndReturnKey(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final User user) {
Expand All @@ -36,6 +36,12 @@ public void update(final User user) {
jdbcTemplate.update(sql, user.getAccount(), user.getPassword(), user.getEmail(), user.getId());
}

public void deleteAll() {
final String sql = "delete from users";

jdbcTemplate.update(sql);
}

public List<User> findAll() {
final String sql = "select id, account, password, email from users";

Expand All @@ -57,9 +63,9 @@ public Optional<User> findById(final Long id) {
return jdbcTemplate.queryForObject(sql, rowMapper, id);
}

public List<User> findByAccount(final String account) {
public Optional<User> findByAccount(final String account) {
final String sql = "select id, account, password, email from users where users.account = ?";

return jdbcTemplate.query(sql, rowMapper, account);
return jdbcTemplate.queryForObject(sql, rowMapper, account);
}
}
49 changes: 9 additions & 40 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,31 @@
package com.techcourse.dao;

import com.techcourse.domain.UserHistory;
import org.springframework.jdbc.core.JdbcTemplate;
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 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 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());
}
}
36 changes: 36 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,36 @@
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(UserDao userDao, UserHistoryDao userHistoryDao) {
this.userDao = userDao;
this.userHistoryDao = userHistoryDao;
}

@Override
public User findById(final long id) {
return userDao.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 아이디의 회원이 존재하지 않습니다."));
}

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

import com.techcourse.config.DataSourceConfig;
import com.techcourse.domain.User;
import org.springframework.transaction.support.TransactionHandler;

public class TxUserService implements UserService {

private final UserService userService;
private final TransactionHandler transactionHandler;

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

@Override
public User findById(long id) {
return transactionHandler.handle(() -> userService.findById(id));
}

@Override
public void insert(User user) {
transactionHandler.handle(() -> userService.insert(user));
}

@Override
public void changePassword(final long id, final String newPassword, final String createBy) {
transactionHandler.handle(() -> userService.changePassword(id, newPassword, createBy));
}
}

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(() -> new IllegalArgumentException("해당 아이디의 회원이 존재하지 않습니다."));
}

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);
}
2 changes: 1 addition & 1 deletion app/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
create table if not exists users (
id bigint auto_increment,
account varchar(100) not null,
account varchar(100) not null unique,
password varchar(100) not null,
email varchar(100) not null,
primary key(id)
Expand Down
21 changes: 10 additions & 11 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;

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

class UserDaoTest {

private UserDao userDao;
private long userId;

@BeforeEach
void setup() {
DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());

userDao = new UserDao(DataSourceConfig.getInstance());
userDao.deleteAll();
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
userId = userDao.insert(user);
}

@Test
Expand All @@ -32,26 +31,26 @@ void findAll() {

@Test
void findById() {
final var user = userDao.findById(1L).get();
final var user = userDao.findById(userId).get();

assertThat(user.getAccount()).isEqualTo("gugu");
}

@Test
void findByAccount() {
final var account = "gugu";
List<User> users = userDao.findByAccount(account);
User user = userDao.findByAccount(account).get();

assertThat(users).extracting("account").containsOnly("gugu");
assertThat(user.getAccount()).isEqualTo("gugu");
}

@Test
void insert() {
final var account = "insert-gugu";
final var user = new User(account, "password", "[email protected]");
userDao.insert(user);
long insertedId = userDao.insert(user);

final var actual = userDao.findById(2L).get();
final var actual = userDao.findById(insertedId).get();

assertThat(actual.getAccount()).isEqualTo(account);
assertThat(actual.getEmail()).isEqualTo("[email protected]");
Expand All @@ -60,12 +59,12 @@ void insert() {
@Test
void update() {
final var newPassword = "password99";
final var user = userDao.findById(1L).get();
final var user = userDao.findById(userId).get();
user.changePassword(newPassword);

userDao.update(user);

final var actual = userDao.findById(1L).get();
final var actual = userDao.findById(userId).get();

assertThat(actual.getPassword()).isEqualTo(newPassword);
}
Expand Down
29 changes: 17 additions & 12 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,43 @@
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;
private UserDao userDao;
private long userId;

@BeforeEach
void setUp() {
this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance());
final var dataSource = DataSourceConfig.getInstance();
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.userDao = new UserDao(jdbcTemplate);

DatabasePopulatorUtils.execute(DataSourceConfig.getInstance());
userDao.deleteAll();
final var user = new User("gugu", "password", "[email protected]");
userDao.insert(user);
userId = userDao.insert(user);
}

@Test
void testChangePassword() {
final var userHistoryDao = new UserHistoryDao(jdbcTemplate);
final var userService = new UserService(userDao, userHistoryDao);
final var userService = new TxUserService(new AppUserService(userDao, userHistoryDao));

final var newPassword = "qqqqq";
final var createBy = "gugu";
userService.changePassword(1L, newPassword, createBy);

final var actual = userService.findById(1L);
userService.changePassword(userId, newPassword, createBy);

final var actual = userService.findById(userId);

assertThat(actual.getPassword()).isEqualTo(newPassword);
}
Expand All @@ -48,15 +50,18 @@ 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";
// 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다.
assertThrows(DataAccessException.class,
() -> userService.changePassword(1L, newPassword, createBy));
() -> userService.changePassword(userId, newPassword, createBy));

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

assertThat(actual.getPassword()).isNotEqualTo(newPassword);
}
Expand Down
Loading

0 comments on commit 6a5bbff

Please sign in to comment.