perf(mapper): skip AST build when the writer is not listening - #209
Open
shihyuho wants to merge 1 commit into
Open
perf(mapper): skip AST build when the writer is not listening#209shihyuho wants to merge 1 commit into
shihyuho wants to merge 1 commit into
Conversation
The debug AST was built and stringified on every toSpec call, then thrown away whenever the logger sat above DEBUG — pure allocation on the hot path. ASTWriterFactory gains a default isEnabled(rootObject) hook so the gate can live where the knowledge does: domain() checks SpecMapper's logger, impersonation() checks the mapped object's logger. Custom factories keep the true default, so their behavior is unchanged. When disabled, SpecMapper hands resolvers a no-op AST and skips the writer entirely. Also document that to-one joins can set @Join(distinct = false); the default stays true since flipping it would be a breaking behavior change. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #201
Work package WP7 — findings PERF-03 and PERF-04.
PERF-03 — eager AST build on every
toSpecSpecMapper.toSpec(Object)built aSpecAST, formatted a node string for everyresolved field, joined the whole tree into one string, and handed it to a
Writerthat dropped it on the floor whenever the logger sat aboveDEBUG.Pure allocation and CPU on the mapping hot path.
The gate could not simply be
log.isDebugEnabled()insideSpecMapper, becauseSpecMapperdoes not own the decision: the built-inimpersonation()factory —the starter's default — writes to the mapped object's logger, not
SpecMapper's. Gating onSpecMapper's own logger would have silently killedAST output for every Spring Boot user who enables
DEBUGon their criteriaclass.
So the check lives where the knowledge is:
ASTWriterFactorygainsdefault boolean isEnabled(Object rootObject)returning
true. Additive and non-breaking — a custom factory that does notoverride it keeps receiving every AST exactly as before.
domain()andimpersonation()now share a smallSlf4jDebugWriterFactorythat reports
isEnabledfrom the same logger it would have written to.isEnabledisfalse,SpecMapperputs a no-opASTin the context andreturns early: no node strings are formatted, no tree is joined, and
createWriteris never called. Resolvers still callast.add(...)unconditionally, so no resolver had to change.
How the DEBUG gating is verified
mapper/src/test/java/tw/com/softleader/data/jpa/spec/ASTWriterFactoryTest.java(4 tests) drives real logback levels against a Mockito spy of the real built-in
factories, plus a resolver that captures the
ASTinstance the mapper handeddown:
noAstWhenDebugDisabledINFO→verify(factory, never()).createWriter(any(), any())and the captured AST'sprint()is empty — the tree itself was never builtastWrittenWhenDebugEnabledDEBUG→createWritercalled once, AST content presentimpersonationHonoursMappedObjectLoggerSpecMapperatINFO, criteria class atDEBUG→ still written (guards the regression described above)customFactoryKeepsReceivingAstByDefaultisEnabledstill gets the AST with everything atINFOConfirmed non-vacuous: reverting only the
SpecMappergate makesnoAstWhenDebugDisabledfail withNeverWantedButInvoked.PERF-04 — always-on
DISTINCTRestricted to the documentation change, as the plan explicitly authorizes.
@Join#distinctkeeps itstruedefault (flipping it is a breaking behaviorchange) and its Javadoc now states that only a to-many association can multiply
rows, so a to-one join can set
distinct = falseto skip the needlesssort/dedup.
The optional optimization — omitting
DISTINCTwhen no to-many joinparticipates — was not attempted, for two reasons:
predicate-build time. Getting it wrong in the permissive direction silently
returns duplicate rows — a wrong-results bug, against a finding rated low.
domain/Join.javaandSpecJoinContext.java, which are concurrently beingreworked by [WP3] Join machinery: silent-wrong-result edge cases + missing edge/error tests #198. Building the optimization on top of code being rewritten
underneath it would produce a conflict-prone change resting on an unstable
base.
To-many join behavior is therefore unchanged, and no existing distinct/join test
was modified.
Verification
make testgreen — mapper 102 tests, starter 15 tests,BUILD SUCCESS.Built under JDK 17 (Temurin 17.0.6); the ambient JDK 25 breaks
google-java-format inside spotless, which is a pre-existing toolchain issue
unrelated to this change — no pom versions were touched.
🤖 Generated with Claude Code