Skip to content

Commit 63e869e

Browse files
Merge pull request #150 from Suzune705/suzune
feat(admin): apply cursor pagination for manager book
2 parents cf93ff2 + 618f63c commit 63e869e

File tree

5 files changed

+171
-63
lines changed

5 files changed

+171
-63
lines changed

src/java/com/library/controller/admin/book/BookManagerController.java

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,40 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
3939
session.removeAttribute("error");
4040
String addBook = (String) session.getAttribute("addBookNotice");
4141
String deleteBook = (String) session.getAttribute("deleteBookNotice");
42-
42+
String cursorParam = request.getParameter("cursor");
43+
String limitParam = request.getParameter("limit");
44+
int cursor = (cursorParam == null) ? 0 : Integer.parseInt(cursorParam);
45+
int limit = (limitParam == null) ? 10 : Integer.parseInt(limitParam);
4346
String search = request.getParameter("search");
44-
if (search == null || search.trim().isEmpty()) {
45-
try {
46-
List<Book> bookList = bookDao.getAllBook();
47+
List<Book> bookList;
48+
try {
49+
50+
// ===== SEARCH MODE =====
51+
if (search != null && !search.trim().isEmpty()) {
52+
53+
bookList = bookDao.searchBookByCursor(search.trim(), cursor, limit);
54+
55+
int nextCursor = bookList.isEmpty() ? 0 : bookList.get(bookList.size() - 1).getBookID();
4756
request.setAttribute("bookList", bookList);
48-
request.setAttribute("addBook", addBook);
49-
request.setAttribute("deleteBook", deleteBook);
57+
request.setAttribute("nextCursor", nextCursor);
58+
request.setAttribute("limit", limit);
59+
request.setAttribute("search", search);
60+
5061
request.getRequestDispatcher("/WEB-INF/views/admin/managerBook.jsp").forward(request, response);
51-
} catch (BookDataAccessException e) {
52-
logger.error("Error loading books", e);
62+
return;
5363
}
54-
} else {
55-
List<Book> bookList = bookDao.searchBook(search);
56-
64+
// ===== NORMAL MODE (Không search) =====
65+
bookList = bookDao.getBooksByCursor(cursor, limit);
66+
// Cursor mới (ID của dòng cuối cùng)
67+
int nextCursor = bookList.isEmpty() ? 0 : bookList.get(bookList.size() - 1).getBookID();
5768
request.setAttribute("bookList", bookList);
69+
request.setAttribute("nextCursor", nextCursor);
70+
request.setAttribute("limit", limit);
71+
72+
request.getRequestDispatcher("/WEB-INF/views/admin/managerBook.jsp").forward(request, response);
73+
} catch (BookDataAccessException e) {
74+
logger.error("Error loading books", e);
75+
request.setAttribute("errorMessage", "Failed to load books.");
5876
request.getRequestDispatcher("/WEB-INF/views/admin/managerBook.jsp").forward(request, response);
5977
}
6078
}

src/java/com/library/controller/admin/book/EditBookController.java

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/java/com/library/dao/BookDao.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@ public interface BookDao {
4747
Map<String, Integer> countingBorrowedBookByCategory();
4848

4949
int getBookID(String title);
50+
51+
List<Book> getBooksByCursor(int cursor, int limit);
52+
List<Book> searchBookByCursor(String query, int cursor, int limit);
53+
5054
}

src/java/com/library/dao/BookDaoImpl.java

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,9 @@ public int getBookID(String coverImage) {
341341
String sql = "select book_id from books\n"
342342
+ "where cover_image = ? ";
343343
try (
344-
Connection conn = DBConnection.getInstance().getConnection();
345-
PreparedStatement ps = conn.prepareStatement(sql)){
346-
ps.setString(1, coverImage);
347-
ResultSet rs = ps.executeQuery() ;
344+
Connection conn = DBConnection.getInstance().getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
345+
ps.setString(1, coverImage);
346+
ResultSet rs = ps.executeQuery();
348347
while (rs.next()) {
349348
return rs.getInt("book_id");
350349
}
@@ -354,4 +353,105 @@ public int getBookID(String coverImage) {
354353
return -1;
355354
}
356355

356+
@Override
357+
public List<Book> getBooksByCursor(int cursor, int limit) {
358+
List<Book> list = new ArrayList<>();
359+
360+
String sql = "SELECT b.book_id, b.title, b.slug, b.author, b.quantity, "
361+
+ "c.category_id AS category_ID, c.name AS category_name, b.cover_image "
362+
+ "FROM books b "
363+
+ "LEFT JOIN categories c ON b.category_id = c.category_id "
364+
+ "WHERE b.book_id > ? "
365+
+ "ORDER BY b.book_id "
366+
+ "OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY";
367+
368+
logger.debug("Executing Cursor Pagination SQL: {}", sql);
369+
370+
try (Connection conn = DBConnection.getInstance().getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
371+
372+
ps.setInt(1, cursor); // ID cuối của trang trước (0 = trang đầu)
373+
ps.setInt(2, limit); // số bản ghi muốn lấy
374+
375+
try (ResultSet rs = ps.executeQuery()) {
376+
while (rs.next()) {
377+
Book b = new Book();
378+
b.setBookID(rs.getInt("book_id"));
379+
b.setTitle(rs.getString("title"));
380+
b.setSlug(rs.getString("slug"));
381+
b.setAuthor(rs.getString("author"));
382+
b.setQuantity(rs.getInt("quantity"));
383+
b.setCoverImage(rs.getString("cover_image"));
384+
385+
Category category = new Category();
386+
category.setCategoryID(rs.getInt("category_ID"));
387+
category.setType(BookType.convert(rs.getString("category_name")));
388+
b.setCategory(category);
389+
390+
list.add(b);
391+
}
392+
}
393+
394+
logger.info("Cursor loaded {} books (cursor={}, limit={})",
395+
list.size(), cursor, limit);
396+
397+
} catch (SQLException e) {
398+
logger.error("Error retrieving books using cursor pagination", e);
399+
}
400+
401+
return list;
402+
}
403+
404+
@Override
405+
public List<Book> searchBookByCursor(String query, int cursor, int limit) {
406+
List<Book> list = new ArrayList<>();
407+
408+
String sql = "SELECT books.*, categories.* "
409+
+ "FROM books "
410+
+ "JOIN categories ON books.category_id = categories.category_id "
411+
+ "WHERE (title_unaccented LIKE ? OR title LIKE ?) "
412+
+ "AND books.book_id > ? "
413+
+ "ORDER BY books.book_id "
414+
+ "OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY";
415+
416+
logger.info("Searching books with cursor. Query={}, Cursor={}, Limit={}", query, cursor, limit);
417+
418+
try (Connection conn = DBConnection.getInstance().getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
419+
420+
String key = "%" + query + "%";
421+
422+
ps.setString(1, key);
423+
ps.setString(2, key);
424+
ps.setInt(3, cursor); // KEYSET pagination
425+
ps.setInt(4, limit);
426+
427+
try (ResultSet rs = ps.executeQuery()) {
428+
while (rs.next()) {
429+
430+
Book b = new Book();
431+
b.setBookID(rs.getInt("book_id"));
432+
b.setSlug(rs.getString("slug"));
433+
b.setAuthor(rs.getString("author"));
434+
b.setTitle(rs.getString("title"));
435+
b.setQuantity(rs.getInt("quantity"));
436+
b.setDescription(rs.getString("description"));
437+
b.setCoverImage(rs.getString("cover_image"));
438+
439+
Category category = new Category();
440+
category.setCategoryID(rs.getInt("category_id"));
441+
category.setType(BookType.convert(rs.getString("name")));
442+
b.setCategory(category);
443+
444+
list.add(b);
445+
}
446+
}
447+
448+
logger.info("Loaded {} search results (cursor mode)", list.size());
449+
450+
} catch (SQLException s) {
451+
logger.error("Error executing search with cursor: {}", s.getMessage(), s);
452+
}
453+
454+
return list;
455+
}
456+
357457
}

web/WEB-INF/views/admin/managerBook.jsp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,17 @@
681681
.toast-message.delete i {
682682
color: #e5e7eb;
683683
}
684+
.page-btn {
685+
padding: 12px 20px;
686+
background: #4f46e5;
687+
color: white;
688+
border-radius: 6px;
689+
border: none;
690+
font-weight: 600;
691+
}
692+
.page-btn:hover {
693+
background: #4338ca;
694+
}
684695
685696
686697
</style>
@@ -840,18 +851,31 @@
840851
</table>
841852
</div>
842853

843-
<!-- Pagination -->
854+
<!-- Cursor Pagination -->
844855
<div class="pagination">
845-
<button class="page-btn" disabled>
846-
<i class="fa-solid fa-chevron-left"></i>
847-
</button>
848-
<button class="page-btn active">1</button>
849-
<button class="page-btn">2</button>
850-
<button class="page-btn">3</button>
851-
<button class="page-btn">
852-
<i class="fa-solid fa-chevron-right"></i>
853-
</button>
856+
857+
<!-- NẾU CÓ SEARCH -->
858+
<c:if test="${not empty search}">
859+
<c:if test="${nextCursor > 0}">
860+
<a class="page-btn"
861+
href="${pageContext.request.contextPath}/admin/books?search=${search}&cursor=${nextCursor}&limit=${limit}">
862+
Load More <i class="fa-solid fa-chevron-down"></i>
863+
</a>
864+
</c:if>
865+
</c:if>
866+
867+
<!-- NẾU KHÔNG SEARCH -->
868+
<c:if test="${empty search}">
869+
<c:if test="${nextCursor > 0}">
870+
<a class="page-btn"
871+
href="${pageContext.request.contextPath}/admin/books?cursor=${nextCursor}&limit=${limit}">
872+
Load More <i class="fa-solid fa-chevron-down"></i>
873+
</a>
874+
</c:if>
875+
</c:if>
876+
854877
</div>
878+
855879
</main>
856880

857881
<!-- Add/Edit Book Modal -->

0 commit comments

Comments
 (0)