diff --git a/data-jpa-testcontainers/README.md b/data-jpa-testcontainers/README.md index 861476d..9207a99 100644 --- a/data-jpa-testcontainers/README.md +++ b/data-jpa-testcontainers/README.md @@ -6,9 +6,19 @@ Companion module for [`@DataJpaTest` with Testcontainers `@ServiceConnection` on Six JUnit 5 test classes against one JPA repository (`Product`), each isolating one fact about what `@DataJpaTest`'s default embedded-database substitution actually does, what -`@ServiceConnection` wires in its place, and what the defaults do not do (Flyway migrations are -not one of them). No `docs/NN-topic.md` chapters — everything beyond what fits in the post itself -is in the Javadoc on these six test classes and in the captured transcripts below. +`@ServiceConnection` wires in its place, and what the defaults do run that you might not expect +(Flyway migrations, as it turns out, are one of them). No `docs/NN-topic.md` chapters — +everything beyond what fits in the post itself is in the Javadoc on these six test classes and in +the captured transcripts below. + +**Correction, made before this module was published:** an earlier draft of two of these classes +asserted that `@DataJpaTest` never runs Flyway or Liquibase migrations, on the strength of reading +its four documented meta-annotations' `.imports` manifests and finding no mention of either tool. +That reading was accurate and incomplete — see `FlywayRunsByDefaultTest`'s Javadoc and +`docs/output/06-autoconfigurejdbc-meta-annotation-javap.txt` for the fifth, undocumented-for-this-slice +meta-annotation that actually wires them in. The two test classes below that carried the wrong +premise (`FlywayDoesNotRunUnderPlainDataJpaTestTest`, `FlywayRunsWhenExplicitlyImportedTest`) were +replaced, not patched, with `FlywayRunsByDefaultTest` and `ExplicitFlywayImportIsRedundantTest`. **This module was built and tested in a sandbox with no Docker daemon available.** Five of the six test classes run and pass (or cleanly skip) without one. The sixth demonstrates, on purpose, the @@ -29,7 +39,7 @@ the test table below for exactly which is which. ## Quickstart ```bash -mvn test # 6 of 7 run; 1 skips cleanly +mvn test # 9 tests run; 1 skips cleanly mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest # the one deliberate failure ``` @@ -38,8 +48,8 @@ mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest # the one delibera | Class | What it proves | Needs Docker | Outcome here | |---|---|---|---| | `DefaultReplacementTest` | Plain `@DataJpaTest` substitutes an embedded H2 DataSource, and each test method is rolled back independently of execution order | No | Pass | -| `FlywayDoesNotRunUnderPlainDataJpaTestTest` | `@DataJpaTest`'s own auto-configuration import list never includes `FlywayAutoConfiguration` or `LiquibaseAutoConfiguration` — your migrations never run, substitution or not | No | Pass | -| `FlywayRunsWhenExplicitlyImportedTest` | The one-line fix: `@ImportAutoConfiguration(FlywayAutoConfiguration.class)` | No | Pass | +| `FlywayRunsByDefaultTest` | A plain `@DataJpaTest`, with flyway-core and `spring-boot-starter-flyway` on the (test) classpath, runs the real Flyway migration by default — no `@ImportAutoConfiguration` needed, because `@AutoConfigureJdbc` is itself meta-annotated with `@AutoConfigureDataSourceInitialization`, which merges contributions from every jar on the classpath that ships one | No | Pass | +| `ExplicitFlywayImportIsRedundantTest` | Explicitly re-adding `@ImportAutoConfiguration(FlywayAutoConfiguration.class)` — the old advice for the (incorrect) premise above — changes nothing: still exactly one `flyway` bean, still exactly one applied migration | No | Pass | | `NoSubstitutionStillSilentlyUsesH2Test` | `@AutoConfigureTestDatabase(replace = Replace.NONE)` alone is not a guarantee of a real connection — with nothing else configured, ordinary `DataSourceAutoConfiguration` still silently hands you embedded H2 | No | Pass | | `ServiceConnectionSkipsCleanlyTest` | The intended shape: `@ServiceConnection` + `@Container` + `replace = Replace.NONE`, guarded by Testcontainers 2.0's new `@EnabledIfDockerAvailable` | Yes (guarded) | Skipped | | `ServiceConnectionWithoutGuardFailsToStartTest` | The same class, minus the guard | Yes (unguarded) | **Fails — on purpose.** Excluded from `mvn test` by name in `pom.xml`; run it explicitly | @@ -48,11 +58,12 @@ mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest # the one delibera | File | Contents | |---|---| -| `docs/output/00-full-test-run.txt` | `mvn clean test` — 7 tests, 1 skip, build green | +| `docs/output/00-full-test-run.txt` | `mvn clean test` — 9 tests, 1 skip, build green | | `docs/output/01-postgresqlcontainer-relocation-javap.txt` | `javap` proof that `org.testcontainers.postgresql.PostgreSQLContainer` (2.0.5's new home) and the deprecated `org.testcontainers.containers.PostgreSQLContainer` shim live in the *same* jar, and that the old class alone keeps the self-referential `` generic | | `docs/output/02-serviceconnection-unaffected-javap.txt` | `javap` proof that `JdbcContainerConnectionDetailsFactory` is typed against `org.testcontainers.containers.JdbcDatabaseContainer` — unmoved by the 2.0 package split, which is why `@ServiceConnection` needed no changes | | `docs/output/04-service-connection-without-guard-fails.txt` | The real `ContainerFetchException` / `IllegalStateException` from trying to start a Postgres container with no Docker daemon present, and no guard against that | | `docs/output/05-service-connection-skips-cleanly.txt` | The same absence of Docker, this time behind `@EnabledIfDockerAvailable` — one line, `Skipped: 1`, no failure | +| `docs/output/06-autoconfigurejdbc-meta-annotation-javap.txt` | `javap` proof that `AutoConfigureJdbc.class` is meta-annotated with `@AutoConfigureDataSourceInitialization`, plus the matching `META-INF/spring/...AutoConfigureDataSourceInitialization.imports` file contents from both `spring-boot-jdbc-test` and `spring-boot-flyway` — the actual mechanism behind `FlywayRunsByDefaultTest` | ## What's sourced from code, not run here diff --git a/data-jpa-testcontainers/docs/output/00-full-test-run.txt b/data-jpa-testcontainers/docs/output/00-full-test-run.txt index 56ee610..5f2e415 100644 --- a/data-jpa-testcontainers/docs/output/00-full-test-run.txt +++ b/data-jpa-testcontainers/docs/output/00-full-test-run.txt @@ -36,10 +36,10 @@ WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release [INFO] ------------------------------------------------------- Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes= [INFO] Running com.ankurm.datajpa.DefaultReplacementTest -17:19:49.092 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.DefaultReplacementTest]: DefaultReplacementTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -17:19:49.365 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.DefaultReplacementTest -17:19:49.370 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.DefaultReplacementTest]: DefaultReplacementTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -17:19:49.405 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.DefaultReplacementTest +17:55:42.824 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.DefaultReplacementTest]: DefaultReplacementTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +17:55:43.042 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.DefaultReplacementTest +17:55:43.046 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.DefaultReplacementTest]: DefaultReplacementTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +17:55:43.073 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.DefaultReplacementTest . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ @@ -50,39 +50,39 @@ Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cace :: Spring Boot :: (v4.1.1) -2026-10-08T17:19:50.032+05:30 INFO 2231 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : Starting DefaultReplacementTest using Java 25.0.4.1 with PID 2231 (started by root in /home/claude/spring-boot-demo/data-jpa-testcontainers) -2026-10-08T17:19:50.034+05:30 INFO 2231 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : No active profile set, falling back to 1 default profile: "default" -2026-10-08T17:19:50.614+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. -2026-10-08T17:19:50.751+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 126 ms. Found 1 JPA repository interface. -2026-10-08T17:19:50.849+05:30 INFO 2231 --- [ main] beddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version -2026-10-08T17:19:51.122+05:30 INFO 2231 --- [ main] o.s.j.d.e.EmbeddedDatabaseFactory : Starting embedded database: url='jdbc:h2:mem:27737b58-793d-4017-ae78-1a78ebfc5b27;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa' -2026-10-08T17:19:51.815+05:30 INFO 2231 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:h2:mem:27737b58-793d-4017-ae78-1a78ebfc5b27 (H2 2.4) -2026-10-08T17:19:51.888+05:30 WARN 2231 --- [ main] o.f.c.internal.database.base.Database : Using H2 2.4.240 which is newer than the version Flyway has been verified with. The latest verified version of H2 is 2.3.232. -2026-10-08T17:19:51.949+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet -2026-10-08T17:19:51.952+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.040s) -2026-10-08T17:19:51.960+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ... -2026-10-08T17:19:52.002+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >> -2026-10-08T17:19:52.015+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - create product" -2026-10-08T17:19:52.034+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.008s) -2026-10-08T17:19:52.268+05:30 INFO 2231 --- [ main] org.hibernate.orm.jpa : HHH008540: Processing PersistenceUnitInfo [name: default] -2026-10-08T17:19:52.332+05:30 INFO 2231 --- [ main] org.hibernate.orm.core : HHH000001: Hibernate ORM core version 7.4.5.Final -2026-10-08T17:19:52.886+05:30 INFO 2231 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer -2026-10-08T17:19:53.006+05:30 INFO 2231 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: - Database JDBC URL [jdbc:h2:mem:27737b58-793d-4017-ae78-1a78ebfc5b27] +2026-10-08T17:55:43.593+05:30 INFO 10940 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : Starting DefaultReplacementTest using Java 25.0.4.1 with PID 10940 (started by root in /home/claude/spring-boot-demo/data-jpa-testcontainers) +2026-10-08T17:55:43.595+05:30 INFO 10940 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : No active profile set, falling back to 1 default profile: "default" +2026-10-08T17:55:44.217+05:30 INFO 10940 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2026-10-08T17:55:44.349+05:30 INFO 10940 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 118 ms. Found 1 JPA repository interface. +2026-10-08T17:55:44.495+05:30 INFO 10940 --- [ main] beddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version +2026-10-08T17:55:44.784+05:30 INFO 10940 --- [ main] o.s.j.d.e.EmbeddedDatabaseFactory : Starting embedded database: url='jdbc:h2:mem:4440be02-551f-486e-a4b1-2182bb84c7a5;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa' +2026-10-08T17:55:45.448+05:30 INFO 10940 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:h2:mem:4440be02-551f-486e-a4b1-2182bb84c7a5 (H2 2.4) +2026-10-08T17:55:45.514+05:30 WARN 10940 --- [ main] o.f.c.internal.database.base.Database : Using H2 2.4.240 which is newer than the version Flyway has been verified with. The latest verified version of H2 is 2.3.232. +2026-10-08T17:55:45.551+05:30 INFO 10940 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet +2026-10-08T17:55:45.555+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.014s) +2026-10-08T17:55:45.582+05:30 INFO 10940 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ... +2026-10-08T17:55:45.640+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >> +2026-10-08T17:55:45.652+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - create product" +2026-10-08T17:55:45.750+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.016s) +2026-10-08T17:55:46.001+05:30 INFO 10940 --- [ main] org.hibernate.orm.jpa : HHH008540: Processing PersistenceUnitInfo [name: default] +2026-10-08T17:55:46.079+05:30 INFO 10940 --- [ main] org.hibernate.orm.core : HHH000001: Hibernate ORM core version 7.4.5.Final +2026-10-08T17:55:46.707+05:30 INFO 10940 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer +2026-10-08T17:55:46.832+05:30 INFO 10940 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: + Database JDBC URL [jdbc:h2:mem:4440be02-551f-486e-a4b1-2182bb84c7a5] Database driver: H2 JDBC Driver Database dialect: H2Dialect Database version: 2.4.240 - Default catalog/schema: 27737B58-793D-4017-AE78-1A78EBFC5B27/PUBLIC + Default catalog/schema: 4440BE02-551F-486E-A4B1-2182BB84C7A5/PUBLIC Autocommit mode: undefined/unknown Isolation level: READ_COMMITTED [default READ_COMMITTED] JDBC fetch size: 100 Pool: DataSourceConnectionProvider Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown -2026-10-08T17:19:54.129+05:30 INFO 2231 --- [ main] org.hibernate.orm.core : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2026-10-08T17:19:54.145+05:30 INFO 2231 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' -2026-10-08T17:19:54.332+05:30 INFO 2231 --- [ main] o.s.d.j.r.query.QueryEnhancerFactories : Hibernate is in classpath; If applicable, HQL parser will be used. -2026-10-08T17:19:54.604+05:30 INFO 2231 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : Started DefaultReplacementTest in 5.076 seconds (process running for 7.153) +2026-10-08T17:55:47.973+05:30 INFO 10940 --- [ main] org.hibernate.orm.core : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2026-10-08T17:55:47.989+05:30 INFO 10940 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' +2026-10-08T17:55:48.156+05:30 INFO 10940 --- [ main] o.s.d.j.r.query.QueryEnhancerFactories : Hibernate is in classpath; If applicable, HQL parser will be used. +2026-10-08T17:55:48.476+05:30 INFO 10940 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : Started DefaultReplacementTest in 5.325 seconds (process running for 7.158) Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended WARNING: A Java agent has been loaded dynamically (/root/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.11/byte-buddy-agent-1.18.11.jar) @@ -95,12 +95,12 @@ JDBC reports database product name: H2 Hibernate: insert into product (name,sku,id) values (?,?,default) Hibernate: select p1_0.id,p1_0.name,p1_0.sku from product p1_0 where p1_0.sku=? Hibernate: select count(*) from product p1_0 -[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.329 s -- in com.ankurm.datajpa.DefaultReplacementTest +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.500 s -- in com.ankurm.datajpa.DefaultReplacementTest [INFO] Running com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test -2026-10-08T17:19:56.108+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test]: NoSubstitutionStillSilentlyUsesH2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -2026-10-08T17:19:56.189+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test -2026-10-08T17:19:56.197+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test]: NoSubstitutionStillSilentlyUsesH2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -2026-10-08T17:19:56.238+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test +2026-10-08T17:55:50.002+05:30 INFO 10940 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test]: NoSubstitutionStillSilentlyUsesH2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +2026-10-08T17:55:50.040+05:30 INFO 10940 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test +2026-10-08T17:55:50.041+05:30 INFO 10940 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test]: NoSubstitutionStillSilentlyUsesH2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +2026-10-08T17:55:50.069+05:30 INFO 10940 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ @@ -111,74 +111,75 @@ Hibernate: select count(*) from product p1_0 :: Spring Boot :: (v4.1.1) -2026-10-08T17:19:56.294+05:30 INFO 2231 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : Starting NoSubstitutionStillSilentlyUsesH2Test using Java 25.0.4.1 with PID 2231 (started by root in /home/claude/spring-boot-demo/data-jpa-testcontainers) -2026-10-08T17:19:56.295+05:30 INFO 2231 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : No active profile set, falling back to 1 default profile: "default" -2026-10-08T17:19:56.452+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. -2026-10-08T17:19:56.469+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 13 ms. Found 1 JPA repository interface. -2026-10-08T17:19:56.642+05:30 INFO 2231 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... -2026-10-08T17:19:56.665+05:30 INFO 2231 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn9: url=jdbc:h2:mem:fb5a3fcb-6cc0-4a37-baff-7dbd0bfb51fd user=SA -2026-10-08T17:19:56.667+05:30 INFO 2231 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. -2026-10-08T17:19:56.673+05:30 INFO 2231 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:h2:mem:fb5a3fcb-6cc0-4a37-baff-7dbd0bfb51fd (H2 2.4) -2026-10-08T17:19:56.686+05:30 WARN 2231 --- [ main] o.f.c.internal.database.base.Database : Using H2 2.4.240 which is newer than the version Flyway has been verified with. The latest verified version of H2 is 2.3.232. -2026-10-08T17:19:56.690+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet -2026-10-08T17:19:56.691+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.002s) -2026-10-08T17:19:56.693+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ... -2026-10-08T17:19:56.703+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >> -2026-10-08T17:19:56.707+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - create product" -2026-10-08T17:19:56.715+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.004s) -2026-10-08T17:19:56.752+05:30 INFO 2231 --- [ main] org.hibernate.orm.jpa : HHH008540: Processing PersistenceUnitInfo [name: default] -2026-10-08T17:19:56.808+05:30 INFO 2231 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer -2026-10-08T17:19:56.814+05:30 INFO 2231 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: - Database JDBC URL [jdbc:h2:mem:fb5a3fcb-6cc0-4a37-baff-7dbd0bfb51fd] +2026-10-08T17:55:50.166+05:30 INFO 10940 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : Starting NoSubstitutionStillSilentlyUsesH2Test using Java 25.0.4.1 with PID 10940 (started by root in /home/claude/spring-boot-demo/data-jpa-testcontainers) +2026-10-08T17:55:50.167+05:30 INFO 10940 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : No active profile set, falling back to 1 default profile: "default" +2026-10-08T17:55:50.366+05:30 INFO 10940 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2026-10-08T17:55:50.381+05:30 INFO 10940 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 12 ms. Found 1 JPA repository interface. +2026-10-08T17:55:50.538+05:30 INFO 10940 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... +2026-10-08T17:55:50.563+05:30 INFO 10940 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn9: url=jdbc:h2:mem:59a60d3a-9329-4255-85ca-9683b850ef31 user=SA +2026-10-08T17:55:50.566+05:30 INFO 10940 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. +2026-10-08T17:55:50.573+05:30 INFO 10940 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:h2:mem:59a60d3a-9329-4255-85ca-9683b850ef31 (H2 2.4) +2026-10-08T17:55:50.597+05:30 WARN 10940 --- [ main] o.f.c.internal.database.base.Database : Using H2 2.4.240 which is newer than the version Flyway has been verified with. The latest verified version of H2 is 2.3.232. +2026-10-08T17:55:50.607+05:30 INFO 10940 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet +2026-10-08T17:55:50.608+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.005s) +2026-10-08T17:55:50.610+05:30 INFO 10940 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ... +2026-10-08T17:55:50.723+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >> +2026-10-08T17:55:50.735+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - create product" +2026-10-08T17:55:50.745+05:30 INFO 10940 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.006s) +2026-10-08T17:55:50.829+05:30 INFO 10940 --- [ main] org.hibernate.orm.jpa : HHH008540: Processing PersistenceUnitInfo [name: default] +2026-10-08T17:55:50.897+05:30 INFO 10940 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer +2026-10-08T17:55:50.902+05:30 INFO 10940 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: + Database JDBC URL [jdbc:h2:mem:59a60d3a-9329-4255-85ca-9683b850ef31] Database driver: H2 JDBC Driver Database dialect: H2Dialect Database version: 2.4.240 - Default catalog/schema: FB5A3FCB-6CC0-4A37-BAFF-7DBD0BFB51FD/PUBLIC + Default catalog/schema: 59A60D3A-9329-4255-85CA-9683B850EF31/PUBLIC Autocommit mode: undefined/unknown Isolation level: READ_COMMITTED [default READ_COMMITTED] JDBC fetch size: 100 Pool: DataSourceConnectionProvider Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown -2026-10-08T17:19:56.850+05:30 INFO 2231 --- [ main] org.hibernate.orm.core : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) -2026-10-08T17:19:56.851+05:30 INFO 2231 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' -2026-10-08T17:19:56.915+05:30 INFO 2231 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : Started NoSubstitutionStillSilentlyUsesH2Test in 0.668 seconds (process running for 9.464) +2026-10-08T17:55:50.956+05:30 INFO 10940 --- [ main] org.hibernate.orm.core : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) +2026-10-08T17:55:50.958+05:30 INFO 10940 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' +2026-10-08T17:55:51.035+05:30 INFO 10940 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : Started NoSubstitutionStillSilentlyUsesH2Test in 0.949 seconds (process running for 9.717) JDBC reports database product name: H2 (even with replace = Replace.NONE and no @ServiceConnection anywhere) -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.852 s -- in com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test -[INFO] Running com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest -2026-10-08T17:19:56.952+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest]: FlywayDoesNotRunUnderPlainDataJpaTestTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -2026-10-08T17:19:57.008+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest -2026-10-08T17:19:57.008+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest]: FlywayDoesNotRunUnderPlainDataJpaTestTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -2026-10-08T17:19:57.064+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.137 s -- in com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest -[INFO] Running com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest -2026-10-08T17:19:57.091+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest]: FlywayRunsWhenExplicitlyImportedTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -2026-10-08T17:19:57.107+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest -2026-10-08T17:19:57.107+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest]: FlywayRunsWhenExplicitlyImportedTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. -2026-10-08T17:19:57.132+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest -flyway_schema_history row: version=1 description=create product success=true -[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.085 s -- in com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest -2026-10-08T17:19:57.218+05:30 INFO 2231 --- [ main] org.testcontainers.DockerClientFactory : Testcontainers version: 2.0.5 +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.083 s -- in com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test +[INFO] Running com.ankurm.datajpa.ExplicitFlywayImportIsRedundantTest +2026-10-08T17:55:51.081+05:30 INFO 10940 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.ExplicitFlywayImportIsRedundantTest]: ExplicitFlywayImportIsRedundantTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +2026-10-08T17:55:51.135+05:30 INFO 10940 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.ExplicitFlywayImportIsRedundantTest +2026-10-08T17:55:51.139+05:30 INFO 10940 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.ExplicitFlywayImportIsRedundantTest]: ExplicitFlywayImportIsRedundantTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +2026-10-08T17:55:51.195+05:30 INFO 10940 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.ExplicitFlywayImportIsRedundantTest +flyway_schema_history real migration rows (rank >= 0): 1 +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.226 s -- in com.ankurm.datajpa.ExplicitFlywayImportIsRedundantTest +2026-10-08T17:55:51.324+05:30 INFO 10940 --- [ main] org.testcontainers.DockerClientFactory : Testcontainers version: 2.0.5 WARNING: A restricted method in java.lang.System has been called WARNING: java.lang.System::load has been called by com.sun.jna.Native in an unnamed module (file:/root/.m2/repository/net/java/dev/jna/jna/5.18.1/jna-5.18.1.jar) WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module WARNING: Restricted methods will be blocked in a future release unless native access is enabled -2026-10-08T17:19:57.537+05:30 INFO 2231 --- [ main] .t.d.DockerMachineClientProviderStrategy : docker-machine executable was not found on PATH ([/opt/jdk-25/bin, /home/claude/.npm-global/bin, /nix/var/nix/profiles/default/bin, /root/.local/bin, /root/.cargo/bin, /usr/local/go/bin, /opt/node22/bin, /opt/maven/bin, /opt/gradle/bin, /opt/rbenv/bin, /root/.bun/bin, /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin]) -2026-10-08T17:19:57.538+05:30 ERROR 2231 --- [ main] o.t.d.DockerClientProviderStrategy : Could not find a valid Docker environment. Please check configuration. Attempted configurations were: +2026-10-08T17:55:51.689+05:30 INFO 10940 --- [ main] .t.d.DockerMachineClientProviderStrategy : docker-machine executable was not found on PATH ([/opt/jdk-25/bin, /home/claude/.npm-global/bin, /nix/var/nix/profiles/default/bin, /root/.local/bin, /root/.cargo/bin, /usr/local/go/bin, /opt/node22/bin, /opt/maven/bin, /opt/gradle/bin, /opt/rbenv/bin, /root/.bun/bin, /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin]) +2026-10-08T17:55:51.691+05:30 ERROR 10940 --- [ main] o.t.d.DockerClientProviderStrategy : Could not find a valid Docker environment. Please check configuration. Attempted configurations were: UnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (Could not find unix domain socket). Root cause NoSuchFileException (/var/run/docker.sock) DockerDesktopClientProviderStrategy: failed with exception NullPointerException (Cannot invoke "java.nio.file.Path.toString()" because the return value of "org.testcontainers.dockerclient.DockerDesktopClientProviderStrategy.getSocketPath()" is null)As no valid configuration was found, execution cannot continue. See https://java.testcontainers.org/on_failure.html for more details. [INFO] Running com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest -[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.001 s -- in com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest +[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.009 s -- in com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest +[INFO] Running com.ankurm.datajpa.FlywayRunsByDefaultTest +2026-10-08T17:55:51.702+05:30 INFO 10940 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayRunsByDefaultTest]: FlywayRunsByDefaultTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +2026-10-08T17:55:51.716+05:30 INFO 10940 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayRunsByDefaultTest +2026-10-08T17:55:51.717+05:30 INFO 10940 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayRunsByDefaultTest]: FlywayRunsByDefaultTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. +2026-10-08T17:55:51.731+05:30 INFO 10940 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayRunsByDefaultTest +flyway_schema_history row: version=1 description=create product success=true +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.079 s -- in com.ankurm.datajpa.FlywayRunsByDefaultTest [INFO] [INFO] Results: [INFO] -[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 1 +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 1 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 15.580 s -[INFO] Finished at: 2026-10-08T17:19:57+05:30 +[INFO] Total time: 14.464 s +[INFO] Finished at: 2026-10-08T17:55:51+05:30 [INFO] ------------------------------------------------------------------------ diff --git a/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt b/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt index e46ce32..4575181 100644 --- a/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt +++ b/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt @@ -75,3 +75,8 @@ $ javap -v org/testcontainers/containers/PostgreSQLContainer.class | grep -i dep Deprecated: true java.lang.Deprecated Deprecated: true + +$ unzip -l /root/.m2/repository/org/testcontainers/testcontainers-postgresql/2.0.5/testcontainers-postgresql-2.0.5.jar | grep PostgreSQLContainer + 5533 2026-04-06 22:36 org/testcontainers/postgresql/PostgreSQLContainer.class + 1745 2026-04-06 22:36 org/testcontainers/containers/PostgreSQLContainerProvider.class + 6150 2026-04-06 22:36 org/testcontainers/containers/PostgreSQLContainer.class diff --git a/data-jpa-testcontainers/docs/output/06-autoconfigurejdbc-meta-annotation-javap.txt b/data-jpa-testcontainers/docs/output/06-autoconfigurejdbc-meta-annotation-javap.txt new file mode 100644 index 0000000..5f1aaa1 --- /dev/null +++ b/data-jpa-testcontainers/docs/output/06-autoconfigurejdbc-meta-annotation-javap.txt @@ -0,0 +1,40 @@ +$ javap -v org/springframework/boot/jdbc/test/autoconfigure/AutoConfigureJdbc.class +# unzipped from spring-boot-jdbc-test-4.1.1.jar +RuntimeVisibleAnnotations: + 0: #10(#11=[e#12.#13]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE] + ) + 1: #14(#11=e#15.#16) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME + ) + 2: #17() + java.lang.annotation.Documented + 3: #18() + java.lang.annotation.Inherited + 4: #19() + org.springframework.boot.autoconfigure.ImportAutoConfiguration + 5: #20() + org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization + +$ find . -iname '*DataSourceInitialization*' # inside spring-boot-jdbc-test-4.1.1.jar +./META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports + +$ cat META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports +org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration + +# the SAME resource path, inside spring-boot-flyway-4.1.1.jar instead: +$ cat META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports +org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration + +$ javap -v org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration.class # unzipped from spring-boot-flyway-4.1.1.jar + #41 = Utf8 Lorg/springframework/boot/autoconfigure/condition/ConditionalOnClass; + #42 = Utf8 value + #43 = Utf8 Lorg/flywaydb/core/Flyway; + #44 = Utf8 Lorg/springframework/context/annotation/Conditional; +-- + org.springframework.boot.autoconfigure.condition.ConditionalOnClass( + value=[class Lorg/flywaydb/core/Flyway;] + ) + 2: #44(#42=[c#45]) diff --git a/data-jpa-testcontainers/pom.xml b/data-jpa-testcontainers/pom.xml index 225e53c..1e089f9 100644 --- a/data-jpa-testcontainers/pom.xml +++ b/data-jpa-testcontainers/pom.xml @@ -47,9 +47,12 @@ + exact same starter). This is what actually makes Flyway run under @DataJpaTest by default + in this module: @AutoConfigureJdbc's @AutoConfigureDataSourceInitialization meta-annotation + merges a META-INF/spring/...AutoConfigureDataSourceInitialization.imports file from every + jar that ships one, and spring-boot-flyway (pulled in by this starter) is one of them — + confirmed with javap and unzip, see FlywayRunsByDefaultTest's Javadoc. Test scope only: + this article's main app never runs Flyway itself outside the test slice. --> org.springframework.boot spring-boot-starter-flyway diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ExplicitFlywayImportIsRedundantTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ExplicitFlywayImportIsRedundantTest.java new file mode 100644 index 0000000..4b143ae --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ExplicitFlywayImportIsRedundantTest.java @@ -0,0 +1,59 @@ +package com.ankurm.datajpa; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration; +import org.springframework.context.ApplicationContext; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Older advice for this exact situation (including an earlier draft of this module, and more + * than one blog post this article will not link to) says to add + * {@code @ImportAutoConfiguration(FlywayAutoConfiguration.class)} yourself to get Flyway running + * inside a {@code @DataJpaTest}. As {@link FlywayRunsByDefaultTest} now shows, that is no longer + * necessary on Spring Boot 4.1 -- the import already happens on its own, by way of + * {@code @AutoConfigureJdbc}'s own {@code @AutoConfigureDataSourceInitialization} meta-annotation. + * + *

Adding the explicit import anyway is not harmful. {@code @ImportAutoConfiguration} is + * backed by ordinary Spring auto-configuration machinery: the same fully-qualified class name, + * however many times it is reached (once through the transitive meta-annotation walk, once + * through this class's own explicit annotation), is only ever registered once. This test proves + * that directly rather than asserting it from the mechanism alone: exactly one {@code flyway} + * bean, and exactly one applied migration -- not two -- with the redundant import sitting right + * there on the class. + */ +@DataJpaTest +@ImportAutoConfiguration(FlywayAutoConfiguration.class) +class ExplicitFlywayImportIsRedundantTest { + + @Autowired + private DataSource dataSource; + + @Autowired + private ApplicationContext ctx; + + @Test + void redundantImportStillProducesExactlyOneFlywayBean() { + assertThat(ctx.getBeanNamesForType(org.flywaydb.core.Flyway.class)).hasSize(1); + } + + @Test + void redundantImportDoesNotReapplyTheMigration() throws Exception { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement()) { + var rs = statement.executeQuery( + "select count(*) from \"flyway_schema_history\" where \"installed_rank\" >= 0"); + assertThat(rs.next()).isTrue(); + int realMigrationRows = rs.getInt(1); + System.out.println("flyway_schema_history real migration rows (rank >= 0): " + realMigrationRows); + assertThat(realMigrationRows).isEqualTo(1); + } + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayDoesNotRunUnderPlainDataJpaTestTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayDoesNotRunUnderPlainDataJpaTestTest.java deleted file mode 100644 index 2ac9d33..0000000 --- a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayDoesNotRunUnderPlainDataJpaTestTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.ankurm.datajpa; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.ResultSet; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This module has a real Flyway migration, {@code V1__create_product.sql}, and flyway-core is a - * main-scope dependency. A plain @DataJpaTest never runs it anyway -- not because of the - * embedded-database substitution, but because @DataJpaTest's own curated set of auto-configuration - * imports simply does not include FlywayAutoConfiguration or LiquibaseAutoConfiguration at all. - * Confirmed by reading the actual import manifests Boot ships inside spring-boot-jdbc-test.jar and - * spring-boot-data-jpa-test.jar -- neither file mentions Flyway or Liquibase, in any form, under - * any replace setting. - * - * The {@code product} table exists in this test for a completely different reason: Hibernate's own - * {@code ddl-auto}, defaulted on because Boot never detects a schema-management tool in this - * slice's context. See {@link FlywayRunsWhenExplicitlyImportedTest} for the one-line fix. - */ -@DataJpaTest -class FlywayDoesNotRunUnderPlainDataJpaTestTest { - - @Autowired - private DataSource dataSource; - - @Test - void productTableExistsButFlywayNeverRan() throws Exception { - try (Connection connection = dataSource.getConnection()) { - try (ResultSet flywayTable = connection.getMetaData() - .getTables(null, null, "FLYWAY_SCHEMA_HISTORY", null)) { - assertThat(flywayTable.next()) - .as("flyway_schema_history should not exist -- Flyway never ran") - .isFalse(); - } - try (ResultSet productTable = connection.getMetaData() - .getTables(null, null, "PRODUCT", null)) { - assertThat(productTable.next()) - .as("product table should exist anyway -- Hibernate's ddl-auto made it") - .isTrue(); - } - } - } -} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsByDefaultTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsByDefaultTest.java new file mode 100644 index 0000000..21fe4a2 --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsByDefaultTest.java @@ -0,0 +1,92 @@ +package com.ankurm.datajpa; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.context.ApplicationContext; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This module has a real Flyway migration, {@code V1__create_product.sql}, and flyway-core + * (plus the test-scoped {@code spring-boot-starter-flyway} -- see the module README for why that + * second one matters) is on the classpath. A plain {@code @DataJpaTest}, with no extra + * annotation anywhere on this class, runs that migration by default. + * + *

An earlier draft of this module asserted the opposite, on the strength of reading + * {@code @DataJpaTest}'s four documented meta-annotations ({@code @AutoConfigureDataJpa}, + * {@code @AutoConfigureJdbc}, {@code @AutoConfigureTestDatabase}, + * {@code @AutoConfigureTestEntityManager}) and finding that none of their four + * {@code .imports} manifests mentions Flyway or Liquibase. That reading is correct and + * incomplete: {@code AutoConfigureJdbc.class} is itself meta-annotated with a fifth annotation, + * {@code @AutoConfigureDataSourceInitialization} (new in Boot 4.0; confirmed with + * {@code javap -v org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureJdbc} against + * {@code spring-boot-jdbc-test-4.1.1.jar}), and Spring's own + * {@code ImportAutoConfigurationImportSelector} walks the entire meta-annotation tree + * transitively -- every annotation, and every meta-annotation on those -- collecting every class + * in that tree that is itself annotated with {@code @ImportAutoConfiguration}. That fifth + * annotation is one of them. + * + *

{@code @AutoConfigureDataSourceInitialization}'s own {@code .imports} resource is loaded via + * {@code ClassLoader.getResources()}, which returns every file at that path across every jar on + * the classpath and merges them -- not a single-file lookup. Three jars ship a file at the exact + * same resource path + * ({@code META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureDataSourceInitialization.imports}): + * {@code spring-boot-jdbc-test} contributes {@code DataSourceInitializationAutoConfiguration}, + * {@code spring-boot-flyway} contributes {@code FlywayAutoConfiguration}, and + * {@code spring-boot-liquibase} contributes {@code LiquibaseAutoConfiguration}. Confirmed by + * unzipping both jars actually on this classpath and reading the file; the Liquibase side was + * not independently re-run here (no Liquibase module in this repo) but the same mechanism applies + * to it by construction. {@code FlywayAutoConfiguration} is still gated by the ordinary + * {@code @ConditionalOnClass(Flyway.class)} -- it only activates once flyway-core is genuinely on + * the classpath, which is the one part of the old draft's observation that was never wrong, just + * misattributed to the wrong mechanism. + * + *

See {@link ExplicitFlywayImportIsRedundantTest} for what this means for the (now + * unnecessary, but harmless) advice to {@code @ImportAutoConfiguration(FlywayAutoConfiguration.class)} + * yourself. + */ +@DataJpaTest +class FlywayRunsByDefaultTest { + + @Autowired + private DataSource dataSource; + + @Autowired + private ApplicationContext ctx; + + @Test + void flywayAutoConfigurationBeansArePresentWithNoExplicitImportAnywhere() { + assertThat(ctx.getBeanNamesForType(org.flywaydb.core.Flyway.class)).isNotEmpty(); + assertThat(ctx.containsBean("flywayInitializer")).isTrue(); + } + + @Test + void theMigrationActuallyRanAgainstTheSubstitutedDatabase() throws Exception { + // Flyway's own history table always carries a bookkeeping row at installed_rank = -1 + // ("<< Flyway Schema History table created >>") ahead of the real migration rows, and + // both the table name and every column name are stored quoted and lowercase -- H2's + // default unquoted-identifier folding (to uppercase) does not apply to any of them. + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery( + "select \"installed_rank\", \"version\", \"description\", \"success\" " + + "from \"flyway_schema_history\" where \"installed_rank\" = 1")) { + assertThat(rs.next()).isTrue(); + String version = rs.getString(2); + String description = rs.getString(3); + boolean success = rs.getBoolean(4); + System.out.println("flyway_schema_history row: version=" + version + + " description=" + description + " success=" + success); + assertThat(version).isEqualTo("1"); + assertThat(description).isEqualTo("create product"); + assertThat(success).isTrue(); + assertThat(rs.next()).isFalse(); + } + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsWhenExplicitlyImportedTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsWhenExplicitlyImportedTest.java deleted file mode 100644 index 1620615..0000000 --- a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsWhenExplicitlyImportedTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.ankurm.datajpa; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; -import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.Statement; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * The one-line fix for {@link FlywayDoesNotRunUnderPlainDataJpaTestTest}: pull - * FlywayAutoConfiguration into the slice yourself. Once it is in the context, Boot's own - * schema-management detection sees it, flips Hibernate's ddl-auto back to {@code none}, and the - * real migration runs against the same substituted (here, H2) DataSource. - */ -@DataJpaTest -@ImportAutoConfiguration(FlywayAutoConfiguration.class) -class FlywayRunsWhenExplicitlyImportedTest { - - @Autowired - private DataSource dataSource; - - @Test - void flywayHistoryTableProvesTheMigrationRanThisTime() throws Exception { - // Flyway's own history table always carries a bookkeeping row at installed_rank = -1 - // ("<< Flyway Schema History table created >>") ahead of the real migration rows -- - // easy to miss if a query only reads the first row back, which is what an earlier draft - // of this test did. The real V1 migration is installed_rank = 1. - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery( - "select \"installed_rank\", \"version\", \"description\", \"success\" " - + "from \"flyway_schema_history\" where \"installed_rank\" = 1")) { - assertThat(rs.next()).isTrue(); - String version = rs.getString(2); - String description = rs.getString(3); - boolean success = rs.getBoolean(4); - System.out.println("flyway_schema_history row: version=" + version - + " description=" + description + " success=" + success); - assertThat(version).isEqualTo("1"); - assertThat(description).isEqualTo("create product"); - assertThat(success).isTrue(); - assertThat(rs.next()).isFalse(); - } - } -}