From 01833f739e6c55c96ed6a648bd85edde3138cc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=80=E1=85=A5=E1=86=AB=E1=84=8E?= =?UTF-8?q?=E1=85=A1=E1=86=BC?= Date: Wed, 17 Jan 2024 11:38:12 +0900 Subject: [PATCH] =?UTF-8?q?Refactor=20:=20test=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcurrencyManagerWithNamedLock.java | 82 +++++++++++-------- .../util/config/DatasourceConfiguration.java | 26 ++++++ src/main/resources/application-local.yml | 12 --- src/main/resources/application-test.yml | 43 ++++++++++ src/main/resources/application.yml | 31 ++++++- .../BankingApiApplicationTests.java | 2 + .../ConcurrencyManagerWithNamedLockTest.java | 16 ++-- .../IdempotentRequestHistoryServiceTest.java | 2 + .../CustomUserDetailServiceTest.java | 2 + .../MemberApplicationServiceTest.java | 2 + .../social/domain/FriendServiceTest.java | 2 + .../domain/SocialNetworkServiceTest.java | 2 + src/test/resources/application.yml | 16 ---- 13 files changed, 171 insertions(+), 67 deletions(-) create mode 100644 src/main/java/bankingapi/util/config/DatasourceConfiguration.java create mode 100644 src/main/resources/application-test.yml delete mode 100644 src/test/resources/application.yml diff --git a/src/main/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLock.java b/src/main/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLock.java index fca096d..4507875 100644 --- a/src/main/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLock.java +++ b/src/main/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLock.java @@ -26,61 +26,79 @@ public class ConcurrencyManagerWithNamedLock implements ConcurrencyManager { private static final String EMPTY_RESULT_MESSAGE = "USER LEVEL LOCK 쿼리 결과 값이 NULL 입니다. type = [{}], userLockName : [{}]"; private static final String INVALID_RESULT_MESSAGE = "USER LEVEL LOCK 쿼리 결과 값이 0 입니다. type = [{}], result : [{}] userLockName : [{}]"; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - private final DataSource dataSource; + private final DataSource userLoackDataSource; @Override public void executeWithLock(String lockName1, String lockName2, Runnable runnable) { - try(var connection = dataSource.getConnection()) { - getLock(connection, getMultiLockName(lockName1, lockName2)); - getLock(connection, lockName1); - getLock(connection, lockName2); - runnable.run(); - releaseLock(connection, lockName2); - releaseLock(connection, lockName1); - releaseLock(connection, getMultiLockName(lockName1, lockName2)); + try (var connection = userLoackDataSource.getConnection()) { + try { + log.debug("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", getMultiLockName(lockName1, lockName2), TIMEOUT_SECONDS, connection); + getLock(connection, getMultiLockName(lockName1, lockName2)); + try { + log.debug("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", lockName1, TIMEOUT_SECONDS, connection); + getLock(connection, lockName1); + try { + log.debug("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", lockName2, TIMEOUT_SECONDS, connection); + getLock(connection, lockName2); + runnable.run(); + } finally { + log.debug("start releaseLock=[{}], connection=[{}]", lockName2, connection); + releaseLock(connection, lockName2); + } + }finally { + log.debug("start releaseLock=[{}], connection=[{}]", lockName1, connection); + releaseLock(connection, lockName1); + } + } finally { + log.debug("start releaseLock=[{}], connection=[{}]", getMultiLockName(lockName1, lockName2), connection); + releaseLock(connection, getMultiLockName(lockName1, lockName2)); + } } catch (SQLException e) { throw new RuntimeException(e); - } finally { - releaseSessionLocks(); } } @Override public void executeWithLock(String lockName, Runnable runnable) { - try(var connection = dataSource.getConnection()) { + try (var connection = userLoackDataSource.getConnection()) { + log.info("start getLock=[{}], timeoutSeconds : [{}], connection=[{}]", lockName, TIMEOUT_SECONDS, connection); getLock(connection, lockName); - runnable.run(); - releaseLock(connection, lockName); + try { + runnable.run(); + } finally { + log.info("start releaseLock, connection=[{}]", connection); + releaseLock(connection, lockName); + } } catch (SQLException e) { throw new RuntimeException(e); - } finally { - releaseSessionLocks(); } } - private void releaseLock(Connection connection, String lockName) { - try (var preparedStatement = connection.prepareStatement(RELEASE_LOCK)) { - preparedStatement.setString(1, lockName); + private void getLock(Connection connection, String userLockName) { + try (var preparedStatement = connection.prepareStatement(GET_LOCK)) { + preparedStatement.setString(1, userLockName); + preparedStatement.setInt(2, TIMEOUT_SECONDS); var resultSet = preparedStatement.executeQuery(); - validateResult(resultSet, lockName, "ReleaseLock"); + validateResult(resultSet, userLockName, "GetLock"); } catch (SQLException e) { - log.error("ReleaseLock_{} : {}", lockName, e.getMessage()); + log.error("GetLock_{} : {}", userLockName, e.getMessage()); throw new IllegalStateException("SQL Exception"); } } - - private void getLock(Connection connection, String userLockName) { - try (var preparedStatement = connection.prepareStatement(GET_LOCK)) { + private void releaseLock(Connection connection, String userLockName) { + try (var preparedStatement = connection.prepareStatement(RELEASE_LOCK)) { preparedStatement.setString(1, userLockName); - preparedStatement.setInt(2, TIMEOUT_SECONDS); - - synchronized (this) { - var resultSet = preparedStatement.executeQuery(); - validateResult(resultSet, userLockName, "GetLock"); - } - + preparedStatement.executeQuery(); } catch (SQLException e) { - log.error("GetLock_{} : {}", userLockName, e.getMessage()); + log.error("Release Lock : {}", e.getMessage()); + throw new IllegalStateException("SQL Exception"); + } + } + private void releaseSessionLocks(Connection connection) { + try (var preparedStatement = connection.prepareStatement(RELEASE_SESSION_LOCKS)) { + preparedStatement.executeQuery(); + } catch (SQLException e) { + log.error("ReleaseSessionLocks : {}", e.getMessage()); throw new IllegalStateException("SQL Exception"); } } diff --git a/src/main/java/bankingapi/util/config/DatasourceConfiguration.java b/src/main/java/bankingapi/util/config/DatasourceConfiguration.java new file mode 100644 index 0000000..1e6e28b --- /dev/null +++ b/src/main/java/bankingapi/util/config/DatasourceConfiguration.java @@ -0,0 +1,26 @@ +package bankingapi.util.config; + +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import javax.sql.DataSource; + +@Configuration +public class DatasourceConfiguration { + @Primary + @Bean + @ConfigurationProperties("spring.datasource.hikari") + public DataSource dataSource() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); + } + + @Bean + @ConfigurationProperties("userlock.datasource.hikari") + public DataSource userLockDataSource() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index f5e3ce9..e69de29 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,12 +0,0 @@ -spring: - datasource: - url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/money_transfer_service?characterEncoding=UTF-8&serverTimezone=Asia/Seoul} - driver-class-name: com.mysql.cj.jdbc.Driver - username: ${SPRING_DATASOURCE_USERNAME:root} - password: ${SPRING_DATASOURCE_PASSWORD:password!} - jpa: - database: mysql - database-platform: org.hibernate.dialect.MySQLDialect - show-sql: true - hibernate: - ddl-auto: create diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..7c4ee42 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,43 @@ +spring: + datasource: + hikari: + maximum-pool-size: 20 + max-lifetime: 60000 + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver + jdbc-url: jdbc:tc:mysql:8.0.24://localhost:3306/test + connection-timeout: 5000 + pool-name: Spring-HikariPool + dbcp2: + driver-class-name: com.mysql.cj.jdbc.Driver + test-on-borrow: true + validation-query: SELECT 1 + jpa: + show-sql: true + hibernate: + ddl-auto: create + generate-ddl: true + jdbc: + template: + query-timeout: 2 + +userlock: + datasource: + hikari: + maximum-pool-size: 20 + max-lifetime: 60000 + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver + jdbc-url: jdbc:tc:mysql:8.0.24://localhost:3306/test + connection-timeout: 5000 + pool-name: UserLock-HikariPool + +logging: + level: + org.hibernate: + SQL: debug + tool.hbm2ddl: debug + type: trace + stat: debug + type.BasicTypeRegistry: warn + org.springframework.jdbc: debug + org.springframework.transaction: debug + bankingapi.concurrency: debug diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d74c444..b448652 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,30 @@ spring: - profiles: - active: local + datasource: + hikari: + maximum-pool-size: 20 + max-lifetime: 60000 + driver-class-name: com.mysql.cj.jdbc.Driver + jdbc-url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/money_transfer_service?characterEncoding=UTF-8&serverTimezone=Asia/Seoul} + username: ${SPRING_DATASOURCE_USERNAME:root} + password: ${SPRING_DATASOURCE_PASSWORD:password!} + connection-timeout: 5000 + pool-name: Spring-HikariPool + + jpa: + database: mysql + database-platform: org.hibernate.dialect.MySQLDialect + show-sql: true + hibernate: + ddl-auto: create + +userlock: + datasource: + hikari: + maximum-pool-size: 20 + max-lifetime: 60000 + jdbc-url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/money_transfer_service?characterEncoding=UTF-8&serverTimezone=Asia/Seoul} + username: ${SPRING_DATASOURCE_USERNAME:root} + password: ${SPRING_DATASOURCE_PASSWORD:password!} + driver-class-name: com.mysql.cj.jdbc.Driver + connection-timeout: 5000 + pool-name: UserLock-HikariPool diff --git a/src/test/java/bankingapi/BankingApiApplicationTests.java b/src/test/java/bankingapi/BankingApiApplicationTests.java index ee9624f..ead98d5 100644 --- a/src/test/java/bankingapi/BankingApiApplicationTests.java +++ b/src/test/java/bankingapi/BankingApiApplicationTests.java @@ -2,7 +2,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("test") @SpringBootTest class BankingApiApplicationTests { diff --git a/src/test/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLockTest.java b/src/test/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLockTest.java index 35dfd12..35c7131 100644 --- a/src/test/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLockTest.java +++ b/src/test/java/bankingapi/concurrency/ConcurrencyManagerWithNamedLockTest.java @@ -2,8 +2,10 @@ import static org.assertj.core.api.Assertions.*; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; +import java.util.random.RandomGenerator; import bankingapi.concurrency.ConcurrencyManager; import org.junit.jupiter.api.BeforeEach; @@ -15,11 +17,15 @@ import bankingapi.banking.domain.Account; import bankingapi.banking.domain.Money; import bankingapi.util.generator.AccountNumberGenerator; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("test") @SpringBootTest class ConcurrencyManagerWithNamedLockTest { private static final int NUMBER_OF_THREADS = 10; private static final int POLL_SIZE = 10; + private static final Money ONE = new Money(1); @Autowired private ConcurrencyManager concurrencyManager; private CountDownLatch latch; @@ -63,11 +69,11 @@ void calculateAtSameTime_controllingConcurrency() throws InterruptedException { for (int i = 0; i < NUMBER_OF_THREADS; i++) { service.execute(() -> { try { - concurrencyManager.executeWithLock("lock1", "lock2", () -> { - account.deposit(new Money(1)); - } - ); - }finally { + Thread.sleep(RandomGenerator.getDefault().nextInt(0, 50)); + concurrencyManager.executeWithLock("lock1", "lock2", () -> account.deposit(ONE)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { latch.countDown(); } }); diff --git a/src/test/java/bankingapi/idempotent/domain/IdempotentRequestHistoryServiceTest.java b/src/test/java/bankingapi/idempotent/domain/IdempotentRequestHistoryServiceTest.java index 9bcc795..e19a867 100644 --- a/src/test/java/bankingapi/idempotent/domain/IdempotentRequestHistoryServiceTest.java +++ b/src/test/java/bankingapi/idempotent/domain/IdempotentRequestHistoryServiceTest.java @@ -13,7 +13,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +@ActiveProfiles("test") @SpringBootTest class IdempotentRequestHistoryServiceTest { diff --git a/src/test/java/bankingapi/member/application/CustomUserDetailServiceTest.java b/src/test/java/bankingapi/member/application/CustomUserDetailServiceTest.java index 826c017..163de07 100644 --- a/src/test/java/bankingapi/member/application/CustomUserDetailServiceTest.java +++ b/src/test/java/bankingapi/member/application/CustomUserDetailServiceTest.java @@ -11,12 +11,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import bankingapi.member.domain.Member; import bankingapi.member.domain.MemberRepository; import bankingapi.member.exception.NotExistMemberException; +@ActiveProfiles("test") @Transactional @SpringBootTest class CustomUserDetailServiceTest { diff --git a/src/test/java/bankingapi/member/application/MemberApplicationServiceTest.java b/src/test/java/bankingapi/member/application/MemberApplicationServiceTest.java index c3182d4..7f11429 100644 --- a/src/test/java/bankingapi/member/application/MemberApplicationServiceTest.java +++ b/src/test/java/bankingapi/member/application/MemberApplicationServiceTest.java @@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import bankingapi.member.domain.Member; @@ -16,6 +17,7 @@ import bankingapi.member.dto.RegisterCommand; import bankingapi.member.exception.NotExistMemberException; +@ActiveProfiles("test") @Transactional @SpringBootTest class MemberApplicationServiceTest { diff --git a/src/test/java/bankingapi/social/domain/FriendServiceTest.java b/src/test/java/bankingapi/social/domain/FriendServiceTest.java index f7d6812..bce86d1 100644 --- a/src/test/java/bankingapi/social/domain/FriendServiceTest.java +++ b/src/test/java/bankingapi/social/domain/FriendServiceTest.java @@ -10,8 +10,10 @@ import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; +@ActiveProfiles("test") @Transactional @SpringBootTest class FriendServiceTest { diff --git a/src/test/java/bankingapi/social/domain/SocialNetworkServiceTest.java b/src/test/java/bankingapi/social/domain/SocialNetworkServiceTest.java index e267350..06980c4 100644 --- a/src/test/java/bankingapi/social/domain/SocialNetworkServiceTest.java +++ b/src/test/java/bankingapi/social/domain/SocialNetworkServiceTest.java @@ -9,11 +9,13 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import bankingapi.member.domain.Member; import bankingapi.member.domain.MemberRepository; +@ActiveProfiles("test") @Transactional @SpringBootTest class SocialNetworkServiceTest { diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml deleted file mode 100644 index 2feca6c..0000000 --- a/src/test/resources/application.yml +++ /dev/null @@ -1,16 +0,0 @@ -spring: - datasource: - driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - url: jdbc:tc:mysql:8.0.24://localhost:3306/test - dbcp2: - driver-class-name: com.mysql.cj.jdbc.Driver - test-on-borrow: true - validation-query: SELECT 1 - jpa: - show-sql: true - hibernate: - ddl-auto: create - generate-ddl: true - jdbc: - template: - query-timeout: 2