Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.springframework.samples.petclinic.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("patientRecords");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import java.util.concurrent.ThreadLocalRandom;

@RestController
@RequestMapping("/api/patient-records")
public class PatientRecordController implements InitializingBean {
@RequestMapping("/api/patient-records")public class PatientRecordController implements InitializingBean {

private static final Logger logger = LoggerFactory.getLogger(PatientRecordController.class);

Expand All @@ -46,9 +45,7 @@ public PatientRecordController(PatientRecordDataService dataService,
@Override
public void afterPropertiesSet() throws Exception {
this.otelTracer = openTelemetry.getTracer("PatientRecordController");
}

@PostMapping("/populate-records")
}@PostMapping("/populate-records")
public ResponseEntity<String> populateData(@RequestParam(name = "count", defaultValue = "6000000") int count) {
logger.info("Received request to populate {} patient records.", count);
if (count <= 0) {
Expand All @@ -64,29 +61,54 @@ public ResponseEntity<String> populateData(@RequestParam(name = "count", default
logger.error("Error during patient records population", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error during data population: " + e.getMessage());
}
}

@GetMapping(value = "/query-records", produces = "application/json")
public List<Map<String, Object>> getRecords(
@RequestParam(name = "weight", defaultValue = "5000") int patientWeight,
@RequestParam(name = "repetitions", defaultValue = "1") int repetitions) {
}@GetMapping(value = "/query-records", produces = "application/json")
@Cacheable(value = "patientRecords", key = "#patientWeight + '-' + #page + '-' + #size")
public ResponseEntity<Map<String, Object>> getRecords(
@RequestParam(name = "weight", defaultValue = "5000") int patientWeight,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "20") int size) {

logger.info("Querying patient records by weight: {} (page: {}, size: {})",
patientWeight, page, size);

try {
// Validate pagination parameters
if (page < 0 || size <= 0 || size > 100) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Invalid pagination parameters. Page must be >= 0 and size must be between 1 and 100."));
}

logger.info("Querying patient records by weight: {} (repetitions: {})", patientWeight, repetitions);
// Calculate offset
int offset = page * size;

// Get total count
String countSql = "SELECT COUNT(*) FROM patient_records WHERE patient_weight = ?";
int totalRecords = mysqlJdbcTemplate.queryForObject(countSql, Integer.class, patientWeight);

// Get paginated records
String sql = "SELECT id, treatment_type, patient_weight, visit_date, treatment_completed, medical_notes " +
"FROM patient_records WHERE patient_weight = ?";
"FROM patient_records WHERE patient_weight = ? " +
"LIMIT ? OFFSET ?";

List<Map<String, Object>> lastResults = null;
for (int i = 0; i < repetitions; i++) {
lastResults = mysqlJdbcTemplate.queryForList(sql, patientWeight);
}
List<Map<String, Object>> records = mysqlJdbcTemplate.queryForList(sql, patientWeight, size, offset);

Map<String, Object> response = new HashMap<>();
response.put("records", records);
response.put("currentPage", page);
response.put("totalItems", totalRecords);
response.put("totalPages", (int) Math.ceil((double) totalRecords / size));

logger.info("Query completed. Found {} records for weight: {} (page: {}/{})",
records.size(), patientWeight, page + 1, response.get("totalPages"));

return ResponseEntity.ok(response);

logger.info("Query completed. Found {} records for weight: {}",
lastResults != null ? lastResults.size() : 0, patientWeight);
return lastResults;
} catch (Exception e) {
logger.error("Error querying patient records", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Error querying patient records: " + e.getMessage()));
}

@DeleteMapping("/cleanup-records")
}}@DeleteMapping("/cleanup-records")
public ResponseEntity<String> cleanupRecords() {
logger.info("Received request to cleanup all patient records.");
try {
Expand All @@ -96,9 +118,7 @@ public ResponseEntity<String> cleanupRecords() {
logger.error("Error during patient records cleanup", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error during cleanup: " + e.getMessage());
}
}

@PostMapping("/recreate-and-populate-records")
}@PostMapping("/recreate-and-populate-records")
public ResponseEntity<String> recreateAndPopulateRecords(@RequestParam(name = "count", defaultValue = "6000000") int count) {
logger.info("Received request to recreate and populate {} patient records.", count);
if (count <= 0) {
Expand All @@ -110,9 +130,7 @@ public ResponseEntity<String> recreateAndPopulateRecords(@RequestParam(name = "c
try {
// Drop the table
mysqlJdbcTemplate.execute("DROP TABLE IF EXISTS patient_records");
logger.info("Table 'patient_records' dropped successfully.");

// Recreate the table with MySQL syntax
logger.info("Table 'patient_records' dropped successfully.");// Recreate the table with MySQL syntax
String createTableSql =
"CREATE TABLE patient_records (" +
"id INT AUTO_INCREMENT PRIMARY KEY," +
Expand All @@ -127,9 +145,7 @@ public ResponseEntity<String> recreateAndPopulateRecords(@RequestParam(name = "c
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";

mysqlJdbcTemplate.execute(createTableSql);
logger.info("Table 'patient_records' created successfully.");

// Populate data
logger.info("Table 'patient_records' created successfully.");// Populate data
dataService.populateData(count);
return ResponseEntity.ok("Successfully recreated and initiated population of " + count + " patient records.");
} catch (Exception e) {
Expand All @@ -144,9 +160,7 @@ public ResponseEntity<String> 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 = "PatientBatch_Type" + queryTypeChar;
Span typeParentSpan = otelTracer.spanBuilder(parentSpanName).startSpan();
Expand All @@ -160,15 +174,11 @@ public ResponseEntity<String> runSimulatedQueries(
} finally {
typeParentSpan.end();
}
}

long endTime = System.currentTimeMillis();
}long endTime = System.currentTimeMillis();
String message = String.format("Executed %d simulated patient query operations in %d ms.", totalOperations, (endTime - startTime));
logger.info(message);
return ResponseEntity.ok(message);
}

private void performObservablePatientOperation(String operationName) {
}private void performObservablePatientOperation(String operationName) {
Span span = otelTracer.spanBuilder(operationName)
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.system", "mysql")
Expand All @@ -187,4 +197,4 @@ private void performObservablePatientOperation(String operationName) {
span.end();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.springframework.samples.petclinic.patientrecords;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -9,26 +11,25 @@
import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface PatientRecordRepository extends JpaRepository<PatientRecord, Integer> {
@Repositorypublic interface PatientRecordRepository extends JpaRepository<PatientRecord, Integer> {

@Query("SELECT pr FROM PatientRecord pr WHERE pr.treatmentType = :treatmentType " +
"AND pr.patientWeight >= :minWeight AND pr.patientWeight <= :maxWeight " +
"AND pr.visitDate >= :startDate AND pr.visitDate < :endDate " +
"AND pr.treatmentCompleted = :treatmentCompleted")
List<PatientRecord> findByComplexCriteria(
Page<PatientRecord> findByComplexCriteria(
@Param("treatmentType") String treatmentType,
@Param("minWeight") Integer minWeight,
@Param("maxWeight") Integer maxWeight,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate,
@Param("treatmentCompleted") Boolean treatmentCompleted
@Param("treatmentCompleted") Boolean treatmentCompleted,
Pageable pageable
);

@Query("SELECT COUNT(1) FROM PatientRecord pr WHERE pr.treatmentType = :treatmentType")
int countRecordsByTreatmentType(@Param("treatmentType") String treatmentType);

@Query("SELECT COUNT(1) FROM PatientRecord pr WHERE pr.treatmentType = :treatmentType " +
@Query("SELECT COUNT(1) FROM PatientRecord pr WHERE pr.treatmentType = :treatmentType " +
"AND pr.treatmentCompleted = true")
int countCompletedRecordsByTreatmentType(@Param("treatmentType") String treatmentType);
}
}
5 changes: 3 additions & 2 deletions src/main/resources/db/mysql/patient_records_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ CREATE TABLE patient_records (

INDEX idx_treatment_type (treatment_type),
INDEX idx_visit_date (visit_date),
INDEX idx_treatment_completed (treatment_completed)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INDEX idx_treatment_completed (treatment_completed),
INDEX idx_patient_weight (patient_weight)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;