JPA Querydsl 로 대부분의 쿼리가 짜여져 있는 프로젝트를 운영 중 이슈 발생.
이슈 상황
1. 로컬 서버에서 개발을 진행하는 중에 지속적으로 Connection is closed 발생
java.sql.SQLException: Connection is closed
at com.zaxxer.hikari.pool.ProxyConnection$ClosedConnection.lambda$getClosedConnection$0(ProxyConnection.java:494) ~[HikariCP-3.4.2.jar!/:na]
at com.sun.proxy.$Proxy118.prepareStatement(Unknown Source) ~[na:na]
at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:316) ~[HikariCP-3.4.2.jar!/:na]
at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java) ~[HikariCP-3.4.2.jar!/:na]
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:149) ~[hibernate-core-5.4.10.Final.jar!/:5.4.10.Final]
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:176) ~[hibernate-core-5.4.10.Final.jar!/:5.4.10.Final]
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:151) ~[hibernate-core-5.4.10.Final.jar!/:5.4.10.Final]
2. Hikari pool 중 Querydsl로 설정되어 있는 pool 만 active 값이 사라지지 않음
3. Connection is closed 발생 시 JPQL을 사용한 쿼리는 정상적으로 실행되고, Querydsl을 사용한 쿼리에서만 오류 발생
해결 방법
1. Connection maximun-pool-size 증량
DB:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/DB-name?serverTimezone=UTC&useSSL=false
username: ***
password: '*****'
maximum-pool-size: 3
driver-class-name: com.mysql.cj.jdbc.Driver
=> 로컬에서 실행할 때는 문제가 되지 않았지만, 하루가 지나면 다시 Connection is closed 발생
2. connection-test-query, validation-query 설정
DB:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/DB-name?serverTimezone=UTC&useSSL=false
username: ***
password: '*****'
maximum-pool-size: 3
driver-class-name: com.mysql.cj.jdbc.Driver
connection-test-query: SELECT 1
validation-query: SELECT 1
* Connection Test Query (연결 테스트 쿼리)
데이터 소스가 데이터베이스와 연결을 맺을 때, 실제로 데이터베이스와의 연결이 성공적으로 이루어졌는지 확인하는 쿼리
* Validation Query (유효성 검증 쿼리)
연결된 데이터베이스의 유효성을 주기적으로 검증하는 쿼리입니다. 데이터 소스는 일정한 주기로 Validation Query를 실행하여 데이터베이스와의 연결이 유효한지 확인
* Connection Test Query: 데이터 소스가 최초에 연결을 수립할 때
* Validation Query: 주기적으로 연결을 유효성을 검증하는 데 사용
=> 설정을 해도 변화 없음
3. idle-timeout, max-lifetime 설정
DB:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/DB-name?serverTimezone=UTC&useSSL=false
username: ***
password: '*****'
maximum-pool-size: 3
driver-class-name: com.mysql.cj.jdbc.Driver
connection-test-query: SELECT 1
validation-query: SELECT 1
idle-timeout: 1800000
max-lifetime: 3000000
mysql을 사용하고 있고, mysql의 wait_timeout = 28800 으로 설정되어 있음.
* Idle Timeout (유휴 시간 초과)
커넥션 풀에 있는 커넥션이 일정 시간 동안 사용되지 않은 경우, 해당 커넥션을 종료하는 시간을 설정하는 것.
유휴 시간 초과를 설정함으로써 애플리케이션에서 일정 기간 동안 사용되지 않는 커넥션을 제거하여, 불필요한 자원 소모를 방지하고 데이터베이스 리소스를 효율적으로 사용할 수 있다.
idle-timeout의 크기는 wait_timeout보다 큰 값을 설정하는 것이 일반적이다.
(일반적으로 1분에서 5분 정도의 값으로 설정)
* Max Lifetime (최대 사용 시간):
커넥션 풀에 있는 커넥션이 최대 얼마나 오래 사용될 수 있는지를 설정하는 것.
일정 시간이 지나면 커넥션 풀에 있는 커넥션은 자동으로 제거되고 새로운 커넥션이 생성됨. 이를 통해 오래된 커넥션으로 인해 발생할 수 있는 문제를 방지하고 커넥션 풀에 새로운 연결을 유지할 수 있다.
데이터베이스 연결이 오랜 시간동안 지속되면 데이터베이스 서버나 네트워크 장애 등으로 인해 커넥션의 상태가 변동될 수 있다. 이러한 상황에서 오래된 커넥션을 재사용하면 예기치 않은 오류가 발생할 수 있으므로, 일정 시간 이상 사용된 커넥션을 제거하고 새로운 커넥션을 생성하는 것이 좋다.
(일반적으로 30분에서 1시간 정도의 값을 설정)
=> 설정 후에도 변화 없음
4. Querydsl 작성 시 connection 직접 closed 하도록 처리
private final EntityManagerUtil entityManagerUtil;
public List<TestDTO> getTest() {
EntityManager entityManager = entityManagerUtil.getCretaEntityManager();
try {
return new JPAQueryFactory(entityManager)
.select(new QTestDTO(
project.id,
project.name,
project.status
))
.from(project)
.fetch();
} finally {
entityManager.close();
}
}
=> 설정 후에도 변화 없음
5. QuerydslConfig 설정 변경
1) EntityManager => EntityManagerFactory로 변경
=> 설정 후에도 변화 없음
package com.-;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.persistence.EntityManager;
@Configuration
public class QuerydslConfig {
private final EntityManagerFactory entityManagerFactory1; <= 변경
private final EntityManagerFactory entityManagerFactory2; <= 변경
public QuerydslConfig(@Qualifier("entityManagerFactory1") EntityManagerFactory entityManagerFactory1,
@Qualifier("entityManagerFactory2") EntityManagerFactory entityManagerFactory2) {
this.entityManagerFactory1 = entityManagerFactory1;
this.entityManagerFactory2 = entityManagerFactory2;
}
@Bean
public JPAQueryFactory jpaQueryFactory1(){
return new JPAQueryFactory(entityManagerFactory1.getEntityManagerFactory().createEntityManager());
}
@Bean
@Primary
public JPAQueryFactory jpaQueryFactory2(){
return new JPAQueryFactory(entityManagerFactory2.getEntityManagerFactory().createEntityManager());
}
}
2) JPAQueryFactory 생성 시 파라미터 변경 (해결)
기존에 JPAQueryFactory 생성 시 entityManager.getEntityManagerFactory().createEntityManager() 의 값을 넘겨줬는데 entityManager만 넘기는 것으로 변경
package com.-;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.persistence.EntityManager;
@Configuration
public class QuerydslConfig {
private final EntityManager entityManager1;
private final EntityManager entityManager2;
public QuerydslConfig(@Qualifier("entityManagerFactory1") EntityManager entityManager1,
@Qualifier("entityManagerFactory2") EntityManager entityManager2) {
this.entityManager1 = entityManager1;
this.entityManager2 = entityManager2;
}
@Bean
public JPAQueryFactory jpaQueryFactory1(){
return new JPAQueryFactory(entityManager1); <= 변경
}
@Bean
@Primary
public JPAQueryFactory jpaQueryFactory2(){
return new JPAQueryFactory(entityManager2); <= 변경
}
}
=> Hikari Pool 의 active 값이 쿼리 종료 후 0으로 반환됨.
=> 8시간(wait-timeout) 이 지나도 커넥션이 끊기지 않고 유지됨.
결론
ChatGPT의 내용을 참고하여 querydsl 설정을 진행했고, 오류 내용에 대해서도 구글링으로 답이 나오지 않아 chatGPT 답변을 대부분 참고했는데, JPAQueryFactory에 entityManager를 넘겨줄 때, entityManager만 넘기는 것이 아니라 생성된 entityManager에서 또 entityManager를 생성한 값을 넘김으로 인해 connection이 끊기지 않고 계속 잡고 있는 상황이 발생한 것으로 추정됨.
ChatGPT를 너무 믿지 말자..
'JPA' 카테고리의 다른 글
spring boot jpa 설정 (0) | 2024.03.25 |
---|---|
Querydsl Projection 패턴 (0) | 2023.05.30 |
Query DSL 설정 (0) | 2023.05.25 |