Skip to content

Commit 3929562

Browse files
jeffjensenclaude
andcommitted
feat(spring): Validate JPA mappings via core audit, not ddl-auto=validate
The generated SchemaEntityValidationAuditIT no longer overrides ddl-auto=validate: the context boots under the default ddl-auto=none and SchemaEntityValidationAudit validates every entity mapping against the live schema, reporting all mismatches in one run instead of Hibernate's fail-fast startup validation that surfaces them one at a time. * Wire the audit via SchemaEntityValidationAudit.forEntityManagerFactory * Bump database-audits-core to 1.2.0 * Refresh the assertion message, archetype template, and audit docs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UkRk16pW4VEM3E6vrDoG5y
1 parent 74ffc64 commit 3929562

11 files changed

Lines changed: 45 additions & 25 deletions

File tree

CLAUDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ assertion API; these ITs show how to wire and call it. They mirror core's three
113113
run *after* the SQL is captured. What actually enforces order is the priming test `RepositoryWorkloadIT`
114114
(`@Order(Integer.MIN_VALUE)`, runs first, via `junit-platform.properties` enabling
115115
`ClassOrderer$OrderAnnotation`). **All example classes must use JUnit's `@Order`, not Spring's.**
116-
- The JPA example sets `@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=validate")`: the real check is
117-
Hibernate's startup schema validation; reaching the test means it passed.
116+
- The JPA example runs under the default `ddl-auto=none`: `SchemaEntityValidationAudit` walks Hibernate's entity
117+
mappings against the live schema and reports every mismatch in one run, instead of relying on Hibernate's
118+
fail-fast `ddl-auto=validate` startup check (which aborts on the first mismatch).
118119
- Most show `EXCLUDED_*` exclusion constants (relations, SQL fragments, columns, indexes, statements) — the
119120
intended way for consumers to suppress known/intentional violations instead of weakening the audit.
120121
`ForeignKeyIndexAuditIT` and `PrimaryKeyPresenceAuditIT` call the no-exclusion overload directly.

archetype/src/main/resources/META-INF/maven/archetype-metadata.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
</requiredProperty>
3939
<!-- When 'true', every generated test is annotated with JUnit's @Disabled, so the suite is generated and
4040
compiles but does not run. SchemaEntityValidationAuditIT is annotated on the class (not the method) so
41-
its ddl-auto=validate Spring context never loads. Default 'false' runs the audits. Useful for scaffolding
41+
its Spring context never loads. Default 'false' runs the audits. Useful for scaffolding
4242
the suite into a project that does not pass it yet, then enabling audits one at a time. -->
4343
<requiredProperty key="disabledTests">
4444
<defaultValue>false</defaultValue>

archetype/src/main/resources/archetype-resources/src/test/java/jpa/SchemaEntityValidationAuditIT.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#end
66
import org.junit.jupiter.api.Test;
77
import org.springframework.beans.factory.annotation.Autowired;
8-
import org.springframework.test.context.TestPropertySource;
98

109
#if($parentClass && $parentClass != '' && $parentClass != 'none')
1110
#set($simpleParentClass = $parentClass.replaceAll('.*\.', ''))
@@ -16,10 +15,10 @@
1615
import io.github.databaseaudits.spring.boot.assertion.SchemaEntityValidationAuditAssertion;
1716

1817
/**
19-
* The {@code ddl-auto=validate} setting makes Hibernate validate entity↔schema at startup; reaching the test means
20-
* it passed.
18+
* Validates that every JPA entity mapping matches the schema, reporting all mismatches in one run. It runs under
19+
* the default {@code ddl-auto=none}: Hibernate's own {@code validate} fails fast on the first mismatch and aborts
20+
* startup, so this audit walks the mappings against the live schema itself instead.
2121
*/
22-
@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=validate")
2322
#if($disabledTests == 'true')
2423
@Disabled("Generated as disabled; remove @Disabled to enable")
2524
#end

archetype/src/main/resources/archetype-resources/src/test/resources/application.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Demo harness configuration that lets the audit ITs run standalone.
22
# The DataSource (URL with preferQueryMode=simple, username, password) is supplied by DemoPostgresTestConfig via a
3-
# DynamicPropertyRegistrar. Liquibase owns the schema; Hibernate does not (ddl-auto=none) except the JPA example,
4-
# which overrides ddl-auto=validate to assert entity/schema agreement.
3+
# DynamicPropertyRegistrar. Liquibase owns the schema; Hibernate does not (ddl-auto=none) the JPA example audit
4+
# validates entity/schema agreement itself rather than relying on Hibernate's fail-fast startup validation.
55
spring.jpa.open-in-view=false
66
spring.jpa.hibernate.ddl-auto=none
77
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml

integration/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</description>
2020

2121
<properties>
22-
<database-audits-core.version>1.1.0</database-audits-core.version>
22+
<database-audits-core.version>1.2.0</database-audits-core.version>
2323
</properties>
2424

2525
<developers>

integration/src/main/java/io/github/databaseaudits/spring/boot/DatabaseAuditSuite.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ public class DatabaseAuditSuite {
7575
* runtime audits inspect; its platform is
7676
* auto-detected from its metadata.
7777
* @param entityManagerFactory
78-
* the entity-manager factory whose Hibernate
79-
* startup validation the JPA audit confirms.
78+
* the entity-manager factory whose entity
79+
* mappings the JPA audit validates against the
80+
* schema.
8081
* @param sqlCapturer
8182
* the SQL capturer the runtime audits read;
8283
* must be the same instance registered as the
@@ -110,7 +111,8 @@ public DatabaseAuditSuite(final DataSource dataSource,
110111
new RedundantIndexAudit(indexCatalog));
111112
this.schemaEntityValidationAuditAssertion =
112113
new SchemaEntityValidationAuditAssertion(
113-
new SchemaEntityValidationAudit(entityManagerFactory));
114+
SchemaEntityValidationAudit.forEntityManagerFactory(
115+
entityManagerFactory, dataSource));
114116
this.joinIndexAuditAssertion = new JoinIndexAuditAssertion(
115117
new JoinIndexAudit(queryPlanExplainer, sqlCapturer));
116118
this.orderByIndexAuditAssertion = new OrderByIndexAuditAssertion(

integration/src/main/java/io/github/databaseaudits/spring/boot/DatabaseAuditTestConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ HibernatePropertiesCustomizer sqlCaptureHibernatePropertiesCustomizer(
9292
* @param dataSource
9393
* the primary datasource to audit.
9494
* @param entityManagerFactory
95-
* the primary entity-manager factory the JPA
96-
* audit confirms.
95+
* the primary entity-manager factory whose
96+
* mappings the JPA audit validates against the
97+
* schema.
9798
* @param sqlCapturer
9899
* the shared SQL capturer the runtime audits
99100
* read.

integration/src/main/java/io/github/databaseaudits/spring/boot/assertion/SchemaEntityValidationAuditAssertion.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.github.databaseaudits.spring.boot.assertion;
22

3+
import java.util.Set;
4+
35
import io.github.databaseaudits.audit.jpa.SchemaEntityValidationAudit;
46

57
/**
@@ -9,8 +11,8 @@
911
public class SchemaEntityValidationAuditAssertion
1012
extends AbstractAuditAssertion {
1113
private static final String MESSAGE =
12-
"JPA entity mappings must match the schema — the EntityManagerFactory must be built under "
13-
+ "ddl-auto=validate (its startup validation is the real check).";
14+
"JPA entity mappings do not match the schema; reconcile each mapping with the schema "
15+
+ "(whichever drifted):";
1416

1517
private final SchemaEntityValidationAudit audit;
1618

@@ -31,4 +33,19 @@ public SchemaEntityValidationAuditAssertion(
3133
public void assertClean() {
3234
failOnViolations(MESSAGE, audit.audit());
3335
}
36+
37+
/**
38+
* Asserts that the JPA entity mappings match the schema, ignoring the
39+
* excluded relations.
40+
*
41+
* @param excludedRelations
42+
* relations to skip, each a table name or a
43+
* {@code table.column} pair — optionally
44+
* schema-qualified ({@code schema.table} or
45+
* {@code schema.table.column}) — matched
46+
* case-insensitively.
47+
*/
48+
public void assertClean(final String... excludedRelations) {
49+
failOnViolations(MESSAGE, audit.audit(Set.of(excludedRelations)));
50+
}
3451
}

integration/src/site/asciidoc/archetype.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ Pass only the ones that differ.
139139
| `disabledTests`
140140
| `false`
141141
| both
142-
| When `true`, annotates every generated test with JUnit's `@Disabled`, so the suite is generated and compiles but does not run. (`SchemaEntityValidationAuditIT` is annotated on the class rather than the method, so its `ddl-auto=validate` context never loads.) Useful for scaffolding the audits into a project that does not pass them yet — remove the `@Disabled` annotations to enable audits one at a time.
142+
| When `true`, annotates every generated test with JUnit's `@Disabled`, so the suite is generated and compiles but does not run. (`SchemaEntityValidationAuditIT` is annotated on the class rather than the method, so its Spring context never loads.) Useful for scaffolding the audits into a project that does not pass them yet — remove the `@Disabled` annotations to enable audits one at a time.
143143

144144
| `dataSourceNames`
145145
| `none`
@@ -258,8 +258,8 @@ The generated tests compile against your existing Spring Boot test context. Run
258258
* Generated classes are pure Java with no Lombok. Dependencies are JUnit Jupiter, Spring Boot test, and this
259259
integration artifact only.
260260
* `@Order` annotations use JUnit's `org.junit.jupiter.api.Order`, not Spring's.
261-
* `SchemaEntityValidationAuditIT` sets `@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=validate")`;
262-
reaching the test body means Hibernate's startup validation passed.
261+
* `SchemaEntityValidationAuditIT` validates Hibernate's entity mappings against the live schema and reports every
262+
mismatch in one run; it runs under the default `ddl-auto=none`, not Hibernate's fail-fast `ddl-auto=validate`.
263263
* Each catalog IT uses constants (e.g., `EXCLUDED_TABLES`, `EXCLUDED_CONSTRAINTS`) to name known/intentional
264264
exclusions. `ForeignKeyIndexAuditIT` and `PrimaryKeyPresenceAuditIT` call the no-exclusion overload directly
265265
to show that pattern as well.

integration/src/site/asciidoc/audits.adoc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,13 @@ void assertForeignKeysIndexed() {
6262

6363
| `SchemaEntityValidationAudit`
6464
| `SchemaEntityValidationAuditAssertion`
65-
| None. Set `spring.jpa.hibernate.ddl-auto=validate` so Hibernate validates the schema at `@SpringBootTest` startup; reaching `assertClean()` confirms that validation passed.
65+
| None. The audit walks Hibernate's entity mappings against the live schema and reports every mismatch (missing table, missing column, incompatible column type) in one run. Run under the default `ddl-auto=none`; do not set `ddl-auto=validate`, whose fail-fast startup check aborts the context on the first mismatch.
6666
|===
6767

6868
=== JPA audit example
6969

7070
[source,java]
7171
----
72-
@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=validate")
7372
class SchemaEntityValidationAuditIT extends AbstractDatabaseAuditIT {
7473
7574
@Autowired

0 commit comments

Comments
 (0)