Skip to content

Commit

Permalink
[JDBC 라이브러리 구현하기 - 3단계] 연어(황재현) 미션 제출합니다. (#461)
Browse files Browse the repository at this point in the history
* feat: 유저 비밀번호 변경 기능 구현

* test: AOP 미션 0단계

* test: AOP 미션 1단계

* test: AOP 미션 2단계

* style: 메서드 위치 변경

* fix: 롤백되지 않는 현상 해결
  • Loading branch information
nuyh99 authored Oct 9, 2023
1 parent e7c1eac commit 367b696
Show file tree
Hide file tree
Showing 23 changed files with 325 additions and 83 deletions.
5 changes: 3 additions & 2 deletions app/src/main/java/com/techcourse/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.jdbc.core.RowMapper;

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

public class UserDao {
Expand All @@ -31,11 +32,11 @@ public void insert(final User user) {
jdbcTemplate.executeUpdate(sql, user.getAccount(), user.getPassword(), user.getEmail());
}

public void update(final User user) {
public void update(final Connection connection, final User user) {
final String sql = "UPDATE users SET account = ?, password = ?, email = ? WHERE id = ?";

final long userId = user.getId();
jdbcTemplate.executeUpdate(sql, user.getAccount(), user.getPassword(), user.getEmail(), userId);
jdbcTemplate.executeUpdate(connection, sql, user.getAccount(), user.getPassword(), user.getEmail(), userId);
}

public List<User> findAll() {
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/com/techcourse/dao/UserHistoryDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.jdbc.core.JdbcTemplate;

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

public class UserHistoryDao {

Expand All @@ -17,11 +18,12 @@ public UserHistoryDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

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

jdbcTemplate.executeUpdate(sql,
jdbcTemplate.executeUpdate(connection,
sql,
userHistory.getUserId(),
userHistory.getAccount(),
userHistory.getPassword(),
Expand Down
28 changes: 24 additions & 4 deletions app/src/main/java/com/techcourse/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
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 org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.ConnectionManager;

import java.sql.Connection;

public class UserService {

Expand All @@ -24,9 +29,24 @@ 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);
userDao.update(user);
userHistoryDao.log(new UserHistory(user, createBy));
final ConnectionManager connectionManager = new ConnectionManager(DataSourceConfig.getInstance());

final Connection connection = connectionManager.getConnection();
try {
connection.setAutoCommit(false);

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

userDao.update(connection, user);
userHistoryDao.log(connection, new UserHistory(user, createBy));

connection.commit();
} catch (final Exception e) {
connectionManager.rollback(connection);
throw new DataAccessException(e);
} finally {
connectionManager.close(connection);
}
}
}
8 changes: 6 additions & 2 deletions app/src/test/java/com/techcourse/dao/UserDaoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.SQLException;

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

class UserDaoTest {
Expand Down Expand Up @@ -54,12 +57,13 @@ void insert() {
}

@Test
void update() {
void update() throws SQLException {
final var newPassword = "password99";
final var user = userDao.findById(1L);
user.changePassword(newPassword);

userDao.update(user);
final Connection connection = DataSourceConfig.getInstance().getConnection();
userDao.update(connection, user);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;

public class MockUserHistoryDao extends UserHistoryDao {

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

@Override
public void log(final UserHistory userHistory) {
public void log(final Connection connection, final UserHistory userHistory) {
throw new DataAccessException();
}
}
6 changes: 2 additions & 4 deletions app/src/test/java/com/techcourse/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
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 Down
12 changes: 12 additions & 0 deletions jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.dao.DataAccessException;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand Down Expand Up @@ -34,6 +35,17 @@ public void executeUpdate(final String sql, final Object... parameters) {
}
}

public void executeUpdate(final Connection connection, final String sql, final Object... parameters) {
try (final PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
setParameters(parameters, preparedStatement);
log.debug("query : {}", preparedStatement);

preparedStatement.executeUpdate();
} catch (final Exception e) {
throw new DataAccessException(e);
}
}

private void setParameters(final Object[] parameters, final PreparedStatement preparedStatement) throws SQLException {
for (int i = 1; i <= parameters.length; i++) {
preparedStatement.setObject(i, parameters[i - 1]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.springframework.jdbc.datasource;

import org.springframework.dao.DataAccessException;

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

public class ConnectionManager {

private final DataSource dataSource;

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

public Connection getConnection() {
try {
return dataSource.getConnection();
} catch (final SQLException e) {
throw new DataAccessException(e);
}
}

public void close(final Connection connection) {
try {
connection.close();
} catch (final SQLException e) {
throw new DataAccessException(e);
}
}

public void rollback(final Connection connection) {
try {
connection.rollback();
} catch (final SQLException e) {
throw new DataAccessException(e);
}
}
}
1 change: 0 additions & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ dependencies {
testImplementation 'org.testcontainers:mysql'

implementation "org.reflections:reflections:0.10.2"
implementation "ch.qos.logback:logback-classic:1.2.12"
implementation "org.apache.commons:commons-lang3:3.13.0"

testImplementation "org.springframework.boot:spring-boot-starter-test:2.7.14"
Expand Down
10 changes: 0 additions & 10 deletions study/src/main/java/aop/Transactional.java

This file was deleted.

53 changes: 53 additions & 0 deletions study/src/main/java/aop/common/TransactionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package aop.common;

import aop.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TransactionHandler implements InvocationHandler {

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

private final PlatformTransactionManager transactionManager;
private final Object target;

public TransactionHandler(final PlatformTransactionManager transactionManager, final Object target) {
this.transactionManager = transactionManager;
this.target = target;
}

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final Method targetMethod = target.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());

if (targetMethod.isAnnotationPresent(Transactional.class)) {
return invokeWithTransaction(target, method, args);
}

return method.invoke(target, args);
}

private Object invokeWithTransaction(final Object target, final Method method, final Object[] args) {
final TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());

try {
log.info("Start Transaction for: {}", method.getName());
final Object result = method.invoke(target, args);
transactionManager.commit(transaction);
log.info("End Transaction for: {}", method.getName());

return result;
} catch (final Exception e) {
transactionManager.rollback(transaction);
log.info("Errors occurred when committing: {}", method.getName());

throw new DataAccessException(e);
}
}
}
15 changes: 15 additions & 0 deletions study/src/main/java/aop/common/Transactional.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package aop.common;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
}
2 changes: 1 addition & 1 deletion study/src/main/java/aop/service/AppUserService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package aop.service;

import aop.Transactional;
import aop.common.Transactional;
import aop.domain.User;
import aop.domain.UserHistory;
import aop.repository.UserDao;
Expand Down
19 changes: 13 additions & 6 deletions study/src/test/java/aop/stage0/Stage0Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,27 @@

import aop.DataAccessException;
import aop.StubUserHistoryDao;
import aop.common.TransactionHandler;
import aop.domain.User;
import aop.repository.UserDao;
import aop.repository.UserHistoryDao;
import aop.service.AppUserService;
import aop.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.PlatformTransactionManager;

import java.lang.reflect.Proxy;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class Stage0Test {

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

@Autowired
private UserDao userDao;

Expand All @@ -45,7 +44,7 @@ void setUp() {
@Test
void testChangePassword() {
final var appUserService = new AppUserService(userDao, userHistoryDao);
final UserService userService = null;
final UserService userService = (UserService) makeProxy(appUserService);

final var newPassword = "qqqqq";
final var createBy = "gugu";
Expand All @@ -56,10 +55,18 @@ void testChangePassword() {
assertThat(actual.getPassword()).isEqualTo(newPassword);
}

private Object makeProxy(final Object target) {
final TransactionHandler transactionHandler = new TransactionHandler(platformTransactionManager, target);

return Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{UserService.class},
transactionHandler);
}

@Test
void testTransactionRollback() {
final var appUserService = new AppUserService(userDao, stubUserHistoryDao);
final UserService userService = null;
final UserService userService = (UserService) makeProxy(appUserService);

final var newPassword = "newPassword";
final var createBy = "gugu";
Expand Down
Loading

0 comments on commit 367b696

Please sign in to comment.