From 2832af79d854333da8be14975a9220cd4c0d78d0 Mon Sep 17 00:00:00 2001 From: kang-hyungu Date: Thu, 29 Sep 2022 13:30:49 +0900 Subject: [PATCH] =?UTF-8?q?JDBC=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=ED=98=84=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 3 + .../main/java/com/techcourse/dao/UserDao.java | 5 ++ .../com/techcourse/dao/UserHistoryDao.java | 62 ++++++++++++++++++ .../com/techcourse/domain/UserHistory.java | 59 +++++++++++++++++ .../com/techcourse/service/UserService.java | 32 ++++++++++ app/src/main/resources/schema.sql | 11 ++++ .../java/com/techcourse/dao/UserDaoTest.java | 24 ++++--- .../service/MockUserHistoryDao.java | 18 ++++++ .../techcourse/service/UserServiceTest.java | 63 +++++++++++++++++++ jdbc/build.gradle | 5 +- .../nextstep/jdbc/DataAccessException.java | 26 ++++++++ .../main/java/nextstep/jdbc/JdbcTemplate.java | 8 +++ 12 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/techcourse/dao/UserHistoryDao.java create mode 100644 app/src/main/java/com/techcourse/domain/UserHistory.java create mode 100644 app/src/main/java/com/techcourse/service/UserService.java create mode 100644 app/src/test/java/com/techcourse/service/MockUserHistoryDao.java create mode 100644 app/src/test/java/com/techcourse/service/UserServiceTest.java create mode 100644 jdbc/src/main/java/nextstep/jdbc/DataAccessException.java diff --git a/app/build.gradle b/app/build.gradle index a4510192d7..a94011f4db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,9 @@ dependencies { implementation project(':mvc') implementation project(':jdbc') + implementation 'org.springframework:spring-tx:5.3.23' + implementation 'org.springframework:spring-jdbc:5.3.23' + implementation 'org.apache.tomcat.embed:tomcat-embed-core:10.1.0-M16' implementation 'org.apache.tomcat.embed:tomcat-embed-jasper:10.1.0-M16' implementation 'ch.qos.logback:logback-classic:1.2.10' diff --git a/app/src/main/java/com/techcourse/dao/UserDao.java b/app/src/main/java/com/techcourse/dao/UserDao.java index 0bd9a5d5d3..ee17c159c2 100644 --- a/app/src/main/java/com/techcourse/dao/UserDao.java +++ b/app/src/main/java/com/techcourse/dao/UserDao.java @@ -1,6 +1,7 @@ package com.techcourse.dao; import com.techcourse.domain.User; +import nextstep.jdbc.JdbcTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +22,10 @@ public UserDao(final DataSource dataSource) { this.dataSource = dataSource; } + public UserDao(final JdbcTemplate jdbcTemplate) { + this.dataSource = null; + } + public void insert(final User user) { final var sql = "insert into users (account, password, email) values (?, ?, ?)"; diff --git a/app/src/main/java/com/techcourse/dao/UserHistoryDao.java b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java new file mode 100644 index 0000000000..a506912cf4 --- /dev/null +++ b/app/src/main/java/com/techcourse/dao/UserHistoryDao.java @@ -0,0 +1,62 @@ +package com.techcourse.dao; + +import com.techcourse.domain.UserHistory; +import nextstep.jdbc.JdbcTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; + + 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) {} + } + } +} diff --git a/app/src/main/java/com/techcourse/domain/UserHistory.java b/app/src/main/java/com/techcourse/domain/UserHistory.java new file mode 100644 index 0000000000..7ae4fdb813 --- /dev/null +++ b/app/src/main/java/com/techcourse/domain/UserHistory.java @@ -0,0 +1,59 @@ +package com.techcourse.domain; + +import java.time.LocalDateTime; + +public class UserHistory { + + private Long id; + + private final long userId; + private final String account; + private final String password; + private final String email; + + private final LocalDateTime createdAt; + + private final String createBy; + + 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, final String email, final String createBy) { + this.id = id; + this.userId = userId; + this.account = account; + this.password = password; + this.email = email; + this.createdAt = LocalDateTime.now(); + this.createBy = createBy; + } + + public Long getId() { + return id; + } + + public long getUserId() { + return userId; + } + + public String getAccount() { + return account; + } + + public String getPassword() { + return password; + } + + public String getEmail() { + return email; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public String getCreateBy() { + return createBy; + } +} diff --git a/app/src/main/java/com/techcourse/service/UserService.java b/app/src/main/java/com/techcourse/service/UserService.java new file mode 100644 index 0000000000..fcf2159dc8 --- /dev/null +++ b/app/src/main/java/com/techcourse/service/UserService.java @@ -0,0 +1,32 @@ +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 { + + private final UserDao userDao; + private final UserHistoryDao userHistoryDao; + + public UserService(final UserDao userDao, final UserHistoryDao userHistoryDao) { + this.userDao = userDao; + this.userHistoryDao = userHistoryDao; + } + + 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 var user = findById(id); + user.changePassword(newPassword); + userDao.update(user); + userHistoryDao.log(new UserHistory(user, createBy)); + } +} diff --git a/app/src/main/resources/schema.sql b/app/src/main/resources/schema.sql index 1bfb704f61..ba235931f1 100644 --- a/app/src/main/resources/schema.sql +++ b/app/src/main/resources/schema.sql @@ -5,3 +5,14 @@ create table if not exists users ( email varchar(100) not null, primary key(id) ); + +create table if not exists user_history ( + id bigint auto_increment, + user_id bigint not null, + account varchar(100) not null, + password varchar(100) not null, + email varchar(100) not null, + created_at datetime not null, + created_by varchar(100) not null, + primary key(id) +); diff --git a/app/src/test/java/com/techcourse/dao/UserDaoTest.java b/app/src/test/java/com/techcourse/dao/UserDaoTest.java index a0bf15297d..773d7faf82 100644 --- a/app/src/test/java/com/techcourse/dao/UserDaoTest.java +++ b/app/src/test/java/com/techcourse/dao/UserDaoTest.java @@ -6,8 +6,6 @@ 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 { @@ -19,52 +17,52 @@ void setup() { DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); userDao = new UserDao(DataSourceConfig.getInstance()); - final User user = new User("gugu", "password", "hkkang@woowahan.com"); + final var user = new User("gugu", "password", "hkkang@woowahan.com"); userDao.insert(user); } @Test void findAll() { - final List users = userDao.findAll(); + final var users = userDao.findAll(); assertThat(users).isNotEmpty(); } @Test void findById() { - final User user = userDao.findById(1L); + final var user = userDao.findById(1L); assertThat(user.getAccount()).isEqualTo("gugu"); } @Test void findByAccount() { - final String account = "gugu"; - final User user = userDao.findByAccount(account); + final var account = "gugu"; + final var user = userDao.findByAccount(account); assertThat(user.getAccount()).isEqualTo(account); } @Test void insert() { - final String account = "insert-gugu"; - final User user = new User(account, "password", "hkkang@woowahan.com"); + final var account = "insert-gugu"; + final var user = new User(account, "password", "hkkang@woowahan.com"); userDao.insert(user); - final User actual = userDao.findById(2L); + final var actual = userDao.findById(2L); assertThat(actual.getAccount()).isEqualTo(account); } @Test void update() { - final String newPassword = "password99"; - final User user = userDao.findById(1L); + final var newPassword = "password99"; + final var user = userDao.findById(1L); user.changePassword(newPassword); userDao.update(user); - final User actual = userDao.findById(1L); + final var actual = userDao.findById(1L); assertThat(actual.getPassword()).isEqualTo(newPassword); } diff --git a/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java new file mode 100644 index 0000000000..da3f2cee4b --- /dev/null +++ b/app/src/test/java/com/techcourse/service/MockUserHistoryDao.java @@ -0,0 +1,18 @@ +package com.techcourse.service; + +import com.techcourse.dao.UserHistoryDao; +import com.techcourse.domain.UserHistory; +import nextstep.jdbc.DataAccessException; +import nextstep.jdbc.JdbcTemplate; + +public class MockUserHistoryDao extends UserHistoryDao { + + public MockUserHistoryDao(final JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + public void log(final UserHistory userHistory) { + throw new DataAccessException(); + } +} diff --git a/app/src/test/java/com/techcourse/service/UserServiceTest.java b/app/src/test/java/com/techcourse/service/UserServiceTest.java new file mode 100644 index 0000000000..a22c3c7a82 --- /dev/null +++ b/app/src/test/java/com/techcourse/service/UserServiceTest.java @@ -0,0 +1,63 @@ +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.support.jdbc.init.DatabasePopulatorUtils; +import nextstep.jdbc.DataAccessException; +import nextstep.jdbc.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; + +@Disabled +class UserServiceTest { + + private JdbcTemplate jdbcTemplate; + private UserDao userDao; + + @BeforeEach + void setUp() { + this.jdbcTemplate = new JdbcTemplate(DataSourceConfig.getInstance()); + this.userDao = new UserDao(jdbcTemplate); + + DatabasePopulatorUtils.execute(DataSourceConfig.getInstance()); + final var user = new User("gugu", "password", "hkkang@woowahan.com"); + userDao.insert(user); + } + + @Test + void testChangePassword() { + final var userHistoryDao = new UserHistoryDao(jdbcTemplate); + final var userService = new UserService(userDao, userHistoryDao); + + final var newPassword = "qqqqq"; + final var createBy = "gugu"; + userService.changePassword(1L, newPassword, createBy); + + final var actual = userService.findById(1L); + + assertThat(actual.getPassword()).isEqualTo(newPassword); + } + + @Test + void testTransactionRollback() { + // 트랜잭션 롤백 테스트를 위해 mock으로 교체 + final var userHistoryDao = new MockUserHistoryDao(jdbcTemplate); + final var userService = new UserService(userDao, userHistoryDao); + + final var newPassword = "newPassword"; + final var createBy = "gugu"; + // 트랜잭션이 정상 동작하는지 확인하기 위해 의도적으로 MockUserHistoryDao에서 예외를 발생시킨다. + assertThrows(DataAccessException.class, + () -> userService.changePassword(1L, newPassword, createBy)); + + final var actual = userService.findById(1L); + + assertThat(actual.getPassword()).isNotEqualTo(newPassword); + } +} diff --git a/jdbc/build.gradle b/jdbc/build.gradle index 5063e30d5e..c5f680359c 100644 --- a/jdbc/build.gradle +++ b/jdbc/build.gradle @@ -7,6 +7,9 @@ repositories { } dependencies { + implementation 'org.springframework:spring-tx:5.3.23' + implementation 'org.springframework:spring-jdbc:5.3.23' + implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'ch.qos.logback:logback-classic:1.2.10' @@ -18,6 +21,6 @@ dependencies { testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' } -tasks.named('test') { +test { useJUnitPlatform() } diff --git a/jdbc/src/main/java/nextstep/jdbc/DataAccessException.java b/jdbc/src/main/java/nextstep/jdbc/DataAccessException.java new file mode 100644 index 0000000000..ac0e0a3a3b --- /dev/null +++ b/jdbc/src/main/java/nextstep/jdbc/DataAccessException.java @@ -0,0 +1,26 @@ +package nextstep.jdbc; + +public class DataAccessException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public DataAccessException() { + super(); + } + + public DataAccessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public DataAccessException(String message, Throwable cause) { + super(message, cause); + } + + public DataAccessException(String message) { + super(message); + } + + public DataAccessException(Throwable cause) { + super(cause); + } +} diff --git a/jdbc/src/main/java/nextstep/jdbc/JdbcTemplate.java b/jdbc/src/main/java/nextstep/jdbc/JdbcTemplate.java index b3914f478e..613a588a93 100644 --- a/jdbc/src/main/java/nextstep/jdbc/JdbcTemplate.java +++ b/jdbc/src/main/java/nextstep/jdbc/JdbcTemplate.java @@ -3,7 +3,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.sql.DataSource; + public class JdbcTemplate { private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class); + + private final DataSource dataSource; + + public JdbcTemplate(final DataSource dataSource) { + this.dataSource = dataSource; + } }