반응형
문제
스프링은 트랜잭션에 진입한 후에 커넥션을 점유하고, 이후에 데이터소스를 결정한다.
우리는 두 개의 데이터소스를 채택해서, 이중으로 커넥션을 점유하는 문제가 있었다.
상황
spring:
application:
name: multi-source-service
datasource:
hikari:
postgres:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:54326/sample
username: sample
password: 1234
postgres-two:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:54327/sample
username: sample
password: 1234
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
prometheus:
enabled: true
우선 application.yml 은 위와 같고,
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
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;
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.hikari.postgres")
public DataSource mainDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.postgres-two")
public DataSource subDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
기존 코드는 위와 같다.
import ex.multisource.domain.Product;
import ex.multisource.dto.ProductCreateReq;
import ex.multisource.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j(topic = "product-service")
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
@Transactional
public Product saveProduct(ProductCreateReq request) throws InterruptedException {
Product product = Product.create(request.name(), request.price());
Thread.sleep(1000L * 180);
return productRepository.save(product);
}
}
그리고 테스트용으로, @Transactional 어노테이션을 붙여서 요청이 들어오면 트랜잭션 내에서 180초 동안 멈춰있도록 했다.
그 뒤 요청을 보내고 actuator로 현재 활성 커넥션 수를 확인해 본 결과, 메인 데이터소스와 서브 데이터소스 2개를 점유하고 있었다.
해결
실제로 DB에 요청을 보낼 때 커넥션을 획득하는 LazyConnectionDataSourceProxy로 데이터소스를 감싸주었다.
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource mainDataSourceProxy(DataSource mainDataSource) {
return new LazyConnectionDataSourceProxy(mainDataSource);
}
@Bean
public DataSource subDataSourceProxy(@Qualifier("subDataSource") DataSource subDataSource) {
return new LazyConnectionDataSourceProxy(subDataSource);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.postgres")
public DataSource mainDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.postgres-two")
public DataSource subDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
변경 코드는 위와 같다. 기존 데이터소스들은 그대로 두고, 각각 프록시로 감싼 데이터소스를 하나 더 만들었다.
java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource
Caused by: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
따로 dataSource를 두지 않고, mainDataSource를 바로 해두고 싶었는데, 위의 에러가 계속 났다. 프록시로 데이터소스를 감싸느라, JPA에서 엔티티매니저를 만드는데 문제가 생기는 것 같은데 정확히는 알아보아야 한다.
LazyConnectionDataSourceProxy로 데이터소스를 감싸면 실제 DB에 요청할 때 커넥션을 획득하기 때문에, 활성 커넥션이 0개인 것을 확인할 수 있다!!!!
반응형
'TIL ✍️' 카테고리의 다른 글
TIL #122 : 읽기전용/쓰기전용DB를 @Transactional의 readOnly로 구분하기 (0) | 2024.11.16 |
---|---|
TIL #121 : 스프링 배치 5버전에서 멀티 dataSource 중 하나 지정하기 (0) | 2024.11.16 |
TIL #119 : Jacoco 테스트 커버리지 항목에 롬복이 생성한 코드 무시하기 (0) | 2024.11.13 |
TIL #118 : 롬복 @RequiredArgsConstructor 사용 시 @Qualifier 사용 불가 해결하기 (0) | 2024.11.13 |
TIL #117 : 인텔리제이 프로젝트 구조의 하위 디렉토리 전부 펼치기 단축키 (0) | 2024.11.13 |