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/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 new file mode 100644 index 0000000..7119740 --- /dev/null +++ b/src/main/java/com/libraryman_api/entity/Reminder.java @@ -0,0 +1,86 @@ +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; + private Long borrowingId; + private LocalDate reminderDate; + private boolean sent; + @Enumerated(EnumType.STRING) + private ReminderType type; + + 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; + } + + + + + + +} \ 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..f391c2b --- /dev/null +++ b/src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java @@ -0,0 +1,65 @@ +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; + +/** + * Scheduler responsible for sending due-date and overdue book reminders. + * + *

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; + private final NotificationService notificationService; + + public ReminderScheduler(ReminderRepository reminderRepository, + NotificationService notificationService) { + this.reminderRepository = reminderRepository; + this.notificationService = notificationService; + } + + /** + * Sends reminders scheduled for the current date. + * + *

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(); + List reminders = + reminderRepository.findByReminderDateAndSentFalse(today); + + for (Reminder reminder : reminders) { + 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..30d02f7 --- /dev/null +++ b/src/main/java/com/libraryman_api/service/NotificationService.java @@ -0,0 +1,23 @@ +package com.libraryman_api.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.libraryman_api.entity.Reminder; + +@Service +public class NotificationService { + + private static final Logger log = + LoggerFactory.getLogger(NotificationService.class); + + public void sendReminder(Reminder reminder) { + log.info( + "Sending reminder for borrowingId: {}, Type: {}", + reminder.getBorrowingId(), + reminder.getType() + ); + // TODO: Integrate with EmailService to send actual email notifications + } +}