Building a Library Management System in Java with SQL
Building a robust library management system requires careful database design, transaction management, and implementing complex business rules. This comprehensive guide details how we built a production-ready system using Java and MySQL that handles thousands of daily transactions, implements advanced features like reservation queues, automatic fine calculation, and real-time availability tracking.
System Overview
A production-ready library management system needs to handle several key operations:
Architecture and Design Patterns
The system follows the DAO (Data Access Object) pattern for clean separation of concerns:
Database Design
The foundation of any good library system is its database schema. Here's our optimized schema with proper indexing:
-- Users table
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Books table
CREATE TABLE books (
book_id INT PRIMARY KEY AUTO_INCREMENT,
isbn VARCHAR(13) UNIQUE NOT NULL,
title VARCHAR(200) NOT NULL,
author VARCHAR(100) NOT NULL,
publication_year INT,
available_copies INT DEFAULT 1,
total_copies INT DEFAULT 1,
INDEX idx_title (title),
INDEX idx_author (author)
);
-- Loans table
CREATE TABLE loans (
loan_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
book_id INT,
loan_date DATE NOT NULL,
due_date DATE NOT NULL,
return_date DATE,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (book_id) REFERENCES books(book_id)
);
-- Reviews table
CREATE TABLE reviews (
review_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
book_id INT,
rating INT CHECK (rating >= 1 AND rating <= 5),
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (book_id) REFERENCES books(book_id)
);
Java Implementation with Advanced Features
Let's look at the core Java classes with connection pooling and transaction management:
// Connection Pool Configuration
public class DatabaseConfig {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/library");
config.setUsername("library_user");
config.setPassword("secure_password");
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30000);
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
// Enhanced Book Entity
public class Book {
private int bookId;
private String isbn;
private String title;
private String author;
private int publicationYear;
private int availableCopies;
private int totalCopies;
private double averageRating;
// Constructor, getters/setters, equals, hashCode
}
// DAO with Transaction Management
public class LibraryDAO {
public boolean loanBook(int userId, int bookId) throws SQLException {
Connection conn = null;
PreparedStatement checkStmt = null;
PreparedStatement loanStmt = null;
PreparedStatement updateStmt = null;
try {
conn = DatabaseConfig.getConnection();
conn.setAutoCommit(false); // Start transaction
// Check if book is available
String checkQuery = "SELECT available_copies FROM books WHERE book_id = ? FOR UPDATE";
checkStmt = conn.prepareStatement(checkQuery);
checkStmt.setInt(1, bookId);
ResultSet rs = checkStmt.executeQuery();
if (rs.next() && rs.getInt("available_copies") > 0) {
// Create loan record
String loanQuery = "INSERT INTO loans (user_id, book_id, loan_date, due_date) " +
"VALUES (?, ?, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 14 DAY))";
loanStmt = conn.prepareStatement(loanQuery);
loanStmt.setInt(1, userId);
loanStmt.setInt(2, bookId);
loanStmt.executeUpdate();
// Update available copies
String updateQuery = "UPDATE books SET available_copies = available_copies - 1 " +
"WHERE book_id = ?";
updateStmt = conn.prepareStatement(updateQuery);
updateStmt.setInt(1, bookId);
updateStmt.executeUpdate();
conn.commit();
return true;
}
conn.rollback();
return false;
} catch (SQLException e) {
if (conn != null) conn.rollback();
throw e;
} finally {
// Close resources in reverse order
if (updateStmt != null) updateStmt.close();
if (loanStmt != null) loanStmt.close();
if (checkStmt != null) checkStmt.close();
if (conn != null) conn.close();
}
}
// Advanced search with pagination
public List<Book> searchBooks(String searchTerm, int offset, int limit) throws SQLException {
String query = "SELECT b.*, AVG(r.rating) as avg_rating " +
"FROM books b " +
"LEFT JOIN reviews r ON b.book_id = r.book_id " +
"WHERE b.title LIKE ? OR b.author LIKE ? " +
"GROUP BY b.book_id " +
"ORDER BY avg_rating DESC NULLS LAST " +
"LIMIT ? OFFSET ?";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement stmt = conn.prepareStatement(query)) {
String searchPattern = "%" + searchTerm + "%";
stmt.setString(1, searchPattern);
stmt.setString(2, searchPattern);
stmt.setInt(3, limit);
stmt.setInt(4, offset);
ResultSet rs = stmt.executeQuery();
List<Book> books = new ArrayList<>();
while (rs.next()) {
Book book = new Book();
book.setBookId(rs.getInt("book_id"));
book.setIsbn(rs.getString("isbn"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
book.setAverageRating(rs.getDouble("avg_rating"));
books.add(book);
}
return books;
}
}
// Review system with validation
public void addReview(int userId, int bookId, int rating, String reviewText) throws SQLException {
// Verify user has borrowed the book
String verifyQuery = "SELECT COUNT(*) FROM loans WHERE user_id = ? AND book_id = ? AND return_date IS NOT NULL";
try (Connection conn = DatabaseConfig.getConnection()) {
PreparedStatement verifyStmt = conn.prepareStatement(verifyQuery);
verifyStmt.setInt(1, userId);
verifyStmt.setInt(2, bookId);
ResultSet rs = verifyStmt.executeQuery();
if (rs.next() && rs.getInt(1) > 0) {
String insertQuery = "INSERT INTO reviews (user_id, book_id, rating, review_text) VALUES (?, ?, ?, ?)";
PreparedStatement insertStmt = conn.prepareStatement(insertQuery);
insertStmt.setInt(1, userId);
insertStmt.setInt(2, bookId);
insertStmt.setInt(3, rating);
insertStmt.setString(4, reviewText);
insertStmt.executeUpdate();
} else {
throw new SQLException("User must borrow and return the book before reviewing");
}
}
}
}
Advanced Features and Best Practices
1. Overdue Book Tracking with Scheduled Jobs
@Component
public class OverdueBookScheduler {
@Scheduled(cron = "0 0 9 * * ?") // Run daily at 9 AM
public void checkOverdueBooks() {
String query = "SELECT l.*, u.email, b.title " +
"FROM loans l " +
"JOIN users u ON l.user_id = u.user_id " +
"JOIN books b ON l.book_id = b.book_id " +
"WHERE l.return_date IS NULL AND l.due_date < CURDATE()";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement stmt = conn.prepareStatement(query)) {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
sendOverdueNotification(
rs.getString("email"),
rs.getString("title"),
rs.getDate("due_date")
);
}
} catch (SQLException e) {
logger.error("Failed to check overdue books", e);
}
}
}
2. Performance Optimization
3. Security Considerations
// Input validation example
public class ValidationUtils {
public static boolean isValidISBN(String isbn) {
return isbn != null && isbn.matches("^(97[89])?\d{9}(\d|X)$");
}
public static String sanitizeInput(String input) {
return input.replaceAll("[<>"']", "");
}
}
// Authentication example
public class AuthService {
public User authenticate(String username, String password) throws SQLException {
String query = "SELECT * FROM users WHERE username = ?";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String hashedPassword = rs.getString("password_hash");
if (BCrypt.checkpw(password, hashedPassword)) {
return mapResultSetToUser(rs);
}
}
}
return null;
}
}
4. RESTful API Design
@RestController
@RequestMapping("/api/books")
public class BookController {
@Autowired
private LibraryService libraryService;
@GetMapping("/search")
public ResponseEntity<List<Book>> searchBooks(
@RequestParam String query,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
List<Book> books = libraryService.searchBooks(query, page * size, size);
return ResponseEntity.ok(books);
}
@PostMapping("/{bookId}/loan")
public ResponseEntity<LoanResponse> loanBook(
@PathVariable int bookId,
@AuthenticationPrincipal User user) {
try {
boolean success = libraryService.loanBook(user.getId(), bookId);
if (success) {
return ResponseEntity.ok(new LoanResponse("Book loaned successfully"));
} else {
return ResponseEntity.badRequest().body(new LoanResponse("Book not available"));
}
} catch (SQLException e) {
return ResponseEntity.internalServerError().body(new LoanResponse("Database error"));
}
}
}
Deployment Considerations
Conclusion
This library management system demonstrates enterprise-level Java development with:
Technical Achievements:
Advanced Features Implemented:
Lessons Learned:
The complete source code demonstrates professional Java development practices and can serve as a foundation for commercial library systems.
About the Author
The Nordic Oculus team brings together skills in modern software development, cloud architecture, and emerging technologies.
Learn more about our team