diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java index 6543460..fa9b5cf 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java @@ -3,14 +3,16 @@ package dev.caskeleton.bootstrap.runtime; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.util.Locale; import java.util.Set; -import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.env.Environment; /** * Prevents Hibernate from becoming a production schema writer. Flyway owns the physical schema; * production may only disable Hibernate DDL or validate the schema. */ -public class JpaSchemaSafetyValidator implements SmartInitializingSingleton { +public class JpaSchemaSafetyValidator implements BeanFactoryPostProcessor { static final String DDL_AUTO_KEY = "spring.jpa.hibernate.ddl-auto"; static final String DDL_AUTO_ENV_KEY = "APP_DATASOURCE_DDL_AUTO"; @@ -24,8 +26,18 @@ public class JpaSchemaSafetyValidator implements SmartInitializingSingleton { this.environment = environment; } + /** + * {@code BeanFactoryPostProcessor} 이지 {@code SmartInitializingSingleton} 이 아닌 이유: 후자는 모든 싱글턴이 + * 만들어진 뒤에 돈다. {@code entityManagerFactory} 도 그 싱글턴 중 하나이고, Hibernate 는 그것을 만들면서 {@code + * ddl-auto} 를 이미 적용한다 — 실측으로 확인했다: {@code ddl-auto=update} 로 prod 를 띄우면 로그에 "Initialized JPA + * EntityManagerFactory" 가 먼저, 그 다음에 이 가드의 PROFILE_MISMATCH 가 찍히고, 스키마에는 그 사이에 만들어진 테이블이 남는다. + * + *
즉 가드가 트래픽은 막았지만 스키마 변조는 못 막고 있었다. 잘못 설정된 배포가 운영 DB 를 이미 바꿔 놓고 실패하는 셈이라, 검사를 빈 인스턴스화 이전으로
+ * 옮긴다.
+ */
@Override
- public void afterSingletonsInstantiated() {
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+ throws BeansException {
if (!isProdActive()) {
return;
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeSafetyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeSafetyConfig.java
index 426de3c..8e42909 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeSafetyConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeSafetyConfig.java
@@ -29,8 +29,12 @@ public class RuntimeSafetyConfig {
return new OpenInViewSafetyValidator(environment);
}
+ /**
+ * {@code static} 이어야 한다 — {@code BeanFactoryPostProcessor} 는 다른 빈보다 먼저 만들어지므로, 인스턴스 메서드로 두면 이 설정
+ * 클래스 전체가 too-early 로 초기화되어 경고가 뜬다.
+ */
@Bean
- JpaSchemaSafetyValidator jpaSchemaSafetyValidator(Environment environment) {
+ static JpaSchemaSafetyValidator jpaSchemaSafetyValidator(Environment environment) {
return new JpaSchemaSafetyValidator(environment);
}
diff --git a/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/service/UploadStudioAssetUseCase.java b/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/service/UploadStudioAssetUseCase.java
index 8fdad45..89bfcc6 100644
--- a/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/service/UploadStudioAssetUseCase.java
+++ b/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/service/UploadStudioAssetUseCase.java
@@ -86,27 +86,44 @@ public class UploadStudioAssetUseCase implements CommandUseCase