fix: 하네스 제거 및 keycloak 문서 보강
This commit is contained in:
@@ -1 +0,0 @@
|
||||
../../vault/10-projects/errors/spring-jpa-flyway-circular-dependency-2026-06-23.md
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: error / spring-jpa-flyway-circular-dependency-2026-06-23
|
||||
source_type: error-note
|
||||
status: raw
|
||||
branch: feature-build-release-supply-chain-contract
|
||||
related_projects: [ca-skeleton]
|
||||
tags: [error, spring-boot, circular-dependency, flyway, jpa]
|
||||
created: 2026-06-23
|
||||
updated: 2026-06-23
|
||||
---
|
||||
|
||||
# Spring JPA-Flyway Circular Dependency during Context Initialization
|
||||
|
||||
## Parent
|
||||
- Parent branch note: [[raw/branch-notes/feature-build-release-supply-chain-contract]]
|
||||
|
||||
## Symptoms
|
||||
|
||||
During application startup using `bootRun`, the application context failed to initialize with a circular dependency error:
|
||||
|
||||
```text
|
||||
Description:
|
||||
|
||||
The dependencies of some of the beans in the application context form a cycle:
|
||||
|
||||
entityManagerFactory defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]
|
||||
┌─────┐
|
||||
| flyway defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]
|
||||
↑ ↓
|
||||
| postgreSqlPersistenceConfig
|
||||
└─────┘
|
||||
```
|
||||
|
||||
When running without a local database connection, this circularity prevented the application from failing fast with a connection error and instead produced secondary errors such as `NoSuchBeanDefinitionException` during context shutdown (e.g. `No bean named 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry' available`).
|
||||
|
||||
## Root Cause
|
||||
|
||||
1. The configuration class `PostgreSqlPersistenceConfig` uses `@PersistenceContext` to inject the JPA `EntityManager`:
|
||||
```java
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
```
|
||||
2. Creating `PostgreSqlPersistenceConfig` therefore requires the `EntityManager` (and consequently `EntityManagerFactory`) to be initialized and available.
|
||||
3. In `PostgreSqlPersistenceConfig`, a customizer bean was declared as a non-static `@Bean`:
|
||||
```java
|
||||
@Bean
|
||||
public FlywayConfigurationCustomizer postgreSqlFlywayLocationCustomizer() {
|
||||
return configuration -> configuration.locations("classpath:db/migration/postgresql");
|
||||
}
|
||||
```
|
||||
4. Because it is a non-static `@Bean` method, Spring requires instantiating `PostgreSqlPersistenceConfig` *before* it can call the method to register the customizer.
|
||||
5. However:
|
||||
- `EntityManagerFactory` depends on `flywayInitializer` (to ensure migrations run first).
|
||||
- `flywayInitializer` depends on the `Flyway` bean.
|
||||
- The `Flyway` bean depends on all registered `FlywayConfigurationCustomizer` beans.
|
||||
- Spring tries to resolve `FlywayConfigurationCustomizer` -> instantiates `PostgreSqlPersistenceConfig` -> injects `EntityManager` -> creates `EntityManagerFactory` -> waits for `flywayInitializer` -> waits for `Flyway` -> waits for `FlywayConfigurationCustomizer`.
|
||||
- This forms a cycle: `EntityManagerFactory` -> `flywayInitializer` -> `Flyway` -> `PostgreSqlPersistenceConfig` -> `EntityManagerFactory`.
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Make the Flyway customizer static
|
||||
Change the `FlywayConfigurationCustomizer` bean declaration to a `static @Bean` method in both `PostgreSqlPersistenceConfig.java` and `SamplePostgreSqlPersistenceConfig.java`:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public static FlywayConfigurationCustomizer postgreSqlFlywayLocationCustomizer() {
|
||||
return configuration -> configuration.locations("classpath:db/migration/postgresql");
|
||||
}
|
||||
```
|
||||
|
||||
This allows Spring to invoke the customizer registration without instantiating the enclosing configuration class, breaking the immediate `EntityManagerFactory` dependency cycle.
|
||||
|
||||
### 2. Refactor `@PersistenceContext` Field Injection to Parameter Injection
|
||||
To prevent the configuration classes from triggering early instantiation of the JPA infrastructure (which causes `NoSuchBeanDefinitionException: No bean named 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry' available`), eliminate the class-level `EntityManager` field injection and instead inject `EntityManager` as a parameter to the factory `@Bean` methods:
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
@Configuration
|
||||
public class PostgreSqlPersistenceConfig {
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Bean
|
||||
public OutboxClaimRepository outboxClaimRepository() {
|
||||
return new PostgreSqlOutboxClaimRepository(entityManager);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
@Configuration
|
||||
public class PostgreSqlPersistenceConfig {
|
||||
@Bean
|
||||
public OutboxClaimRepository outboxClaimRepository(EntityManager entityManager) {
|
||||
return new PostgreSqlOutboxClaimRepository(entityManager);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Why it works
|
||||
- **Static customizer**: Decouples customizer registration from the instantiation of the enclosing `@Configuration` class.
|
||||
- **Parameter injection**: Prevents Spring's configuration class post-processor from resolving the `EntityManager` bean prematurely during class creation, postponing its resolution until the specific factory method is executed. This completely avoids the early bootstrap lifecycle cycle.
|
||||
|
||||
## Verification & Outcomes
|
||||
|
||||
### Local Verification
|
||||
1. Ensured the local database container `ca-pg` is running on port `5432`.
|
||||
2. Cleaned and recreated the database using `psql` to clear any checksum mismatch issues.
|
||||
3. Ran `./gradlew :app-bootstrap:bootRun`.
|
||||
4. The application initialized the connection pool, ran Flyway migrations, and successfully booted:
|
||||
```text
|
||||
2026-06-23 14:32:00.619 INFO [main] o.f.core.internal.command.DbMigrate - Successfully applied 3 migrations to schema "public", now at version v4 (execution time 00:00.085s)
|
||||
...
|
||||
2026-06-23 14:32:03.278 INFO [main] d.c.bootstrap.CaSkeletonApplication - Started CaSkeletonApplication in 4.87 seconds (process running for 5.034)
|
||||
```
|
||||
Reference in New Issue
Block a user