From f227cb0cd7d7c7d0ecc07f3ba7fc1621a4c1123f Mon Sep 17 00:00:00 2001 From: Roni Dover Date: Thu, 9 Oct 2025 08:39:44 -0700 Subject: [PATCH] Enhance queryLogs method with pagination pagination filtering and performance optimizations --- .../ClinicActivityController.java | 93 ++++++++++++------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/clinicactivity/ClinicActivityController.java b/src/main/java/org/springframework/samples/petclinic/clinicactivity/ClinicActivityController.java index 41302f59b69..04e6dd3fee6 100644 --- a/src/main/java/org/springframework/samples/petclinic/clinicactivity/ClinicActivityController.java +++ b/src/main/java/org/springframework/samples/petclinic/clinicactivity/ClinicActivityController.java @@ -21,7 +21,50 @@ @RestController @RequestMapping("/api/clinic-activity") -public class ClinicActivityController implements InitializingBean { +public class ClinicActivityController { + private static final Logger logger = LoggerFactory.getLogger(ClinicActivityController.class); + private static final int MAX_RESULT_SIZE = 1000; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @GetMapping("/logs") + public ResponseEntity> queryLogs( + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "50") int size, + @RequestParam(required = false) Map filters) { + + // Validate and limit page size + size = Math.min(size, MAX_RESULT_SIZE); + int offset = page * size; + + // Build dynamic query with prepared statement + StringBuilder queryBuilder = new StringBuilder("SELECT * FROM clinic_activity_logs WHERE 1=1 "); + List params = new ArrayList<>(); + + // Add dynamic filters + if (filters != null) { + for (Map.Entry entry : filters.entrySet()) { + queryBuilder.append(" AND ").append(entry.getKey()).append(" = ? "); + params.add(entry.getValue()); + } + } + + // Add pagination + queryBuilder.append(" LIMIT ? OFFSET ? "); + params.add(size); + params.add(offset); + + // Execute query with parameter binding + List logs = jdbcTemplate.query( + queryBuilder.toString(), + params.toArray(), + new BeanPropertyRowMapper<>(ClinicActivityLog.class) + ); + + return ResponseEntity.ok(logs); + } +}public class ClinicActivityController implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ClinicActivityController.class); @@ -46,9 +89,7 @@ public ClinicActivityController(ClinicActivityDataService dataService, @Override public void afterPropertiesSet() throws Exception { this.otelTracer = openTelemetry.getTracer("ClinicActivityController"); - } - - // This ep is here to throw error + }// This ep is here to throw error @GetMapping("active-errors-ratio") public int getActiveErrorsRatio() { return dataService.getActiveLogsRatio("errors"); @@ -67,21 +108,20 @@ public ResponseEntity populateData(@RequestParam(name = "count", default logger.error("Error during clinic activity log population", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error during data population: " + e.getMessage()); } - } - - @GetMapping(value = "/query-logs", produces = "application/json") + }@GetMapping(value = "/query-logs", produces = "application/json") public List> getLogs( - @RequestParam(name = "repetitions", defaultValue = "1") int repetitions) { - int numericValueToTest = 50000; - String sql = "SELECT id, activity_type, numeric_value, event_timestamp, status_flag, payload FROM clinic_activity_logs WHERE numeric_value = ?"; + @RequestParam(name = "repetitions", defaultValue = "1") int repetitions, + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "100") int size, + @RequestParam(name = "filterValue", required = false) Integer filterValue) { + int numericValueToTest = filterValue != null ? filterValue : 50000; + String sql = "SELECT id, activity_type, numeric_value, event_timestamp, status_flag, payload FROM clinic_activity_logs WHERE numeric_value = ? LIMIT ? OFFSET ?"; List> lastResults = null; for (int i = 0; i < repetitions; i++) { - lastResults = jdbcTemplate.queryForList(sql, numericValueToTest); + lastResults = jdbcTemplate.queryForList(sql, numericValueToTest, size, page * size); } return lastResults; - } - - @DeleteMapping("/cleanup-logs") + }@DeleteMapping("/cleanup-logs") public ResponseEntity cleanupLogs() { logger.info("Received request to cleanup all clinic activity logs."); try { @@ -99,9 +139,7 @@ public ResponseEntity runSimulatedQueries( @RequestParam(name = "repetitions", defaultValue = "100") int repetitions ) { long startTime = System.currentTimeMillis(); - int totalOperations = 0; - - for (int queryTypeIndex = 0; queryTypeIndex < uniqueQueriesCount; queryTypeIndex++) { + int totalOperations = 0;for (int queryTypeIndex = 0; queryTypeIndex < uniqueQueriesCount; queryTypeIndex++) { char queryTypeChar = (char) ('A' + queryTypeIndex); String parentSpanName = "Batch_Type" + queryTypeChar; Span typeParentSpan = otelTracer.spanBuilder(parentSpanName).startSpan(); @@ -115,9 +153,7 @@ public ResponseEntity runSimulatedQueries( } finally { typeParentSpan.end(); } - } - - long endTime = System.currentTimeMillis(); + }long endTime = System.currentTimeMillis(); String message = String.format("Executed %d simulated clinic query operations in %d ms.", totalOperations, (endTime - startTime)); logger.info(message); return ResponseEntity.ok(message); @@ -132,9 +168,7 @@ public ResponseEntity recreateAndPopulateLogs(@RequestParam(name = "coun try { // Drop the table jdbcTemplate.execute("DROP TABLE IF EXISTS clinic_activity_logs"); - logger.info("Table 'clinic_activity_logs' dropped successfully."); - - // Recreate the table + logger.info("Table 'clinic_activity_logs' dropped successfully.");// Recreate the table String createTableSql = "CREATE TABLE clinic_activity_logs (" + "id SERIAL PRIMARY KEY," + "activity_type VARCHAR(255)," + @@ -153,9 +187,7 @@ public ResponseEntity recreateAndPopulateLogs(@RequestParam(name = "coun logger.error("Error during clinic activity log recreation and population", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error during data recreation and population: " + e.getMessage()); } - } - - @PostMapping("/io-intensive-load") + }@PostMapping("/io-intensive-load") public ResponseEntity createIOIntensiveLoad(@RequestParam(name = "duration", defaultValue = "5") int durationMinutes, @RequestParam(name = "threads", defaultValue = "6") int numThreads, @RequestParam(name = "limit", defaultValue = "400000") int limit) { @@ -170,8 +202,7 @@ public ResponseEntity createIOIntensiveLoad(@RequestParam(name = "durati if (numThreads <= 0) { return ResponseEntity.badRequest().body("Number of threads must be a positive integer."); } - if (numThreads > 20) { - return ResponseEntity.badRequest().body("Too many threads for I/O intensive load - maximum 20 to prevent system crash."); + if (numThreads > 20) {return ResponseEntity.badRequest().body("Too many threads for I/O intensive load - maximum 20 to prevent system crash."); } if (limit <= 0) { return ResponseEntity.badRequest().body("Limit must be a positive integer."); @@ -186,9 +217,7 @@ public ResponseEntity createIOIntensiveLoad(@RequestParam(name = "durati logger.error("Error during I/O intensive load", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error during I/O intensive load: " + e.getMessage()); } - } - - private void performObservableOperation(String operationName) { + }private void performObservableOperation(String operationName) { Span span = otelTracer.spanBuilder(operationName) .setSpanKind(SpanKind.CLIENT) .setAttribute("db.system", "postgresql") @@ -207,4 +236,4 @@ private void performObservableOperation(String operationName) { span.end(); } } -} +} \ No newline at end of file