Skip to content

Build1 publisher3 min readPublished

A Spring JDBC search demo moves role visibility out of a StringBuilder and into Strategy classes

Posted on dev.to, a Spring JDBC demo splits a five-role, ten-filter SQL Server search into two Strategy families that feed one rule-enforcing query builder. The legacy version buries a delegation rule in five appends and fetches admin-only emails for every role.

The Engineer · Build desk

Illustration accompanying A Spring JDBC search demo moves role visibility out of a StringBuilder and into Strategy classes

What happened

  • A dev.to post rebuilds a role-scoped document search as two Strategy families, one for what a user may see and one for what they asked for, feeding a builder that enforces the rules.
  • The demo has five visibility roles and ten optional filters, several of which need joins, over a hierarchy of national, regional and local offices.
  • Its legacy version is one method: a StringBuilder that opens with SELECT DISTINCT and every possible join, then one if per role and one per filter.
  • Both versions run against a real SQL Server container via Testcontainers, and the post says the tests show the same rows apart from two quirks.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure Author emails leave the database for every role, and for non-admins a single if inside a row-mapper lambda is all that keeps the column out of the response.
  • constraint A count query or export that needs the same visibility has to copy the appends, so each copy is another place where a security rule can drift out of step.
  • cost Fixing the shared catch-all plan with OPTION (RECOMPILE) moves the cost to a fresh compilation on every execution of the search.
  • decision Teams on SQL Server 2025 have to measure Optional Parameter Plan Optimization against their own ten-predicate catch-all before relying on it.

The local officer's query shows where visibility lives in the legacy code. It is one line, `AND d.org_unit_id = :userUnitId`, sitting under `WHERE 1 = 1` [8]. `WHERE 1 = 1` exists so that no later append has to know whether it comes first. The method has one `if` per role [7], so the boundary exists only on the paths where one of those branches ran.

The chartered unit is where that gets expensive. A local office that reports straight to the national office is visible to a regional supervisor only while the supervisor holds an explicit, dated delegation for it [5]. In the legacy method that rule is five `sql.append` calls inside an `else if`, with nothing to call, reuse or test in isolation [10]. The post's author wrote: "A search with optional filters and role-based visibility is application logic, and one of its invariants is a security boundary" [1].

I agree, with a condition. The composed design is a boundary only if the builder refuses to produce SQL when no visibility strategy has contributed a fragment. The post says the builder enforces the rules [2]. The available text of the post ends before the composed code, so how it enforces them is described here, not shown. I would want one test per visibility strategy, with a delegation fixture on each side of its end date.

The performance case is specific to SQL Server. The engine caches one plan per statement text [12]. The legacy text never changes, so every filter combination shares a plan compiled for the first call's values [12]. That plan has to stay valid whenever any parameter is NULL. The post says that requirement pushes the optimizer towards scans [12]. Ten optional filters allow up to 1,024 on/off combinations [1]. The post points to Erland Sommarskog's Dynamic Search Conditions in T-SQL for the full set of options [18]. If the composed builder emits only the predicates a request uses, the same one-plan-per-text rule gives each filter shape its own plan [12].

The test setup is the best engineering in the post. It runs both versions on one schema against a real SQL Server 2025 container [3][4], on Spring JDBC with `NamedParameterJdbcTemplate` and records, no JPA [4]. That matters because the plan-cache argument is about one engine's behaviour. A matching-rows result says the demo's fixtures agree. It carries to another codebase only if that codebase's fixtures exercise every role and every delegation edge it has.

Two more findings point the same way. The legacy `LEFT JOIN`s multiply rows, and `DISTINCT` hides that instead of avoiding it [16]. The author email is hidden in the row mapper after it has already been fetched [15]. A visibility strategy that also chose the projection would keep the column in the database for non-admins.

In my view the strategy split is right for a screen with five roles and dated delegations [5][6]. A screen with two roles and three filters is cheaper to own as one method.

What to watch

  • Published measurements of SQL Server 2025's Optional Parameter Plan Optimization on a query with ten optional catch-all predicates.
  • The composed version's code, showing whether the builder rejects a query that no visibility strategy has contributed to.
  • A count or export path in the demo that reuses the same visibility strategies as the search screen.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories