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
}
}