Performance: DTO Projection Tickler on read operations (Tickler, Tickler Comment, Tickler Link)#2257
Conversation
…ly this to tickler_link but code is currently working fine
…s well, need to test and look to refactor some areas of these changes
Reviewer's GuideIntroduces DTO-based JPQL projections and batch loading for ticklers, comments, and links to eliminate N+1 queries on TicklerMain, wires them through DAO/manager layers, updates the JSP to consume DTOs instead of entities, and adds JavaMelody wiring plus unit/integration tests for the new DTOs and DAO behavior. Sequence diagram for TicklerMain DTO-based tickler loadingsequenceDiagram
actor User
participant TicklerMainJSP
participant TicklerManagerImpl
participant TicklerDaoImpl
participant EntityManager
participant Database
User->>TicklerMainJSP: Load TicklerMain.jsp
TicklerMainJSP->>TicklerManagerImpl: getTicklerDTOs(loggedInInfo, filter)
TicklerManagerImpl->>TicklerManagerImpl: checkPrivilege(loggedInInfo, PRIVILEGE_READ)
TicklerManagerImpl->>TicklerDaoImpl: getTicklerDTOs(filter)
TicklerDaoImpl->>TicklerDaoImpl: getTicklerDTOQueryString(paramList, filter)
TicklerDaoImpl->>EntityManager: createQuery(jpql)
EntityManager-->>TicklerDaoImpl: Query ticklerQuery
TicklerDaoImpl->>EntityManager: ticklerQuery.setParameters(paramList)
TicklerDaoImpl->>EntityManager: ticklerQuery.setFirstResult(offset)
TicklerDaoImpl->>EntityManager: setLimit(ticklerQuery, limit)
TicklerDaoImpl->>Database: Execute ticklerQuery
Database-->>TicklerDaoImpl: List<TicklerListDTO> ticklerDTOs
TicklerDaoImpl->>TicklerDaoImpl: loadCommentsForTicklerDTOs(ticklerDTOs)
TicklerDaoImpl->>EntityManager: createQuery(commentSql)
EntityManager-->>TicklerDaoImpl: Query commentQuery
TicklerDaoImpl->>EntityManager: commentQuery.setParameter(ticklerIds)
TicklerDaoImpl->>Database: Execute commentQuery
Database-->>TicklerDaoImpl: List<TicklerCommentDTO> comments
TicklerDaoImpl->>TicklerDaoImpl: Map comments to TicklerListDTO.comments
TicklerDaoImpl->>TicklerDaoImpl: loadLinksForTicklerDTOs(ticklerDTOs)
TicklerDaoImpl->>EntityManager: createQuery(linkSql)
EntityManager-->>TicklerDaoImpl: Query linkQuery
TicklerDaoImpl->>EntityManager: linkQuery.setParameter(ticklerIds)
TicklerDaoImpl->>Database: Execute linkQuery
Database-->>TicklerDaoImpl: List<TicklerLinkDTO> links
TicklerDaoImpl->>TicklerDaoImpl: Map links to TicklerListDTO.links
TicklerDaoImpl-->>TicklerManagerImpl: List<TicklerListDTO>
TicklerManagerImpl-->>TicklerMainJSP: List<TicklerListDTO>
TicklerMainJSP-->>User: Render tickler table using DTOs
Class diagram for new Tickler DTOs and DAO/manager methodsclassDiagram
class Tickler {
<<entity>>
enum STATUS
enum PRIORITY
}
class TicklerListDTO {
<<dto>>
- Integer id
- String message
- Date serviceDate
- Date createDate
- Tickler.STATUS status
- Tickler.PRIORITY priority
- Integer demographicNo
- String demographicLastName
- String demographicFirstName
- String creatorLastName
- String creatorFirstName
- String assigneeLastName
- String assigneeFirstName
- List~TicklerCommentDTO~ comments
- List~TicklerLinkDTO~ links
+ TicklerListDTO()
+ TicklerListDTO(Integer id, String message, Date serviceDate, Date createDate, Tickler.STATUS status, Tickler.PRIORITY priority, Integer demographicNo, String demographicLastName, String demographicFirstName, String creatorLastName, String creatorFirstName, String assigneeLastName, String assigneeFirstName)
+ Integer getId()
+ void setId(Integer id)
+ String getMessage()
+ void setMessage(String message)
+ Date getServiceDate()
+ void setServiceDate(Date serviceDate)
+ Date getCreateDate()
+ void setCreateDate(Date createDate)
+ Tickler.STATUS getStatus()
+ void setStatus(Tickler.STATUS status)
+ Tickler.PRIORITY getPriority()
+ void setPriority(Tickler.PRIORITY priority)
+ Integer getDemographicNo()
+ void setDemographicNo(Integer demographicNo)
+ String getDemographicLastName()
+ void setDemographicLastName(String demographicLastName)
+ String getDemographicFirstName()
+ void setDemographicFirstName(String demographicFirstName)
+ String getCreatorLastName()
+ void setCreatorLastName(String creatorLastName)
+ String getCreatorFirstName()
+ void setCreatorFirstName(String creatorFirstName)
+ String getAssigneeLastName()
+ void setAssigneeLastName(String assigneeLastName)
+ String getAssigneeFirstName()
+ void setAssigneeFirstName(String assigneeFirstName)
+ List~TicklerCommentDTO~ getComments()
+ void setComments(List~TicklerCommentDTO~ comments)
+ List~TicklerLinkDTO~ getLinks()
+ void setLinks(List~TicklerLinkDTO~ links)
+ String getDemographicFormattedName()
+ String getCreatorFormattedName()
+ String getAssigneeFormattedName()
+ String getStatusDesc(Locale locale)
}
class TicklerCommentDTO {
<<dto>>
- Integer id
- Integer ticklerNo
- String message
- Date updateDate
- String providerLastName
- String providerFirstName
+ TicklerCommentDTO()
+ TicklerCommentDTO(Integer id, Integer ticklerNo, String message, Date updateDate, String providerLastName, String providerFirstName)
+ Integer getId()
+ void setId(Integer id)
+ Integer getTicklerNo()
+ void setTicklerNo(Integer ticklerNo)
+ String getMessage()
+ void setMessage(String message)
+ Date getUpdateDate()
+ void setUpdateDate(Date updateDate)
+ String getProviderLastName()
+ void setProviderLastName(String providerLastName)
+ String getProviderFirstName()
+ void setProviderFirstName(String providerFirstName)
+ String getProviderFormattedName()
+ boolean isUpdateDateToday()
+ String getUpdateTime(Locale locale)
+ String getUpdateDateTime(Locale locale)
+ String getUpdateDate(Locale locale)
}
class TicklerLinkDTO {
<<dto>>
- Integer id
- Integer ticklerNo
- String tableName
- Long tableId
+ TicklerLinkDTO()
+ TicklerLinkDTO(Integer id, Integer ticklerNo, String tableName, Long tableId)
+ Integer getId()
+ void setId(Integer id)
+ Integer getTicklerNo()
+ void setTicklerNo(Integer ticklerNo)
+ String getTableName()
+ void setTableName(String tableName)
+ Long getTableId()
+ void setTableId(Long tableId)
}
class TicklerDao {
<<interface>>
+ List~Tickler~ getTicklers(CustomFilter filter)
+ int getNumTicklers(CustomFilter filter)
+ List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter, int offset, int limit)
+ List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter)
}
class TicklerDaoImpl {
<<service>>
- EntityManager entityManager
+ List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter, int offset, int limit)
+ List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter)
- String getTicklerDTOQueryString(List~Object~ paramList, CustomFilter filter)
- void loadCommentsForTicklerDTOs(List~TicklerListDTO~ ticklerDTOs)
- void loadLinksForTicklerDTOs(List~TicklerListDTO~ ticklerDTOs)
}
class TicklerManager {
<<interface>>
+ List~Tickler~ getTicklers(LoggedInInfo loggedInInfo, CustomFilter filter)
+ List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter)
+ List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter, int offset, int limit)
}
class TicklerManagerImpl {
<<service>>
- TicklerDao ticklerDao
- SecurityInfoManager securityInfoManager
+ List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter)
+ List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter, int offset, int limit)
- void checkPrivilege(LoggedInInfo loggedInInfo, String privilege)
}
TicklerListDTO "1" o-- "*" TicklerCommentDTO : comments
TicklerListDTO "1" o-- "*" TicklerLinkDTO : links
TicklerDaoImpl ..|> TicklerDao
TicklerManagerImpl ..|> TicklerManager
TicklerListDTO --> Tickler : uses STATUS and PRIORITY enums
TicklerCommentDTO --> TicklerListDTO : ticklerNo
TicklerLinkDTO --> TicklerListDTO : ticklerNo
TicklerManagerImpl --> TicklerDao : uses
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughAdds DTO-based list retrieval for Ticklers: three new DTOs (TicklerListDTO, TicklerCommentDTO, TicklerLinkDTO), DAO and Manager methods (with optional pagination and batch-loading of comments/links), a JSP refactor to use DTOs, and accompanying unit/integration tests and persistence update. Changes
Sequence DiagramsequenceDiagram
actor User
participant JSP as "ticklerMain.jsp"
participant Manager as "TicklerManager"
participant DAO as "TicklerDao"
participant DB as "Database"
User->>JSP: Request tickler list
JSP->>Manager: getTicklerDTOs(loggedInInfo, filter[, offset,limit])
Manager->>Manager: Privilege checks / log demographic access
Manager->>DAO: getTicklerDTOs(filter[, offset,limit])
DAO->>DB: Execute JPQL projection -> TicklerListDTOs
DB-->>DAO: TicklerListDTO rows
DAO->>DB: Batch query comments for tickler IDs -> TicklerCommentDTOs
DB-->>DAO: TicklerCommentDTOs
DAO->>DAO: Attach comments to corresponding DTOs
DAO->>DB: Batch query links for tickler IDs -> TicklerLinkDTOs
DB-->>DAO: TicklerLinkDTOs
DAO->>DAO: Attach links to corresponding DTOs
DAO-->>Manager: Populated TicklerListDTOs
Manager-->>JSP: DTO list
JSP->>JSP: Render using DTO fields
JSP-->>User: Rendered tickler list
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @LiamStanziani, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request delivers a significant performance enhancement for tickler read operations, particularly on the 'TicklerMain' page. By implementing DTO projections and batch loading for related entities, the system now retrieves tickler data, comments, and links with drastically fewer database queries, addressing long-standing N+1 query problems and improving overall responsiveness. The changes span the data access layer, business logic, and the UI, all supported by new DTO structures and comprehensive tests. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant optimization to the Tickler list view by implementing Data Transfer Objects (DTOs) and JPQL projection queries. New DTOs (TicklerListDTO, TicklerCommentDTO, TicklerLinkDTO) have been added to provide a lightweight representation of tickler data, reducing the number of database queries from approximately 25 per page load to 3 (one for ticklers, one for comments, and one for links). The TicklerDao and TicklerManager interfaces and their implementations have been updated to support these new DTO-based retrieval methods, including pagination and batch loading of comments and links. The ticklerMain.jsp page has been refactored to consume these DTOs directly, removing previous direct entity access and separate DAO calls for related data. Additionally, new unit and integration tests have been added to validate the DTO projection and helper methods. Review comments highlight the need to remove temporary JavaMelody configuration, correct Javadoc inaccuracies regarding the number of queries performed, refactor dynamic JPQL query construction to use named parameters for safety, and remove redundant list initializations in DTOs. A potential bug was also identified where multiple demographic number conditions could be added to the query.
I am having trouble creating individual review comments. Click here to see my feedback.
src/main/resources/spring_jpa.xml (20-21)
This change introduces a JavaMelody bean for development purposes, as indicated by the comment 'TEMPORARY FOR DEV'. Such development-specific configurations should not be merged into the main codebase. Please remove this bean before merging the pull request.
src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (580-588)
There is a potential bug in the query construction logic. If both includeClientClause and includeDemographicClause are true, the query will contain two separate AND t.demographicNo = ? conditions. This will likely lead to incorrect query results or an error, depending on the database. Please ensure that only one condition for demographicNo is added to the query, or that these filters are mutually exclusive and handled appropriately.
src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java (76-78)
The Javadoc states that this method reduces database queries to 'exactly 2 queries' and that 'Comments are batch-loaded'. The implementation actually performs up to three queries (one for ticklers, one for comments, and one for links) and also batch-loads links. Please update the Javadoc to accurately reflect that it performs 3 queries and batch-loads both comments and links for clarity.
* for display, reducing database queries from ~25 per page load to 3 queries.
* Comments and links are batch-loaded in separate queries.
src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java (91-93)
The Javadoc states that this method reduces database queries to 'exactly 2 queries' and that 'Comments are batch-loaded'. The implementation actually performs up to three queries (one for ticklers, one for comments, and one for links) and also batch-loads links. Please update the Javadoc to accurately reflect that it performs 3 queries and batch-loads both comments and links for clarity.
* for display, reducing database queries from ~25 per page load to 3 queries.
* Comments and links are batch-loaded in separate queries.
src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (453)
The method getTicklerDTOQueryString dynamically constructs a JPQL query using positional parameters (?). This approach is error-prone, especially with complex conditional logic, as it's easy to mismatch parameters and their positions. For improved readability, maintainability, and safety, please refactor this to use named parameters (e.g., :paramName).
src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (616)
The comments list is already initialized in the TicklerListDTO constructor. This call to dto.setComments(new ArrayList<>()) is redundant and can be removed.
src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (657)
The links list is already initialized in the TicklerListDTO constructor. This call to dto.setLinks(new ArrayList<>()) is redundant and can be removed.
src/main/java/ca/openosp/openo/managers/TicklerManager.java (160-161)
The Javadoc states that this method reduces database queries to 'exactly 2 queries'. The underlying implementation actually performs up to three queries (one for ticklers, one for comments, and one for links). Please update the Javadoc to accurately reflect the number of queries for clarity.
* This method provides optimized data retrieval for list views, reducing database
* queries from ~25 per page load (due to EAGER relationships) to 3 queries.
src/main/java/ca/openosp/openo/managers/TicklerManager.java (173-174)
The Javadoc states that this method reduces database queries to 'exactly 2 queries'. The underlying implementation actually performs up to three queries (one for ticklers, one for comments, and one for links). Please update the Javadoc to accurately reflect the number of queries for clarity.
* This method provides optimized data retrieval for list views, reducing database
* queries from ~25 per page load (due to EAGER relationships) to 3 queries.
|
@claude review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Claude finished @LiamStanziani's task in 3m 0s —— View job Code Review for DTO Projection Performance OptimizationOverviewThis PR introduces a significant performance optimization for tickler read operations through DTO projection. The implementation successfully reduces database queries from ~25 per page load to 3 queries (ticklers, comments, links) by eliminating N+1 issues caused by EAGER relationships. The code quality is generally high with good test coverage, but there are several critical issues that must be addressed before merging. Critical Issues 🔴1. Temporary Development Code Must Be RemovedLocation: Latest commit message and PR description The commit Action Required: Verify that all temporary development/debugging code has been removed before merging. 2. Potential Duplicate WHERE Clause BugLocation: if (includeClientClause) {
query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
paramList.add(Integer.parseInt(filter.getClient()));
}
if (includeDemographicClause) {
query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
paramList.add(Integer.parseInt(filter.getDemographicNo()));
}Issue: If both Recommendation:
3. Inaccurate JavaDoc - Query CountLocations:
Current JavaDoc:
Actual Implementation: The code performs 3 queries:
Action Required: Update all JavaDoc to accurately reflect "3 queries" and mention that both comments and links are batch-loaded. High Priority Issues 🟡4. JPQL Query Construction Should Use Named ParametersLocation: Current Approach: Uses positional parameters ( Issues:
Recommendation: Refactor to use named parameters for improved safety and maintainability: // Instead of:
query.append("AND t.status = ?").append(paramIndex++);
paramList.add(status);
// Use:
query.append("AND t.status = :status ");
// Then in query setup:
query.setParameter("status", status);This is a best practice explicitly mentioned in CLAUDE.md: "Parameterized queries ONLY - never string concatenation" 5. Redundant List InitializationLocations: dto.setComments(new ArrayList<>()); // Line 616
dto.setLinks(new ArrayList<>()); // Line 657Issue: Both Action Required: Remove these redundant lines. Security Review ✅Excellent OWASP Encoding UsageLocation: The JSP properly uses <%=Encode.forHtmlContent(tickler.getDemographicLastName())%>
<%=Encode.forHtmlContent(tickler.getMessage())%>
<%=Encode.forHtmlContent(tc.getMessage())%>Compliant with CLAUDE.md security requirements ✅ Proper Security ChecksLocations:
Security implementation follows repository patterns correctly ✅ Code Quality AssessmentStrengths ✅
Areas for Improvement
Test Coverage Review ✅Location: Excellent test coverage including:
Follows modern test framework standards:
Missing test case: Edge case for duplicate demographic filter (see issue #2) Performance Impact AssessmentBefore: ~25 database queries per page load (N+1 issues from EAGER relationships) After: 3 optimized queries:
Estimated improvement: ~88% reduction in database queries 🚀 This aligns with the PR's stated goal and JavaMelody testing mentioned in the PR description. Recommendations for MergeMust Fix Before Merge:
Should Fix Before Merge:
Can Address in Follow-up:
Final VerdictThis is a well-implemented performance optimization with strong test coverage and proper security practices. The DTO projection pattern is correctly applied and will significantly improve the tickler page performance. However, the duplicate demographic filter bug (#2) is a potential data correctness issue that must be resolved before merging. The JavaDoc inaccuracies (#3) should also be corrected for accurate developer documentation. Once these critical issues are addressed, this PR will be ready for merge. The code quality is generally excellent and follows OpenO EMR development standards. Overall Assessment: ✅ Approve with required changes |
There was a problem hiding this comment.
Pull request overview
Implements DTO-based JPQL projections for Tickler read/list operations to reduce N+1 query overhead on the Tickler main page, and adds modern tests around the new projection behavior.
Changes:
- Added
TicklerListDTO/TicklerCommentDTO/TicklerLinkDTOand DAO/manager APIs to fetch ticklers via projection + batch-loaded related data. - Updated
ticklerMain.jspto render ticklers/comments/links from DTOs instead of entity graphs. - Added modern unit/integration tests and updated test JPA persistence config for the new entity usage.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test-modern/resources/META-INF/persistence.xml | Adds TicklerLink entity to the modern test persistence unit. |
| src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerListDTOTest.java | Unit tests for TicklerListDTO helper behavior. |
| src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerCommentDTOTest.java | Unit tests for TicklerCommentDTO helper behavior. |
| src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java | Integration tests validating the new DTO projection DAO methods. |
| src/main/webapp/tickler/ticklerMain.jsp | Switches Tickler list rendering to DTOs to avoid N+1 entity loading. |
| src/main/webapp/WEB-INF/web.xml | Modifies monitoring security constraint configuration. |
| src/main/resources/spring_jpa.xml | Adds JavaMelody DataSource post-processor bean to Spring context. |
| src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java | Introduces the tickler list DTO used by JPQL constructor projections. |
| src/main/java/ca/openosp/openo/tickler/dto/TicklerLinkDTO.java | Introduces lightweight DTO for tickler links (batch-loaded). |
| src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java | Introduces lightweight DTO for tickler comments (batch-loaded). |
| src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java | Adds manager methods to call the new DAO DTO projection APIs with privilege checks. |
| src/main/java/ca/openosp/openo/managers/TicklerManager.java | Adds manager interface methods for DTO projection retrieval (with pagination overload). |
| src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java | Implements DTO projection query + batch-loading of comments and links. |
| src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java | Adds DAO interface for DTO projection retrieval (paged + non-paged). |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 15
🤖 Fix all issues with AI agents
In `@src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java`:
- Around line 73-99: Add `@since` tags to both public DAO methods
getTicklerDTOs(CustomFilter, int, int) and getTicklerDTOs(CustomFilter) using
the appropriate dates from git history; update each JavaDoc block to include the
`@since` tag and also ensure the `@param` tags specify the exact data types (e.g.,
CustomFilter, int offset, int limit), the `@return` tag states the exact return
type (List<TicklerListDTO>), and include `@throws` tags for any checked/runtime
exceptions the methods can propagate so the public API JavaDoc meets the project
guidelines.
In `@src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java`:
- Around line 420-444: Add comprehensive JavaDoc to both public methods
getTicklerDTOs(CustomFilter, int, int) and its overload
getTicklerDTOs(CustomFilter): document each `@param` (filter: CustomFilter,
offset: int, limit: int) with expected semantics, include `@return` with the exact
type List<TicklerListDTO>, add `@throws` for runtime persistence exceptions (e.g.,
PersistenceException or runtime exceptions thrown by entityManager/query
execution), and include a `@since` tag (use the appropriate git history/tag
value). Ensure the JavaDoc mentions pagination behavior for offset/limit and
that the overload without pagination uses TicklerDao.MAX_LIST_RETURN_SIZE.
- Around line 453-595: The method getTicklerDTOQueryString currently composes
JPQL via StringBuilder; replace it with a Criteria API implementation: create a
CriteriaBuilder and CriteriaQuery<TicklerListDTO>, define Root<Tickler> t and
LEFT JOINs to Demographic d and Provider creator/assignee, build a
List<Predicate> and add predicates conditionally based on CustomFilter (use
filter.getMrp(), getStartDate(), getEndDate(), getProviders()/getAssignees(),
getProgramId(), getStatus() via convertStatus(), getPriority() via
convertPriority(), getClient(), getDemographicNo(), getMessage()), handle
endDate by setting time to 23:59:59 before creating predicate, then set the
selection to construct new TicklerListDTO(t.get("id"), t.get("message"), ...
creator/assignee/d fields), apply predicates with
criteriaQuery.where(cb.and(predicates.toArray(new Predicate[0])), create
TypedQuery from entityManager, and replace paramList use with direct parameter
values bound by Criteria (or set them on the TypedQuery) so callers expecting
the Query result can execute the typed query instead of using
getTicklerDTOQueryString.
In `@src/main/java/ca/openosp/openo/managers/TicklerManager.java`:
- Around line 157-183: Add `@since` tags to the two new public methods
getTicklerDTOs(LoggedInInfo, CustomFilter) and getTicklerDTOs(LoggedInInfo,
CustomFilter, int, int) in TicklerManager.java by looking up the commit date
from git history for when each overload was introduced and adding that date in
the JavaDoc block (e.g., "@since 2024-01-15"); keep the existing `@param` and
`@return` text intact and only append the correct `@since` line to each method's
JavaDoc.
In `@src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java`:
- Around line 771-783: The getTicklerDTOs(LoggedInInfo, CustomFilter) and
getTicklerDTOs(LoggedInInfo, CustomFilter, int, int) methods currently call
ticklerDao.getTicklerDTOs(...) directly and must enforce the same facility/role
access as getTicklers(); after checkPrivilege(...) call, retrieve DTOs from
ticklerDao.getTicklerDTOs(...), then apply the existing
ticklerFacilityFiltering(...) and/or the manager-level
filterTicklersByAccess(...) (or add a new
filterTicklerDTOsByAccess(LoggedInInfo, List<TicklerListDTO>) that mirrors
getTicklers() logic) before returning results to ensure facility/role
constraints; also annotate the TicklerManagerImpl class with `@Transactional` to
reflect patient-data modifications.
In `@src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java`:
- Around line 73-177: The public methods in TicklerCommentDTO (e.g.,
getProviderFormattedName, isUpdateDateToday, getUpdateTime(Locale),
getUpdateDateTime(Locale), getUpdateDate(Locale), and all public getters/setters
like getId/setId, getTicklerNo/setTicklerNo, getMessage/setMessage,
getUpdateDate/setUpdateDate, getProviderLastName/setProviderLastName,
getProviderFirstName/setProviderFirstName) are missing comprehensive JavaDoc;
add JavaDoc to each public method including a one-line description, `@param` for
parameters (with types, e.g., Locale locale), `@return` with exact return type,
`@throws` where applicable, and `@since` using the appropriate git commit/tag date
or id, ensuring helper methods (isUpdateDateToday, getProviderFormattedName)
also get `@since` and clear descriptions to satisfy coding guidelines.
In `@src/main/java/ca/openosp/openo/tickler/dto/TicklerLinkDTO.java`:
- Around line 62-94: The public accessor methods in TicklerLinkDTO (getId,
setId, getTicklerNo, setTicklerNo, getTableName, setTableName, getTableId,
setTableId) lack the required JavaDoc; update each public getter/setter with
full JavaDoc including a one-line description, an `@return` tag with the exact
return type (e.g., Integer, String, Long) for getters, an `@param` tag with the
exact parameter type for setters, an `@since` tag populated using the git history
date for when the method was introduced, and include `@throws` tags only if the
method can throw checked exceptions (none in these accessors). Ensure the
JavaDoc format matches project guidelines for all listed methods in
TicklerLinkDTO.
In `@src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java`:
- Around line 172-295: The public getters/setters in TicklerListDTO (e.g.,
getId, setId, getMessage, setMessage, getServiceDate, setServiceDate,
getCreateDate, setCreateDate, getStatus, setStatus, getPriority, setPriority,
getDemographicNo, setDemographicNo, getDemographicLastName,
setDemographicLastName, getDemographicFirstName, setDemographicFirstName,
getCreatorLastName, setCreatorLastName, getCreatorFirstName,
setCreatorFirstName, getAssigneeLastName, setAssigneeLastName,
getAssigneeFirstName, setAssigneeFirstName, getComments, setComments, getLinks,
setLinks) lack the required JavaDoc; add Javadoc blocks for each public method
including a concise description, `@param` for setters (with exact data types),
`@return` for getters (with exact return types), any applicable `@throws`, and an
`@since` tag populated from the git history date for when the method was
introduced; ensure setComments and setLinks note the null-to-empty-list behavior
in the description and `@param`, and apply the same JavaDoc style consistently
across all those methods in TicklerListDTO.
In `@src/main/webapp/tickler/ticklerMain.jsp`:
- Around line 875-878: The onclick handler and the JavaScript URL parameters
embed unencoded values; update the popupPage and openNoteDialog invocations to
wrap dynamic values with OWASP Encoder's Encode.forUriComponent() (e.g. use
Encode.forUriComponent(tickler.getDemographicNo()) and
Encode.forUriComponent(tickler.getId())) so the demographic_no and id URL
parameters are safely encoded in the onClick attribute and in openNoteDialog
calls; ensure you replace the raw tickler.getDemographicNo() and tickler.getId()
usages in the popupPage(...) and openNoteDialog(...) argument strings with the
encoded variants.
In
`@src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java`:
- Around line 118-133: The test
shouldReturnEmptyList_whenFilterMatchesNoTicklers currently doesn’t assert
emptiness; make the filter guaranteed to match nothing (e.g., use clearly
impossible values on CustomFilter such as
filter.setStatus("NON_EXISTENT_STATUS") and
filter.setPriority("NON_EXISTENT_PRIORITY") or set an impossible identifier) and
replace the loose assertions with assertThat(results).isEmpty(); reference the
existing CustomFilter instance and the ticklerDao.getTicklerDTOs(...) call so
the test deterministically verifies an empty result.
- Around line 54-60: The test class TicklerDaoDTOIntegrationTest is missing the
required `@Tag`("dao") annotation; update the class declaration in
TicklerDaoDTOIntegrationTest (the class that extends
TicklerDaoBaseIntegrationTest) to include `@Tag`("dao") alongside the existing
tags so Maven test filtering picks it up (i.e., add `@Tag`("dao") to the
annotation list above the class).
In
`@src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerCommentDTOTest.java`:
- Line 42: The test class TicklerCommentDTOTest must extend the modern OpenO
test base; update its declaration to extend OpenOUnitTestBase (instead of a
plain class) so SpringUtils mocking and JUnit5 modern test conventions work;
ensure the appropriate import for OpenOUnitTestBase is added and keep the class
as a unit test (use OpenOUnitTestBase rather than OpenOTestBase).
- Around line 37-40: The test class TicklerCommentDTOTest is missing a standard
CRUD/filter tag; update the class-level `@Tag` annotations (those currently
`@Tag`("unit"), `@Tag`("fast"), `@Tag`("tickler"), `@Tag`("dto")) to include at least
one standard tag such as `@Tag`("read") (or another from the allowed set:
"integration","dao","create","read","update","delete") so modern Maven test
filters will pick it up; keep the existing custom tags and add the chosen
standard tag annotation to the class declaration in TicklerCommentDTOTest.
In `@src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerListDTOTest.java`:
- Line 43: The test class TicklerListDTOTest does not extend the required OpenO
test base; change its declaration to extend the appropriate base (likely
OpenOUnitTestBase for a pure unit test) so SpringUtils and modern test behaviors
work correctly. Locate the class TicklerListDTOTest and update its declaration
to extend OpenOUnitTestBase (or OpenOTestBase if this is an integration test),
and ensure any necessary imports for the chosen base are added and compile. Keep
the original BDD-style test method names intact and run the unit tests to verify
SpringUtils-related mocks behave as expected.
- Around line 38-41: The test class TicklerListDTOTest uses only custom tags
(`@Tag`("unit"), `@Tag`("fast"), `@Tag`("tickler"), `@Tag`("dto")) which aren't
recognized by standard Maven filters; update the class annotations (the `@Tag`
lines in TicklerListDTOTest) to include at least one standard CRUD/filter tag
such as `@Tag`("read") (you may also add `@Tag`("create"), `@Tag`("update"),
`@Tag`("delete"), or `@Tag`("integration") as needed) while keeping the existing
custom tags so modern test filtering picks up this test.
…om/cc-ar-emr/Open-O into performance/dto-projection-tickler
|
@coderabbitai review |
|
@claude review |
✅ Actions performedReview triggered.
|
|
Claude finished @LiamStanziani's task in 2m 36s —— View job Code Review Complete ✅I've completed a comprehensive review of PR #2257. This is a well-implemented performance optimization that successfully addresses N+1 query issues through DTO projections. The code quality is generally excellent with proper security controls and comprehensive test coverage. SummaryOverall Assessment: ✅ APPROVE with minor recommendations This PR delivers on its promise to reduce database queries from ~25 to 3 per page load through well-designed DTO projection. The implementation follows OpenO EMR coding standards with proper security checks, OWASP encoding, and audit logging. The test coverage is comprehensive with both unit and integration tests following BDD naming conventions. Performance Impact: 🚀 ~88% reduction in database queries (from ~25 to 3) Strengths1. Excellent DTO Design ✅
2. Proper Security Implementation ✅The code follows all OpenO EMR security requirements: OWASP Encoding (ticklerMain.jsp): <%=Encode.forHtmlContent(tickler.getDemographicLastName())%>
<%=Encode.forHtmlContent(tickler.getMessage())%>
<%=Encode.forUriComponent(String.valueOf(tickler.getDemographicNo()))%>
<%=Encode.forJavaScript(String.valueOf(tickler.getId()))%>✅ All user-controlled data properly encoded for context Authorization Checks (TicklerManagerImpl.java:778, 800): checkPrivilege(loggedInInfo, PRIVILEGE_READ);✅ Proper Parameterized Queries (TicklerDaoImpl.java:436-437): Query query = entityManager.createQuery(sql);
for (int x = 0; x < paramList.size(); x++) {
query.setParameter(x + 1, paramList.get(x));
}✅ All query parameters properly bound, preventing SQL injection Audit Logging (TicklerManagerImpl.java:784-787): for (TicklerListDTO dto : result) {
LogAction.addLogSynchronous(loggedInInfo, "TicklerManager.getTicklerDTOs",
"ticklerId=" + dto.getId());
}✅ PHI access properly logged when filtering by demographic 3. Efficient Batch Loading Pattern ✅The DAO implementation correctly eliminates N+1 queries: Main Query (TicklerDaoImpl.java:494-502): SELECT NEW ca.openosp.openo.tickler.dto.TicklerListDTO(...)
FROM Tickler t
LEFT JOIN Demographic d ON d.DemographicNo = t.demographicNo
LEFT JOIN Provider creator ON creator.ProviderNo = t.creator
LEFT JOIN Provider assignee ON assignee.ProviderNo = t.taskAssignedToBatch Comment Loading (lines 632-642): WHERE c.ticklerNo IN (:ticklerIds)Batch Link Loading (lines 673-680): WHERE l.ticklerNo IN (:ticklerIds)This achieves exactly 3 queries as claimed, properly reducing the EAGER relationship overhead. 4. Comprehensive Test Coverage ✅Modern JUnit 5 tests with proper organization: Test Class Structure (TicklerDaoDTOIntegrationTest.java):
Unit Tests:
5. Proper Package Structure ✅
Issues FoundCritical Issues: None ✅The code is production-ready with no blocking issues. Recommendations for Future Enhancement1. Consider Defensive Copying for Collections (Minor)Location: The getters for public List<TicklerCommentDTO> getComments() {
return comments; // Exposes internal list
}Recommendation: For strict immutability, consider defensive copying in getters as well: public List<TicklerCommentDTO> getComments() {
return new ArrayList<>(comments);
}Impact: Low - DTOs are typically used in read-only contexts, but defensive copying would prevent accidental modification. 2. Potential for Query Optimization (Enhancement)Location: The query builder uses positional parameters ( // Current approach (positional)
query.append("AND t.status = ?").append(paramIndex++);
paramList.add(status);
// Alternative (named parameters)
query.append("AND t.status = :status ");
query.setParameter("status", status);Benefit: Easier to maintain and less error-prone for complex conditional queries. Trade-off: The current approach works correctly and is already implemented. This is an enhancement for future refactoring, not a bug. Performance AnalysisVerified Performance Claims ✅Before: ~25 database queries per page load
After: Exactly 3 queries
Reduction: ~88% fewer database queries 🚀 This matches the PR description's claim of reducing queries from ~25 to 3 (not 2 as mentioned in some JavaDoc, but 3 is still accurate). Code Review by CategorySecurity: ✅ EXCELLENT
Code Quality: ✅ EXCELLENT
Test Coverage: ✅ EXCELLENT
Documentation: ✅ GOOD
Minor note: The class-level JavaDoc in JSP Review: ✅ SECURE
Comparison with Other ReviewsI've reviewed the feedback from other AI reviewers (sourcery-ai, coderabbitai, gemini-code-assist, copilot). Most suggestions fall into these categories:
The code quality is already high, and the security implementation is excellent. Final Verdict✅ APPROVEDThis PR is ready to merge. It delivers significant performance improvements while maintaining OpenO EMR's high security and code quality standards. Key Achievements:
Recommendations for follow-up (optional enhancements):
Great work on this performance optimization! The reduction from ~25 to 3 queries will significantly improve the user experience on the TicklerMain page. 🚀 |
There was a problem hiding this comment.
Sorry @LiamStanziani, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Will need to re-test, but want to see a final review again |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||||||||||||||
There was a problem hiding this comment.
1 issue found across 12 files
Confidence score: 4/5
- Minor UI formatting risk in
src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java: provider name can render with a leading/trailing comma when only one name part is present. - Overall impact appears small and localized, so this PR is likely safe to merge with minimal risk.
- Pay close attention to
src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java- provider name formatting logic may produce stray commas.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java">
<violation number="1" location="src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java:84">
P3: Avoid returning a leading/trailing comma when only one of the provider name parts is present; it will render as ", John" or "Doe, " in the UI.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. This PR demonstrates excellent adherence to OpenO EMR coding standards: ✅ Security Compliance
✅ Package Structure
✅ Documentation Standards
✅ Modern Test Framework
✅ Code Quality
The performance optimization (25 queries → 2-3 queries) is well-architected and maintains all security, compliance, and audit logging requirements. |
|
I see one small issue with ordering compared to the old version, I will fix soon, and that's the only issue i see |
Code ReviewI found 1 issue that should be addressed: Incorrect OWASP encoding context for JavaScript function parametersFile: The code uses CLAUDE.md Rule:
Current code: onClick="openNoteDialog('<%=Encode.forJavaScriptAttribute(String.valueOf(tickler.getDemographicNo()))%>','<%=Encode.forJavaScriptAttribute(String.valueOf(tickler.getId()))%>')"Recommended fix: onClick="openNoteDialog('<%=Encode.forJavaScript(String.valueOf(tickler.getDemographicNo()))%>','<%=Encode.forJavaScript(String.valueOf(tickler.getId()))%>')"The values are within JavaScript string literals (single quotes), so Reference: See CLAUDE.md Error Handling section and appointmentprovideradminday.jsp:1213 which correctly uses |
|
No, this use is fine. It works for this use case. Also not sure why I can't tag yingbull for review |
User description
In this PR, I have:
I have tested this by:
Summary by Sourcery
Introduce DTO-based read path for tickler list views to reduce database load and support efficient projection of ticklers, comments, and links.
New Features:
Enhancements:
Tests:
Summary by cubic
Introduced DTO projection for Tickler list reads (ticklers, comments, links) and updated ticklerMain.jsp to use it. This removes N+1 queries and cuts TicklerMain page DB calls from ~25 to 2–3 per load.
Refactors
Migration
Written for commit 61d8389. Summary will update on new commits.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.
PR Type
Enhancement, Tests
Description
Introduced DTO projection for tickler list reads reducing database queries from ~25 to 2-3 per page load
TicklerListDTO,TicklerCommentDTO,TicklerLinkDTOwith JPQL constructor projectionsAdded new DAO and Manager methods for optimized tickler retrieval with pagination support
getTicklerDTOs(filter)andgetTicklerDTOs(filter, offset, limit)in both TicklerDao and TicklerManagerRefactored
ticklerMain.jspto render from DTOs only, removing legacy entity-based rendering pathAdded comprehensive unit and integration tests for DTO functionality and formatting methods
Diagram Walkthrough
File Walkthrough
8 files
Added DTO projection method signaturesImplemented DTO projection with batch loadingAdded DTO retrieval method signaturesImplemented DTO retrieval with privilege checksCreated main DTO for tickler list displayCreated DTO for tickler comments with formattingCreated lightweight DTO for tickler linksRefactored to render from DTOs only3 files
Added integration tests for DTO projectionAdded unit tests for comment DTO methodsAdded unit tests for list DTO methods1 files
Added TicklerLink entity to test configuration