# 검증기 자바독이 적은 두 결함과 그 상쇄 JpaDataSourceProfileValidator.java:11 *

It used to validate a different one. The bounds came from {@code JpaDataSourceSettings}, bound JpaDataSourceProfileValidator.java:12 * to {@code app.jpa-platform.datasource.*}, while the pool that serves requests is built from JpaDataSourceProfileValidator.java:13 * {@code spring.datasource.hikari.*} — one pool with two descriptions, and a validator can pass JpaDataSourceProfileValidator.java:14 * against the description that is not in use. JpaDataSourceProfileValidator.java:15 * JpaDataSourceProfileValidator.java:16 *

Worse, the parallel namespace appeared in no shipped YAML and no row of the env-key registry, JpaDataSourceProfileValidator.java:17 * so both of its fields were always null and {@code requirePoolBounds} would have thrown on every JpaDataSourceProfileValidator.java:18 * deployment. Nothing called it, so nothing failed. Two defects that cancelled each other out, and JpaDataSourceProfileValidator.java:19 * the reason the application started was the second one hiding the first. # 그 병렬 네임스페이스가 src 안에 남아 있는 자리 main · NotificationSmtpProviderConfig.java:48 * app.jpa-platform.datasource.*} validated a pool that {@code spring.datasource.hikari.*} had main · NotificationSmtpSettings.java:14 * paid for — {@code app.jpa-platform.datasource.*} validated a pool that {@code main · SmtpProviderRuntimeAssembler.java:40 * already paid for elsewhere — {@code app.jpa-platform.datasource.*} validated a pool that {@code main · JpaDataSourceProfileValidator.java:12 * to {@code app.jpa-platform.datasource.*}, while the pool that serves requests is built from test · JpaResolvedDataSourceValidationTest.java:22 *

{@code JpaDataSourceSettings} bound {@code app.jpa-platform.datasource.*} while the pool that test · JpaResolvedDataSourceValidationTest.java:57 .filter(path -> withoutJavaComments(read(path)).contains("app.jpa-platform.datasource")) main · KafkaMessagingAutoConfiguration.java:119 * repository has already paid for in {@code app.jpa-platform.datasource.*}, and the src 밖에서 이 문자열을 쓰는 파일과 줄 수 : docs/study/postgresql-jpa-walkthrough.ko.md:1 docs/superpowers/plans/2026-08-15-wave2-module-on-path.md:4 docs/superpowers/plans/evidence/2026-08-15-wave2-decisions.md:3 docs/superpowers/specs/2026-08-15-five-adapter-runtime-remediation-review-design.md:1 infra/jpa/postgres/README.md:1 그중 운영자용 표에 살아 있는 줄 : infra/jpa/postgres/README.md:18:| `max_connections` | The number `app.jpa-platform.datasource.maximum-pool-size` must be sized against — across every instance, and allowing for 설정 파일(yml·yaml·properties)에서 : 0 건 그중 주석이 아닌 줄 : main 0 건 · test 1 건 (test 쪽은 회귀 테스트의 검색 문자열) 그것을 바인딩하던 JpaDataSourceSettings 클래스 : 0 건 # 옛 상한 검사 메서드 이름이 남아 있는 자리 main · JpaDataSourceProfileValidator.java:17 * so both of its fields were always null and {@code requirePoolBounds} would have thrown on every test · JpaResolvedDataSourceValidationTest.java:26 * of its fields were always null — and {@code requirePoolBounds} throws on a null. Had anything test · JpaResolvedDataSourceValidationTest.java:29 *

Nothing did. {@code validateStable} and {@code requirePoolBounds} were reachable only from # 지금 검증기가 읽는 것과 던지는 것 JpaDataSourceProfileValidator.java:50 public void validateResolved(DataSource dataSource, boolean requirePostgreSql) { JpaDataSourceProfileValidator.java:51 Objects.requireNonNull(dataSource, "dataSource"); JpaDataSourceProfileValidator.java:52 if (!requirePostgreSql) { JpaDataSourceProfileValidator.java:53 // Local development runs H2 by design, and PersistenceVendorProdSafetyValidator is what keeps JpaDataSourceProfileValidator.java:54 // that out of production. Demanding PostgreSQL here as well would refuse every laptop. JpaDataSourceProfileValidator.java:55 return; JpaDataSourceProfileValidator.java:56 } JpaDataSourceProfileValidator.java:57 try (Connection connection = dataSource.getConnection()) { JpaDataSourceProfileValidator.java:58 versionPolicy.requireStable(connection.getMetaData()); JpaDataSourceProfileValidator.java:59 } catch (SQLException unreachable) { JpaDataSourceProfileValidator.java:60 throw new IllegalStateException( JpaDataSourceProfileValidator.java:61 "could not open a connection to validate the datasource: " JpaDataSourceProfileValidator.java:62 + unreachable.getMessage() JpaDataSourceProfileValidator.java:63 + ". A context that starts without this check reports healthy and fails on whoever " JpaDataSourceProfileValidator.java:64 + "sends the first request.", JpaDataSourceProfileValidator.java:65 unreachable); JpaDataSourceProfileValidator.java:66 } # validateResolved 를 부르는 자리 전부 main · PersistenceJpaRootAutoConfiguration.java:113 return () -> validator.validateResolved(dataSource, "postgresql".equalsIgnoreCase(vendor)); test · JpaResolvedDataSourceValidationTest.java:82 .validateResolved(pool, true)) test · JpaResolvedDataSourceValidationTest.java:101 assertThatCode(() -> validator.validateResolved(h2, false)) test · JpaResolvedDataSourceValidationTest.java:104 assertThatThrownBy(() -> validator.validateResolved(h2, true)) # 그 호출자가 붙는 조건과 출하 기본값 PersistenceJpaRootAutoConfiguration.java:56 @AutoConfiguration PersistenceJpaRootAutoConfiguration.java:57 @ConditionalOnProperty( PersistenceJpaRootAutoConfiguration.java:58 prefix = "ca-skeleton.persistence-jpa", PersistenceJpaRootAutoConfiguration.java:59 name = "enabled", PersistenceJpaRootAutoConfiguration.java:60 havingValue = "true") PersistenceJpaRootAutoConfiguration.java:61 @Import({ application.yml:364: enabled: ${APP_PERSISTENCE_JPA_ENABLED:false} # 직접 생성의 이유로 적힌 조건과, 그 조건의 현재 상태 PersistenceJpaRootAutoConfiguration.java:95 * {@code @ConditionalOnBean(DataSource.class)} on a plain {@code @Configuration} imported by this PersistenceJpaRootAutoConfiguration.java:96 * root — a condition evaluated during configuration-class parsing, before the datasource bean PersistenceJpaRootAutoConfiguration.java:97 * definition is registered. That class therefore drops out silently in the real application, PersistenceJpaRootAutoConfiguration.java:98 * taking the whole JPA add-on layer with it. Depending on a bean from it would make this check PersistenceJpaRootAutoConfiguration.java:99 * disappear for the same reason the thing it checks disappeared. The condition-ordering defect is JpaPlatformRuntimeAutoConfiguration.java:57 * root, which already carries the master switch, so "is there a datasource" has been answered yes JpaPlatformRuntimeAutoConfiguration.java:58 * by the time it is parsed. A bean here that needs one takes it as a parameter, and a missing JpaPlatformRuntimeAutoConfiguration.java:59 * datasource with the switch on is then a loud failure — which is the outcome that was wanted, JpaPlatformRuntimeAutoConfiguration.java:60 * rather than the layer vanishing. JpaPlatformRuntimeAutoConfiguration.java:61 */ JpaPlatformRuntimeAutoConfiguration.java:62 @org.springframework.context.annotation.Configuration(proxyBeanMethods = false) JpaPlatformRuntimeAutoConfiguration.java:63 @ConditionalOnClass(DataSource.class) JpaPlatformRuntimeAutoConfiguration.java:64 @ConditionalOnProperty(prefix = "app.jpa-platform", name = "addons-enabled", matchIfMissing = true) # 그 호출자가 검증기를 어디서 얻는가 PersistenceJpaRootAutoConfiguration.java:106 @Bean PersistenceJpaRootAutoConfiguration.java:107 public InitializingBean jpaResolvedDataSourceCheck( PersistenceJpaRootAutoConfiguration.java:108 DataSource dataSource, Environment environment) { PersistenceJpaRootAutoConfiguration.java:109 JpaDataSourceProfileValidator validator = PersistenceJpaRootAutoConfiguration.java:110 new JpaDataSourceProfileValidator(new PostgreSqlVersionPolicy()); PersistenceJpaRootAutoConfiguration.java:111 String vendor = PersistenceJpaRootAutoConfiguration.java:112 environment.getProperty(PersistenceVendorSettings.VENDOR_PROPERTY, "postgresql").trim(); PersistenceJpaRootAutoConfiguration.java:113 return () -> validator.validateResolved(dataSource, "postgresql".equalsIgnoreCase(vendor)); PersistenceJpaRootAutoConfiguration.java:114 } # 이 타입을 빈으로 정의하는 자리와, 그 빈을 주입받는 코드 JpaPlatformAutoConfiguration 의 @Bean 개수 : 0 JpaPlatformAutoConfiguration.java:19 *

Plain construction rather than Spring auto-configuration. This repository's rule is that JpaPlatformAutoConfiguration.java:20 * {@code app-bootstrap} owns composition, and an adapter leaf that auto-configured itself would JpaPlatformAutoConfiguration.java:48 /** The datasource product, version, and pool validator. */ JpaPlatformAutoConfiguration.java:49 public JpaDataSourceProfileValidator dataSourceProfileValidator() { JpaPlatformAutoConfiguration.java:50 return new JpaDataSourceProfileValidator(new PostgreSqlVersionPolicy()); JpaPlatformRuntimeAutoConfiguration.java:111 /** The datasource product, version and pool validator. */ JpaPlatformRuntimeAutoConfiguration.java:112 @Bean JpaPlatformRuntimeAutoConfiguration.java:113 @ConditionalOnMissingBean JpaPlatformRuntimeAutoConfiguration.java:114 public JpaDataSourceProfileValidator jpaDataSourceProfileValidator( JpaPlatformRuntimeAutoConfiguration.java:115 JpaPlatformAutoConfiguration composition) { JpaPlatformRuntimeAutoConfiguration.java:116 return composition.dataSourceProfileValidator(); JpaPlatformRuntimeAutoConfiguration.java:117 } 파라미터나 필드로 받는 프로덕션 코드 : 0 건 # HikariPoolConstraintValidator 가 읽는 키와 그 빈, 그리고 풀 크기를 보는 다른 검증기 HikariPoolConstraintValidator.java:18 static final String CONNECTION_TIMEOUT_KEY = "spring.datasource.hikari.connection-timeout"; HikariPoolConstraintValidator.java:19 static final String VALIDATION_TIMEOUT_KEY = "spring.datasource.hikari.validation-timeout"; HikariPoolConstraintValidator.java:20 static final String KEEPALIVE_TIME_KEY = "spring.datasource.hikari.keepalive-time"; HikariPoolConstraintValidator.java:21 static final String MAX_LIFETIME_KEY = "spring.datasource.hikari.max-lifetime"; HikariPoolConstraintValidator.java:22 static final String LEAK_DETECTION_KEY = "spring.datasource.hikari.leak-detection-threshold"; RuntimeSafetyConfig.java:49 @Bean RuntimeSafetyConfig.java:50 HikariPoolConstraintValidator hikariPoolConstraintValidator(Environment environment) { RuntimeSafetyConfig.java:51 return new HikariPoolConstraintValidator(environment); maximum-pool-size 와 minimum-idle 을 실제로 검사하는 곳 : main · RuntimeNumericBoundsValidator.java:22 "spring.datasource.hikari.maximum-pool-size", "APP_DATASOURCE_POOL_MAX_SIZE", 1), main · RuntimeNumericBoundsValidator.java:26 new Bound("spring.datasource.hikari.minimum-idle", "APP_DATASOURCE_POOL_MIN_IDLE", 0), app-bootstrap 이 HikariCP 를 선언하는 구성 : build.gradle:140: testImplementation 'com.zaxxer:HikariCP' 그런데 runtimeClasspath 를 풀어 보면 : | | | \--- com.zaxxer:HikariCP:7.0.2 +--- com.zaxxer:HikariCP:{strictly 7.0.2} -> 7.0.2 (c) # 그 부재를 고정하는 회귀 테스트 test · JpaResolvedDataSourceValidationTest.java:45 @DisplayName("no production code binds the parallel namespace") test · JpaResolvedDataSourceValidationTest.java:46 void theParallelNamespaceIsGone() { test · JpaResolvedDataSourceValidationTest.java:70 @DisplayName("an unreachable database fails at startup, not at the first query") test · JpaResolvedDataSourceValidationTest.java:71 void anUnreachableDatabaseFailsAtStartup() { test · JpaResolvedDataSourceValidationTest.java:91 @DisplayName("the product check applies only where PostgreSQL is the selected vendor") test · JpaResolvedDataSourceValidationTest.java:92 void theProductCheckFollowsTheVendorSelector() { 그 테스트가 훑는 범위 : JpaResolvedDataSourceValidationTest.java:115 private static Stream productionSources() { JpaResolvedDataSourceValidationTest.java:116 Path root = repositoryRoot().resolve("src"); JpaResolvedDataSourceValidationTest.java:117 return Stream.of("app-bootstrap", "adapter/outbound/persistence-jpa") JpaResolvedDataSourceValidationTest.java:118 .map(root::resolve) JpaResolvedDataSourceValidationTest.java:119 .filter(Files::isDirectory) 공허한 통과를 막는 단언과 주석 제거 : JpaResolvedDataSourceValidationTest.java:47 List sources = productionSources().toList(); JpaResolvedDataSourceValidationTest.java:48 JpaResolvedDataSourceValidationTest.java:49 assertThat(sources) JpaResolvedDataSourceValidationTest.java:50 .as("a scan that reached no source would report every binding as absent") JpaResolvedDataSourceValidationTest.java:51 .isNotEmpty(); JpaResolvedDataSourceValidationTest.java:52 JpaResolvedDataSourceValidationTest.java:53 // Comments are stripped: a sentence recording that the namespace was retired is the opposite of JpaResolvedDataSourceValidationTest.java:54 // the defect, which was a binder reading a namespace nothing supplied. JpaResolvedDataSourceValidationTest.java:55 List offenders = JpaResolvedDataSourceValidationTest.java:56 sources.stream() JpaResolvedDataSourceValidationTest.java:57 .filter(path -> withoutJavaComments(read(path)).contains("app.jpa-platform.datasource")) JpaResolvedDataSourceValidationTest.java:58 .map(path -> path.getFileName().toString()) JpaResolvedDataSourceValidationTest.java:59 .sorted() JpaResolvedDataSourceValidationTest.java:60 .toList();