diff --git a/build.gradle b/build.gradle index b7bc055..b9c1a7f 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,9 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + // 테스트 코드에서 Lombok 사용할 경우 필요 + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' compileOnly 'javax:javaee-api:8.0.1' // 테스트를 위한 h2 설정 diff --git a/src/main/java/com/example/Jinus/service/v2/userInfo/UserServiceV2.java b/src/main/java/com/example/Jinus/service/v2/userInfo/UserServiceV2.java index faa85bf..9a331cb 100644 --- a/src/main/java/com/example/Jinus/service/v2/userInfo/UserServiceV2.java +++ b/src/main/java/com/example/Jinus/service/v2/userInfo/UserServiceV2.java @@ -1,12 +1,11 @@ package com.example.Jinus.service.v2.userInfo; import com.example.Jinus.repository.v2.userInfo.UserRepositoryV2; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.time.Duration; import java.util.Map; import java.util.Optional; @@ -39,4 +38,12 @@ public int getUserDepartmentId(String userId) { return parentDepartmentMap.getOrDefault(departmentId, departmentId); } + // hikariCP test + @Transactional + public void selectCampusIdTest(String userId) throws InterruptedException { + log.info("selectCampusIdTest 시작 - userId: {}", userId); + Thread.sleep(2500); // 커넥션을 5초간 점유 (풀 점유 시 타임아웃 유도) + userRepositoryV2.findCampusIdById(userId); + log.info("selectCampusIdTest 종료 - userId: {}", userId); + } } diff --git a/src/test/java/com/example/Jinus/service/userInfo/UserServiceHikariCPTest.java b/src/test/java/com/example/Jinus/service/userInfo/UserServiceHikariCPTest.java new file mode 100644 index 0000000..91592df --- /dev/null +++ b/src/test/java/com/example/Jinus/service/userInfo/UserServiceHikariCPTest.java @@ -0,0 +1,75 @@ +package com.example.Jinus.service.userInfo; + +import com.example.Jinus.service.v2.userInfo.UserServiceV2; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +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 javax.sql.DataSource; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Slf4j +@SpringBootTest +@ActiveProfiles("test") +public class UserServiceHikariCPTest { + + @Autowired + private UserServiceV2 userService; + + @Autowired + private DataSource dataSource; + + @Test + @DisplayName("멀티스레드 10개 생성 후 id 조회") + void hikariConnectionTimeoutTest() throws InterruptedException { + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + + for (int i = 0; i < threadCount; i++) { + final int userNo = i; + executor.submit(() -> { + try { + log.info(">> [{}] 서비스 호출 시작", userNo); + userService.selectCampusIdTest("user" + userNo); + log.info(">> [{}] 서비스 호출 완료", userNo); + } catch (Exception e) { + log.error(">> [{}] 예외 발생: {}", userNo, e.getMessage()); + } finally { + latch.countDown(); + } + }); + } + + // 커넥션 풀 상태 모니터링 (2초마다 찍기) + for (int i = 0; i < 5; i++) { + printHikariStatus(); + Thread.sleep(2000); + } + + latch.await(); + executor.shutdown(); + } + + private void printHikariStatus() { + if (!(dataSource instanceof HikariDataSource hikariDataSource)) { + log.warn("데이터소스는 Hikari가 아님: {}", dataSource.getClass()); + return; + } + + HikariPoolMXBean poolStats = hikariDataSource.getHikariPoolMXBean(); + log.info("### HikariCP 상태 체크 -> 총: {}, 사용 중: {}, 유휴: {}, 대기중 쓰레드: {}", + poolStats.getTotalConnections(), + poolStats.getActiveConnections(), + poolStats.getIdleConnections(), + poolStats.getThreadsAwaitingConnection() + ); + } +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 2025534..88a1479 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -1,8 +1,9 @@ # Datasource ?? spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:test +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.password= +spring.datasource.type=com.zaxxer.hikari.HikariDataSource # H2 ??????? Hibernate dialect(JPA? ??? ?????? ???? ????) spring.jpa.database-platform=org.hibernate.dialect.H2Dialect @@ -14,4 +15,13 @@ spring.jpa.properties.hibernate.format_sql=true # H2 ?? ?? spring.h2.console.enabled=true -spring.h2.console.path=/h2 \ No newline at end of file +spring.h2.console.path=/h2 + +spring.datasource.hikari.maximumPoolSize=4 +spring.datasource.hikari.connectionTimeout=3000 +spring.datasource.hikari.maxLifetime=50000 +spring.datasource.hikari.idleTimeout=0 +spring.datasource.hikari.keepaliveTime=30000 +spring.datasource.hikari.validationTimeout=1000 +logging.level.com.zaxxer.hikari=trace +logging.level.com.zaxxer.hikari.HikariConfig=debug \ No newline at end of file