Skip to content

Paginate /api/user/all and stop the N+1 recipient loads (sfind-cf43a4da) - #364

Open
devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/sfind-cf43a4da-paginate-user-list
Open

devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/sfind-cf43a4da-paginate-user-list

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Summary

Fixes code-scan finding sfind-cf43a4da. GET /api/user/all used to load every User. Serializing that list then lazy-loaded recipientList and userRoles once per user, plus each user's accounts.

  • Pagination: GET /api/user/all?page=0&size=50 (default 50, max 200, sorted by userId; out-of-range values return 400). The response body is still a plain JSON array, so existing callers keep working. Totals go in the X-Total-Count, X-Page, X-Page-Size and X-Total-Pages headers, which RequestFilter now exposes via Access-Control-Expose-Headers.
  • DAO and service: UserDao.findAll(Pageable) uses @EntityGraph(attributePaths = {"primaryAccount","savingsAccount"}, type = LOAD), so accounts are join-fetched into the single page query. UserService.findUserList() is now findUserList(Pageable). The unbounded List<User> findAll() is removed.
  • User entity:
    • @JsonIgnore on User.recipientList, matching appointmentList. The admin screen never shows recipients.
    • @BatchSize(size = 200) on the EAGER userRoles.
    • The graph type matters: with the default FETCH type, Hibernate skipped batching and still ran one userRoles query per user. With LOAD it runs one batched IN (...) query.
  • AdminPortal: UserService.getUsers(page, size) and a Previous/Next pager in user-account.component driven by X-Total-Pages.

Measured against MySQL 5.7 with 121 seeded users, 3 recipients each, counting spring.jpa.show-sql statements per request:

master this PR
SQL statements for GET /api/user/all 485 3 (page query with join + LIMIT, count, batched roles)
Users in response 121 50 (page 0 of 3)
Payload 94.9 KB 23.7 KB

Verification

  • New UserResourceTest (standalone MockMvc + Mockito, 4 tests) covers the defaults and totals headers, passing page/size through, 400s for out-of-range values, and that recipientList is not serialized. It passes against the Boot 1.5-era jars (Spring 4.3.12, spring-data-commons 1.13.8), compiled with javac --release 8 and run through JUnitCore.
  • The numbers in the table come from a /tmp copy of the app rebuilt on the cached Boot 2.7.18 parent (PageRequest.of instead of new PageRequest, findById in AppointmentServiceImpl, mysql-connector-j), logged in as an admin over HTTP.
  • Not verified:
    • mvn test on the real Boot 1.5.4 build: the 1.5.4 parent isn't cached and Maven Central is blocked in this session.
    • The AdminPortal pager: no node_modules, and the npm registry is blocked, so it wasn't built or exercised in a browser.

Related

Written by Devin

Devin-Org: engineering

Open in Devin Desktop: https://app.devin.ai/desktop/session/7e2990c52edb4f878983ffb32de62260?variant=devin


Devin Review

Status Commit
⚪ Not started —

Run Devin Review

Devin Review (Staging)

@devin-ai-integration

Copy link
Copy Markdown
Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants