Parameterize SQL and close JDBC resources in DbInteractions (#139, #140)#143
Merged
Merged
Conversation
Rework DbInteractions to use PreparedStatement with bound parameters and try-with-resources: - query(sql, params...) binds parameters, runs the statement inside try-with-resources, and returns a disconnected CachedRowSet (rows copied out) so the PreparedStatement and live ResultSet are always closed — fixing the Statement/ResultSet leak (#140). Callers keep using the ResultSet API. - update(sql, params...) binds parameters and closes the statement via try-with-resources (#140). - Both signatures are varargs, so existing parameterless call sites are unaffected. Migrate the user-controlled (name) interpolations off string concatenation to bound parameters (#139): EntityFactory / EnvironmentFactory inserts, EntityRepositoryImpl save + updateName, EnvironmentRepositoryImpl findByName + updateName. Integer ids are left inline (not an injection vector). Affected repository test mocks updated to the parameterized call signatures. Adds DbInteractionsTest (H2 in-memory, test scope) covering parameter binding, that a value containing a SQL payload (`O'Brien'); DROP TABLE ...`) is stored literally and does not inject, the disconnected-ResultSet behavior, and the error paths. H2 added as a test-scope dependency. Closes #139 Closes #140 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dmccoystephenson
left a comment
Member
Author
There was a problem hiding this comment.
Self-review rubric (adversarial; anchored on green CI run 27468231673 + the diff):
- Scope: PASS — 10 files, all necessary:
DbInteractionsrework, the 4 name-bearing call sites (2 factories + 2 repos), their updated test mocks, the new H2 test, and thepom.xmltest dep. No unrelated churn. - Resource fix (#140): PASS — both methods use try-with-resources on the
PreparedStatement;query()copies rows into aCachedRowSetbefore the liveResultSet/statement close.DbInteractionsTest.query_returnsDisconnectedResultSet_readableAfterReturnreads the result fully afterquery()returns, which is only possible because it is disconnected. - Injection fix (#139): PASS — demonstrated, not asserted.
parameterizedValueWithSqlPayload_isStoredLiterally_andDoesNotInjectinsertsO'Brien'); DROP TABLE person; --as a bound parameter; the table survives and the value round-trips verbatim. The data layer no longer concatenates user-controllednamevalues. - Backward compatibility: PASS — both signatures are varargs, so the ~40 existing parameterless call sites compile and run unchanged; full suite is 226/226 (was 220). Repositories consume the
CachedRowSetvia the sameResultSetAPI (forwardnext()+getInt/getStringby label/index), all supported. - Tests-fix mocks: PASS — the 12 repository-test stubs whose SQL changed (
?placeholders + param arg) were updated;EntityFactoryTestwas updated for the new varargs INSERT. - CI: PASS —
buildgreen on PR head; CI compiles and runs the H2 test. - DTO boundary / Spec / Java-Python parallelism: N/A — internal data layer; no endpoint, DTO, or Python-client surface changed (the Python client is an HTTP client with no
DbInteractionsmirror). - Override correctness: PASS — only method bodies changed; no
@Overrideadded or removed.
Honest caveats for the reviewer:
query()now returns aCachedRowSetrather than a liveResultSet— a deliberate behavioral change in the core data layer (the point of the leak fix). Repositories iterate forward-only, so this is compatible, but it warrants a look.DbInteractionsTestcoversgetInt/getStringby label and the factory'sgetInt(1)by index is standardCachedRowSetbut not separately asserted here.- Integer ids remain string-concatenated (not an injection vector); only user-controlled strings were parameterized to bound the diff.
This PR is intentionally not auto-merged — it touches pom.xml (do-not-auto-merge). Flagged for manual review.
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.
Summary
Hardens the hand-rolled data layer to fix the injection/breakage risk (#139) and the JDBC resource leak (#140) together, since both live in
DbInteractions.DbInteractionsrework:query(sql, params...)— binds parameters viaPreparedStatement, runs inside try-with-resources, and returns a disconnectedCachedRowSet(rows copied out) so thePreparedStatementand liveResultSetare always closed. Callers keep using theResultSetAPI unchanged. Fixes the leak in DbInteractions.query/update never close the Statement (JDBC resource leak) #140.update(sql, params...)— binds parameters, closes the statement via try-with-resources. Fixes the leak in DbInteractions.query/update never close the Statement (JDBC resource leak) #140.Parameterization (#139): the user-controlled
nameinterpolations were moved off string concatenation to bound parameters:EntityFactory/EnvironmentFactoryinsertsEntityRepositoryImpl.save+updateNameEnvironmentRepositoryImpl.findByName+updateNameInteger ids are left inline (not an injection vector); the grid/location inserts in
EnvironmentFactoryare int-only and untouched. The affected repository test mocks were updated to the parameterized call signatures.Tests
Adds
DbInteractionsTest(H2 in-memory, test scope) — this is what actually exercises the new binding/resource behavior, sinceDbInteractionsis mocked everywhere else:O'Brien'); DROP TABLE person; --) is stored literally and does not inject (the table survives) — the core Data layer builds SQL by string-concatenating user-supplied names (injection / breakage risk) #139 proof;ResultSetis disconnected and fully readable afterquery()returns (evidence for the DbInteractions.query/update never close the Statement (JDBC resource leak) #140 resource fix);null/falsewithout throwing; a no-row update returnsfalse.This PR modifies
pom.xml(adds thecom.h2database:h2test-scope dependency), which is on the do-not-auto-merge list. Flagging for manual review rather than merging automatically. Thequery()redesign (returning aCachedRowSetinstead of a liveResultSet) is also a deliberate behavioral change in the core data layer worth a careful look.Test plan
./mvnw test— 226/226 pass (was 220; +6DbInteractionsTest), JDK 21DbInteractionsTestruns real SQL on H2, including the injection-payload casesrc/main/pythonchange)Follow-up note
The deeper fix for
query()returning aResultSetat all (aRowMapper-style API returningList<T>) would let every repository drop its manualResultSethandling, but that is a much larger refactor; theCachedRowSetapproach here fixes the leak with minimal blast radius.Closes #139
Closes #140
🤖 Generated with Claude Code