From 6d5da5ee43cfbde93317265ec3aaad93ac7f7d4f Mon Sep 17 00:00:00 2001 From: divyanshkande Date: Fri, 26 Dec 2025 12:51:11 +0530 Subject: [PATCH 1/3] Add optimized reminder scheduling with documentation --- README.md | 27 +++++++ .../com/libraryman_api/entity/Reminder.java | 74 +++++++++++++++++++ .../libraryman_api/entity/ReminderType.java | 11 +++ .../repository/ReminderRepository.java | 18 +++++ .../scheduler/ReminderScheduler.java | 48 ++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 src/main/java/com/libraryman_api/entity/Reminder.java create mode 100644 src/main/java/com/libraryman_api/entity/ReminderType.java create mode 100644 src/main/java/com/libraryman_api/repository/ReminderRepository.java create mode 100644 src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java diff --git a/README.md b/README.md index 6813b64..00e051b 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,33 @@ If you have any questions or would like to connect, please don't hesitate to rea

+--- + +## Reminder Scheduling Optimization +This logic is implemented in the `ReminderScheduler` and `ReminderRepository` +using scheduled jobs and optimized JPA queries. + +### Problem +The application previously scanned all borrow records daily to identify +upcoming due dates, resulting in unnecessary database load and performance +bottlenecks. + +### Solution +A dedicated `Reminder` entity is now created when a book is borrowed. +The scheduler queries only reminders that are due on a given date, +eliminating full table scans. + +### Key Improvements +- Reduced number of database queries +- Improved performance and scalability +- Cleaner separation of concerns + +### Edge Case Handling +- Book renewals update the scheduled reminder date +- Early returns remove pending reminders +- Overdue reminders trigger daily notifications with dynamic fine calculation + +--- ## Thankyou ❤️ Thank you for taking the time to explore my project. I hope you find them informative and useful in your journey to learn Java and enhance your programming skills. Your support and contributions are highly appreciated. diff --git a/src/main/java/com/libraryman_api/entity/Reminder.java b/src/main/java/com/libraryman_api/entity/Reminder.java new file mode 100644 index 0000000..7a3758b --- /dev/null +++ b/src/main/java/com/libraryman_api/entity/Reminder.java @@ -0,0 +1,74 @@ +package com.libraryman_api.entity; + +import java.time.LocalDate; + +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +/** + * Entity representing a scheduled reminder for borrowed books. + * + *

This entity is created at the time of borrowing a book and is used to + * efficiently schedule due-date and overdue notifications without scanning + * all borrow records daily.

+ * + *

Each reminder is triggered on a specific date and marked as sent + * after notification delivery.

+ */ + +@Entity +public class Reminder { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getBorrowingId() { + return borrowingId; + } + + public void setBorrowingId(Long borrowingId) { + this.borrowingId = borrowingId; + } + + public LocalDate getReminderDate() { + return reminderDate; + } + + public void setReminderDate(LocalDate reminderDate) { + this.reminderDate = reminderDate; + } + + public boolean isSent() { + return sent; + } + + public void setSent(boolean sent) { + this.sent = sent; + } + + public ReminderType getType() { + return type; + } + + public void setType(ReminderType type) { + this.type = type; + } + + private Long borrowingId; + private LocalDate reminderDate; + private boolean sent; + + @Enumerated(EnumType.STRING) + private ReminderType type; +} \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/entity/ReminderType.java b/src/main/java/com/libraryman_api/entity/ReminderType.java new file mode 100644 index 0000000..6334e39 --- /dev/null +++ b/src/main/java/com/libraryman_api/entity/ReminderType.java @@ -0,0 +1,11 @@ +package com.libraryman_api.entity; +/** + * Defines the type of reminder to be sent. + * + * DUE_SOON - Reminder sent before the book due date. + * OVERDUE - Daily reminder sent after the due date has passed. + */ +public enum ReminderType { + DUE_SOON, + OVERDUE +} \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/repository/ReminderRepository.java b/src/main/java/com/libraryman_api/repository/ReminderRepository.java new file mode 100644 index 0000000..a819b8d --- /dev/null +++ b/src/main/java/com/libraryman_api/repository/ReminderRepository.java @@ -0,0 +1,18 @@ +package com.libraryman_api.repository; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.libraryman_api.entity.Reminder; +/** + * Repository for accessing and managing Reminder entities. + * + * Provides optimized queries to fetch only pending reminders + * scheduled for a specific date. + */ + +public interface ReminderRepository extends JpaRepository { + List findByReminderDateAndSentFalse(LocalDate date); +} diff --git a/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java b/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java new file mode 100644 index 0000000..5b5cefd --- /dev/null +++ b/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java @@ -0,0 +1,48 @@ +package com.libraryman_api.scheduler; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.libraryman_api.entity.Reminder; +import com.libraryman_api.repository.ReminderRepository; + +@Component +/** + * Scheduler responsible for sending due-date and overdue book reminders. + * + *

This scheduler queries only the reminders that are due on the current + * date, avoiding full table scans of borrowing records and significantly + * improving system performance.

+ */ + +public class ReminderScheduler { + + private final ReminderRepository reminderRepository; + public ReminderScheduler(ReminderRepository reminderRepository) { + this.reminderRepository = reminderRepository; + } + /** + * Sends reminders scheduled for the current date. + * + *

Executed daily at 9 AM to process pending reminders and mark them + * as sent after successful notification delivery.

+ */ + + @Scheduled(cron = "0 0 9 * * ?") + public void sendDueReminders() { + + LocalDate today = LocalDate.now(); + List reminders = + reminderRepository.findByReminderDateAndSentFalse(today); + + for (Reminder reminder : reminders) { + // call notification logic + reminder.setSent(true); + } + + reminderRepository.saveAll(reminders); + } +} From 412565c7566f2120ecc16f3f28eee9a08e6d123e Mon Sep 17 00:00:00 2001 From: divyanshkande Date: Fri, 26 Dec 2025 22:23:15 +0530 Subject: [PATCH 2/3] Fix review comments and improve reminder scheduler --- pom.xml | 6 +++ .../com/libraryman_api/entity/Reminder.java | 26 ++++++++---- .../scheduler/ReminderScheduler.java | 41 +++++++++++++------ .../service/NotificationService.java | 17 ++++++++ 4 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/libraryman_api/service/NotificationService.java diff --git a/pom.xml b/pom.xml index 6dfee89..a4adfa2 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,12 @@ org.springframework.boot spring-boot-starter-actuator + + org.projectlombok + lombok + 1.18.30 + provided + diff --git a/src/main/java/com/libraryman_api/entity/Reminder.java b/src/main/java/com/libraryman_api/entity/Reminder.java index 7a3758b..cfd3167 100644 --- a/src/main/java/com/libraryman_api/entity/Reminder.java +++ b/src/main/java/com/libraryman_api/entity/Reminder.java @@ -32,7 +32,9 @@ public Long getId() { public void setId(Long id) { this.id = id; } - + + private Long borrowingId; + public Long getBorrowingId() { return borrowingId; } @@ -40,7 +42,9 @@ public Long getBorrowingId() { public void setBorrowingId(Long borrowingId) { this.borrowingId = borrowingId; } - + + private LocalDate reminderDate; + public LocalDate getReminderDate() { return reminderDate; } @@ -48,7 +52,9 @@ public LocalDate getReminderDate() { public void setReminderDate(LocalDate reminderDate) { this.reminderDate = reminderDate; } - + + private boolean sent; + public boolean isSent() { return sent; } @@ -56,7 +62,10 @@ public boolean isSent() { public void setSent(boolean sent) { this.sent = sent; } - + + @Enumerated(EnumType.STRING) + private ReminderType type; + public ReminderType getType() { return type; } @@ -65,10 +74,9 @@ public void setType(ReminderType type) { this.type = type; } - private Long borrowingId; - private LocalDate reminderDate; - private boolean sent; + + + - @Enumerated(EnumType.STRING) - private ReminderType type; + } \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java b/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java index 5b5cefd..f391c2b 100644 --- a/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java +++ b/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java @@ -1,37 +1,46 @@ package com.libraryman_api.scheduler; - import java.time.LocalDate; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import com.libraryman_api.entity.Reminder; import com.libraryman_api.repository.ReminderRepository; +import com.libraryman_api.service.NotificationService; -@Component /** * Scheduler responsible for sending due-date and overdue book reminders. * - *

This scheduler queries only the reminders that are due on the current - * date, avoiding full table scans of borrowing records and significantly - * improving system performance.

+ *

This scheduler runs daily and processes only reminders that are due + * on the current date, avoiding unnecessary full-table scans.

*/ - +@Component public class ReminderScheduler { + private static final Logger log = + LoggerFactory.getLogger(ReminderScheduler.class); + private final ReminderRepository reminderRepository; - public ReminderScheduler(ReminderRepository reminderRepository) { + private final NotificationService notificationService; + + public ReminderScheduler(ReminderRepository reminderRepository, + NotificationService notificationService) { this.reminderRepository = reminderRepository; + this.notificationService = notificationService; } + /** * Sends reminders scheduled for the current date. * - *

Executed daily at 9 AM to process pending reminders and mark them - * as sent after successful notification delivery.

+ *

Runs daily at 9 AM. A reminder is marked as sent only after + * successful notification delivery.

*/ - @Scheduled(cron = "0 0 9 * * ?") + @Transactional public void sendDueReminders() { LocalDate today = LocalDate.now(); @@ -39,8 +48,16 @@ public void sendDueReminders() { reminderRepository.findByReminderDateAndSentFalse(today); for (Reminder reminder : reminders) { - // call notification logic - reminder.setSent(true); + try { + notificationService.sendReminder(reminder); // actual notification + reminder.setSent(true); + } catch (Exception e) { + log.error( + "Failed to send reminder for borrowingId: {}", + reminder.getBorrowingId(), + e + ); + } } reminderRepository.saveAll(reminders); diff --git a/src/main/java/com/libraryman_api/service/NotificationService.java b/src/main/java/com/libraryman_api/service/NotificationService.java new file mode 100644 index 0000000..0fcb302 --- /dev/null +++ b/src/main/java/com/libraryman_api/service/NotificationService.java @@ -0,0 +1,17 @@ +package com.libraryman_api.service; + +import org.springframework.stereotype.Service; + +import com.libraryman_api.entity.Reminder; + +@Service +public class NotificationService { + + public void sendReminder(Reminder reminder) { + + System.out.println( + "Reminder sent for borrowingId: " + reminder.getBorrowingId() + + " Type: " + reminder.getType() + ); + } +} From 22572749df9decdb50dd511f48e80ff735d6e0c2 Mon Sep 17 00:00:00 2001 From: divyanshkande Date: Sat, 27 Dec 2025 00:24:08 +0530 Subject: [PATCH 3/3] Replace System.out with SLF4J logging in NotificationService and aligned Reminder entity --- .../java/com/libraryman_api/entity/Reminder.java | 14 +++++++++----- .../service/NotificationService.java | 14 ++++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/libraryman_api/entity/Reminder.java b/src/main/java/com/libraryman_api/entity/Reminder.java index cfd3167..7119740 100644 --- a/src/main/java/com/libraryman_api/entity/Reminder.java +++ b/src/main/java/com/libraryman_api/entity/Reminder.java @@ -24,6 +24,11 @@ public class Reminder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private Long borrowingId; + private LocalDate reminderDate; + private boolean sent; + @Enumerated(EnumType.STRING) + private ReminderType type; public Long getId() { return id; @@ -33,7 +38,7 @@ public void setId(Long id) { this.id = id; } - private Long borrowingId; + public Long getBorrowingId() { return borrowingId; @@ -43,7 +48,7 @@ public void setBorrowingId(Long borrowingId) { this.borrowingId = borrowingId; } - private LocalDate reminderDate; + public LocalDate getReminderDate() { return reminderDate; @@ -53,7 +58,7 @@ public void setReminderDate(LocalDate reminderDate) { this.reminderDate = reminderDate; } - private boolean sent; + public boolean isSent() { return sent; @@ -63,8 +68,7 @@ public void setSent(boolean sent) { this.sent = sent; } - @Enumerated(EnumType.STRING) - private ReminderType type; + public ReminderType getType() { return type; diff --git a/src/main/java/com/libraryman_api/service/NotificationService.java b/src/main/java/com/libraryman_api/service/NotificationService.java index 0fcb302..30d02f7 100644 --- a/src/main/java/com/libraryman_api/service/NotificationService.java +++ b/src/main/java/com/libraryman_api/service/NotificationService.java @@ -1,5 +1,7 @@ package com.libraryman_api.service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.libraryman_api.entity.Reminder; @@ -7,11 +9,15 @@ @Service public class NotificationService { + private static final Logger log = + LoggerFactory.getLogger(NotificationService.class); + public void sendReminder(Reminder reminder) { - - System.out.println( - "Reminder sent for borrowingId: " + reminder.getBorrowingId() + - " Type: " + reminder.getType() + log.info( + "Sending reminder for borrowingId: {}, Type: {}", + reminder.getBorrowingId(), + reminder.getType() ); + // TODO: Integrate with EmailService to send actual email notifications } }