Exclusions Guide
Audits are strictest by default. When a violation is intentional — a legacy table without a primary key, a nullable FK for a nullable relationship, a known full-table migration statement — exclude it rather than weakening the audit. Exclusions are named and visible in the test source, making the intent explicit.
When to exclude vs. when to fix
Fix the schema when the audit finding reflects a genuine structural problem (missing index, type mismatch).
Exclude when:
- A Liquibase migration or one-off script intentionally runs a full-table
UPDATE/DELETE. - A relation is known to be small enough that a sequential scan is appropriate and intentional.
- A FK is intentionally nullable (the relationship is optional).
- A third-party table (e.g., from an embedded library) cannot be modified.
The DatabaseAuditExcludes builder
DatabaseAuditExcludes is an immutable value object. Use DatabaseAuditExcludes.none() for no exclusions,
or the fluent builder to name each exclusion:
DatabaseAuditExcludes excludes = DatabaseAuditExcludes.builder()
.primaryKeyTables(Set.of("legacy_table_no_pk"))
.primaryKeyTypeColumns(Set.of("lookup_values.id")) // table.column
.duplicateForeignKeyConstraints(Set.of("fk_orders_customer_legacy"))
.foreignKeyIndexConstraints(Set.of("fk_unindexed_by_design"))
.foreignKeyNotNullColumns(Set.of("order.customer_id")) // table.column
.foreignKeyTypeMatchColumns(Set.of("event.entity_id")) // table.column
.redundantIndexes(Set.of("idx_status_redundant"))
.uniqueIndexNotNullIndexes(Set.of("uq_orders_active_customer"))
.jpaExcludedRelations(Set.of("legacy_reporting_view")) // table or table.column, case-insensitive
.unmappedDatabaseObjectRelations(Set.of("databasechangelog", "databasechangeloglock"))
.missingVersionEntities(Set.of("AuditLogEntry")) // fully-qualified name, simple name, or table
.eagerCollectionRoles(Set.of("com.acme.Order.items"))
.planRelations(Set.of("small_lookup_table")) // shared by WHERE/ORDER BY/JOIN audits
.planSqlFragments(List.of("UPDATE migration_flag")) // matched as substring
.unusedIndexes(Set.of("idx_orders_admin_report"))
.unconditionalMutationStatements(Set.of("DELETE FROM scratch_pad")) // normalized, case-insensitive
.offsetPaginationSqlFragments(List.of("from admin_report"))
.repeatedStatementThreshold(50) // a generous regression tripwire, not a precise count
.repeatedStatementSqlFragments(List.of("from feature_flags"))
.build();Pass the exclusions to the facade:
audits.assertCatalogClean(schema, excludes);
audits.assertRuntimeClean(excludes);
audits.assertAllClean(schema, excludes);Per-family exclusion reference
Catalog exclusions
| Builder method | Applies to | Match semantics |
|---|---|---|
primaryKeyTables(Set<String>) |
PrimaryKeyPresenceAuditAssertion |
Exact table name. |
primaryKeyTypeColumns(Set<String>) |
PrimaryKeyTypeAuditAssertion |
Case-insensitive table.column string. |
duplicateForeignKeyConstraints(Set<String>) |
DuplicateForeignKeyAuditAssertion |
Exact FK constraint name. Excluding one constraint of a duplicate pair drops that relationship’s group below two members, suppressing the finding entirely. |
foreignKeyIndexConstraints(Set<String>) |
ForeignKeyIndexAuditAssertion |
Exact FK constraint name. |
foreignKeyNotNullColumns(Set<String>) |
ForeignKeyNotNullAuditAssertion |
Exact table.column string. |
foreignKeyTypeMatchColumns(Set<String>) |
ForeignKeyTypeMatchAuditAssertion |
Exact table.column string. |
redundantIndexes(Set<String>) |
RedundantIndexAuditAssertion |
Exact index name. |
uniqueIndexNotNullIndexes(Set<String>) |
UniqueIndexNotNullAuditAssertion |
Exact index name. |
PrimaryKeyPresenceAuditAssertion also excludes the Liquibase bookkeeping tables
(databasechangelog, databasechangeloglock) automatically, without needing an explicit exclusion.
PrimaryKeyTypeAuditAssertion and UnmappedDatabaseObjectAuditAssertion do not — since
databasechangeloglock’s primary key is a narrow `INT and neither bookkeeping table is entity-mapped,
exclude them explicitly (see the archetype example ITs).
JPA exclusions
| Builder method | Applies to | Match semantics |
|---|---|---|
jpaExcludedRelations(Set<String>) |
SchemaEntityValidationAuditAssertion |
A table or table.column name — optionally schema-qualified as schema.table / schema.table.column — matched case-insensitively. Suppresses a known, acceptable entity/schema mismatch. |
unmappedDatabaseObjectRelations(Set<String>) |
UnmappedDatabaseObjectAuditAssertion |
A table or table.column name — optionally schema-qualified — matched case-insensitively. Suppresses a known, acceptable unmapped relation (e.g. a migration-tool bookkeeping table). |
missingVersionEntities(Set<String>) |
MissingVersionAttributeAuditAssertion |
An entity’s fully-qualified name, simple name, or physical table name — matched case-insensitively. |
eagerCollectionRoles(Set<String>) |
EagerCollectionFetchAuditAssertion |
Exact Hibernate collection role (e.g. com.acme.Order.items), matched case-insensitively. |
Runtime exclusions
| Builder method | Applies to | Match semantics |
|---|---|---|
planRelations(Set<String>) |
WhereClauseIndexAuditAssertion, OrderByIndexAuditAssertion, JoinIndexAuditAssertion |
Exact relation (table) name. Any statement touching that relation is skipped. |
planSqlFragments(List<String>) |
WhereClauseIndexAuditAssertion, OrderByIndexAuditAssertion, JoinIndexAuditAssertion |
Substring match against the full SQL statement. |
unusedIndexes(Set<String>) |
UnusedIndexAuditAssertion |
Exact index name. |
unconditionalMutationStatements(Set<String>) |
UnconditionalMutationAuditAssertion |
Case-insensitive match against the normalized statement text. |
offsetPaginationSqlFragments(List<String>) |
OffsetPaginationAuditAssertion |
Case-insensitive substring match against the normalized statement text. |
repeatedStatementThreshold(int) |
RepeatedStatementAuditAssertion |
The minimum capture count (inclusive, at least 2) for a SELECT shape to be reported; defaults to a generous 50 as a regression tripwire rather than a precise N+1 count. |
repeatedStatementSqlFragments(List<String>) |
RepeatedStatementAuditAssertion |
Case-insensitive substring match against the normalized statement text. |
Passing exclusions to individual assertion beans
Each assertion bean also accepts exclusions directly on its assertClean(…) overload:
// Catalog: pass a Set of excluded table names
primaryKeyPresenceAuditAssertion.assertClean(schema, Set.of("legacy_table_no_pk"));
// Catalog: pass a Set of excluded FK constraint names
foreignKeyIndexAuditAssertion.assertClean(schema, Set.of("fk_unindexed_by_design"));
// Runtime: pass excluded relations and SQL fragments separately
whereClauseIndexAuditAssertion.assertClean(
Set.of("small_lookup_table"),
List.of("UPDATE migration_flag")
);
// Runtime: pass exact statements to exclude from the mutation audit
unconditionalMutationAuditAssertion.assertClean(Set.of("DELETE FROM scratch_pad"));Using the DatabaseAuditExcludes builder and the facade is preferred when running multiple audits together,
since it keeps all exclusions in one place.

