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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,33 @@ If you have any questions or would like to connect, please don't hesitate to rea
</picture>
</p>

---

## 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.
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Remove lombok

<version>1.18.30</version>
<scope>provided</scope>
</dependency>

</dependencies>

Expand Down
86 changes: 86 additions & 0 deletions src/main/java/com/libraryman_api/entity/Reminder.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.</p>
*
* <p>Each reminder is triggered on a specific date and marked as sent
* after notification delivery.</p>
*/

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






}
11 changes: 11 additions & 0 deletions src/main/java/com/libraryman_api/entity/ReminderType.java
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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<Reminder, Long> {
List<Reminder> findByReminderDateAndSentFalse(LocalDate date);
}
65 changes: 65 additions & 0 deletions src/main/java/com/libraryman_api/scheduler/ReminderScheduler.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>This scheduler runs daily and processes only reminders that are due
* on the current date, avoiding unnecessary full-table scans.</p>
*/
@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.
*
* <p>Runs daily at 9 AM. A reminder is marked as sent only after
* successful notification delivery.</p>
*/
@Scheduled(cron = "0 0 9 * * ?")
@Transactional
public void sendDueReminders() {

LocalDate today = LocalDate.now();
List<Reminder> 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);
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/libraryman_api/service/NotificationService.java
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement it also

}
Comment on lines +15 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace System.out.println with proper logging and implement actual notification.

This method currently only prints to stdout, which is not suitable for production. Use SLF4J logger for consistency with the rest of the codebase (e.g., ReminderScheduler). Also, consider integrating with the existing EmailService to send actual email notifications, following the established pattern in this codebase.

🔎 Proposed logging fix and integration approach
 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) {
-       
-        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
     }
 }
🤖 Prompt for AI Agents
In src/main/java/com/libraryman_api/service/NotificationService.java around
lines 10 to 16, replace the System.out.println call with SLF4J logging and
integrate the existing EmailService to send real notifications: inject or obtain
EmailService in this class, create an email payload from Reminder
(subject/body/recipient based on borrowingId/type), call
emailService.sendEmail(...) and log at info on success and error on exceptions;
initialize a private static final Logger via
LoggerFactory.getLogger(NotificationService.class) and wrap the email send in
try/catch to log failures with the exception.

}
Loading