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

Choose a reason for hiding this comment

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

Implement it also

}
}
Loading