Files
Book-management-system/.trellis/spec/backend/database-guidelines.md
T
2026-04-27 21:19:23 +08:00

22 KiB

Database Guidelines

MySQL data and DAO conventions for the library-management system.


Overview

MySQL is the project data layer. DAO classes perform CRUD and query operations against MySQL. The initial scaffold schema exists at src/main/resources/db/schema.sql; future module tables should follow the same DDL style and DAO boundaries.


Core Tables

Use primary keys for every table and foreign keys for cross-entity integrity.

Implemented scaffold tables:

  • roles: administrator, librarian, reader, and future role definitions.
  • permissions: permission definitions for protected actions.
  • role_permissions: role-to-permission mapping.
  • users: login accounts for administrator, librarian, and reader roles.
  • system_logs: key operation logs, backup events, and exception traces.
  • book_categories: category names and descriptions for catalog grouping.
  • books: book information, category reference, inventory counts, and catalog status.
  • readers: reader profiles, optional login-account linkage, borrowing eligibility, contact information, and management status.

Planned module tables:

  • borrow_records: book-reader borrowing, return, renew, and overdue data.

Record new schema changes in src/main/resources/db/schema.sql and update this spec with exact table names, key columns, and DAO/service contracts.


DAO Responsibilities

  • DAOs own database CRUD and query details.
  • Use parameterized SQL or prepared-statement style access; never concatenate raw request parameters into SQL.
  • Keep transaction boundaries in the service layer for workflows that span multiple DAO calls, such as borrow/return operations that also update inventory status.
  • Return entities or small query result objects to services, not HTML or servlet response objects.
  • Keep MySQL connection details in src/main/resources/db.properties loaded by JdbcUtil. The required keys are db.driver, db.url, db.username, and db.password.

Query Guidance

  • Book search must support combined lookup by title, author, category, and ID.
  • Statistics queries should cover borrowing rankings, inventory reports, and overdue reports.
  • Borrowing records should preserve enough dates/status fields for borrow, return, renew, overdue calculation, and automatic collection status updates.
  • Permission queries should support role-based checks for administrator, librarian, and reader workflows.

Integrity Constraints

  • books.category_id should reference book_categories.
  • borrow_records.book_id should reference books.
  • borrow_records.reader_id should reference readers.
  • Administrator-role and role-permission mapping tables should use foreign keys to preserve authorization integrity.
  • users.role_code must reference roles.code.
  • role_permissions.role_code must reference roles.code.
  • role_permissions.permission_code must reference permissions.code.
  • books.status must match BookStatus enum codes: available, unavailable, and archived.
  • readers.user_id may reference users.id when a reader profile is linked to a login account.
  • readers.status must match ReaderStatus enum codes: active, suspended, and inactive.
  • readers.max_borrow_count must stay between 1 and 50.

Scenario: Book Catalog And Management Slice

1. Scope / Trigger

  • Trigger: the first concrete business module introduced catalog search and basic book management across MySQL, DAO, service, Servlet, and JSP layers.
  • Schema path: src/main/resources/db/schema.sql.
  • JSP paths: WEB-INF/jsp/books/catalog.jsp, books/manage.jsp, and books/form.jsp.

2. Signatures

  • DAO signatures: BookDao.findAllCategories(), BookDao.search(BookSearchCriteria criteria), findById(long id), findByIdentifier(String identifier), create(Book book), update(Book book), and delete(long id).
  • Entity/search signatures: Book fields are id, identifier, title, author, categoryId, categoryName, totalCopies, availableCopies, status, createdAt, and updatedAt; BookSearchCriteria fields are identifier, title, author, and nullable categoryId.
  • Service signatures: BookService.listCategories(), searchBooks(BookSearchCriteria criteria), findBook(long id), createBook(AuthenticatedUser actor, Book book), updateBook(AuthenticatedUser actor, Book book), and deleteBook(AuthenticatedUser actor, long id), all returning ServiceResult<T>.
  • Read route: GET /catalog with optional identifier, title, author, and categoryId query fields.
  • Management routes: GET /books, GET /books/new, GET /books/edit?id=..., POST /books, POST /books/update, and POST /books/delete.
  • Protected permissions: /catalog requires VIEW_CATALOG; /books* requires MANAGE_BOOKS.
  • DB signatures:
    • book_categories(id, name, description, created_at, updated_at), with unique key uk_book_categories_name(name).
    • books(id, book_identifier, title, author, category_id, total_copies, available_copies, status, created_at, updated_at), with unique key uk_books_identifier(book_identifier), indexes on title, author, category, and status, foreign key fk_books_category, and checks for non-negative copy counts and allowed status values.

3. Contracts

  • book_categories.name is unique and displayed through category selectors.
  • books.book_identifier is the unique user-facing book ID.
  • books.category_id references book_categories.id.
  • books.total_copies and books.available_copies are non-negative, and available copies cannot exceed total copies.
  • books.status stores the Java BookStatus code exactly.
  • Servlet controllers parse request fields and set JSP attributes such as criteria, categories, books, book, statuses, errors, errorMessage, and successMessage.
  • ServiceResult<T> is the service-to-controller response contract: successful, nullable data, nullable message, and field-level errors. Controllers must pass validation errors to JSPs so form/search redisplay can highlight the exact field, for example errors.categoryId.
  • JSP pages render JavaBean properties only; they must not call DAOs or embed SQL.

4. Validation & Error Matrix

  • Missing identifier, title, author, category, copy counts, or status -> return to books/form.jsp with field errors.
  • Negative total or available copies -> return a field error.
  • Available copies greater than total copies -> return Available copies cannot exceed total copies.
  • Duplicate book_identifier -> return a field error on identifier.
  • Reader or unauthenticated actor attempts write -> HTTP 403 through authorization filter or service denial.
  • DAO failure during list/search/write -> log server-side details and return Book service is temporarily unavailable. Please try again later.
  • Successful create/update/delete -> redirect to /books with a short flash message.

5. Good/Base/Bad Cases

  • Good: a librarian creates BK-1002, sees it in /books, and readers can find it from /catalog without write controls.
  • Base: catalog search with no filters lists available records ordered by title, author, and identifier.
  • Bad: a JSP opens JDBC, builds SQL from request parameters, or renders a stack trace from a failed DAO call.

6. Tests Required

  • Run BookServiceCheck or equivalent assertions for invalid inventory, duplicate identifiers, reader write denial, successful librarian CRUD, search, and DAO failure fallback.
  • Run PermissionPolicyCheck to confirm readers lack MANAGE_BOOKS and retain VIEW_CATALOG.
  • Scan JSPs for scriptlets and SQL/JDBC references.
  • When Maven/Tomcat dependencies are installed, run mvn clean package to compile Servlets and package JSP resources.

7. Wrong vs Correct

Wrong

books/form.jsp -> JDBC -> INSERT INTO books using request parameters

Correct

books/form.jsp -> BookManagementServlet -> BookService -> BookDao -> books

Scenario: Reader Information Management Slice

1. Scope / Trigger

  • Trigger: the reader profile foundation was implemented after login, permissions, catalog search, and book management.
  • Schema path: src/main/resources/db/schema.sql.
  • JSP paths: WEB-INF/jsp/readers/manage.jsp and readers/form.jsp.

2. Signatures

  • DAO signatures: ReaderDao.search(ReaderSearchCriteria criteria), findById(long id), findByIdentifier(String identifier), findByUserId(long userId), create(Reader reader), update(Reader reader), and deactivate(long id).
  • Entity/search signatures: Reader fields are id, identifier, nullable userId, nullable username, fullName, phone, email, status, maxBorrowCount, createdAt, and updatedAt; ReaderSearchCriteria fields are identifier, name, contact, and statusCode.
  • Status signature: ReaderStatus enum codes are active, suspended, and inactive.
  • Service signatures: ReaderService.searchReaders(ReaderSearchCriteria), findReader(long id), createReader(AuthenticatedUser actor, Reader reader), updateReader(AuthenticatedUser actor, Reader reader), and deactivateReader(AuthenticatedUser actor, long id), all returning ServiceResult<T>.
  • Management routes: GET /readers, GET /readers/new, GET /readers/edit?id=..., POST /readers, POST /readers/update, and POST /readers/delete.
  • Protected permission: /readers* requires MANAGE_READERS.
  • DB signature: readers(id, reader_identifier, user_id, full_name, phone, email, status, max_borrow_count, created_at, updated_at), with unique keys on reader_identifier and nullable user_id, indexes on name/contact/status, a foreign key to users(id), and checks for supported status values and borrow-limit range.

3. Contracts

  • readers.reader_identifier is the unique user-facing reader ID.
  • readers.user_id is optional; when present, only one reader profile may link to the same login account.
  • readers.status stores the Java ReaderStatus code exactly. Soft-delete behavior deactivates the profile by setting status to inactive.
  • Reader searches support partial identifier, full-name, and phone/email contact matching plus exact status filtering.
  • Servlet controllers parse request fields and set JSP attributes such as criteria, readers, reader, statuses, formValues, errors, errorMessage, and successMessage.
  • JSP pages render JavaBean properties only; they must not call DAOs or embed SQL.
  • Navigation should expose reader management to administrator/librarian users, matching the current MANAGE_READERS policy.

4. Validation & Error Matrix

  • Missing identifier or full name -> return to readers/form.jsp with field errors.
  • Missing both phone and email -> return a field error on phone.
  • Invalid phone or email format -> return field errors on phone or email.
  • Unsupported status -> return a field error on status.
  • Max borrow count outside 1 to 50 -> return a field error on maxBorrowCount.
  • Non-positive linked user_id -> return a field error on userId.
  • Duplicate reader_identifier -> return a field error on identifier.
  • Duplicate linked user_id -> return a field error on userId.
  • Reader or unauthenticated actor attempts write -> HTTP 403 through authorization filter or service denial.
  • DAO failure during list/search/write -> log server-side details and return Reader service is temporarily unavailable. Please try again later.
  • Successful create/update/deactivate -> redirect to /readers with a short flash message.

5. Good/Base/Bad Cases

  • Good: a librarian creates RD-1003, searches by email, edits the borrow limit, and can later deactivate the profile without deleting future borrowing history.
  • Base: /readers with no filters lists reader records ordered by full name and reader identifier.
  • Bad: normal readers access /readers, a JSP opens JDBC, or deleting a reader profile removes rows that future borrow records need to reference.

6. Tests Required

  • Run ReaderServiceCheck or equivalent assertions for invalid contact, invalid borrow limit, duplicate identifiers, duplicate linked users, reader write denial, successful librarian CRUD-like operations, search, deactivate, and DAO failure fallback.
  • Run PermissionPolicyCheck to confirm librarians have MANAGE_READERS and readers do not.
  • Scan reader JSPs for scriptlets and SQL/JDBC references.
  • When Maven/Tomcat dependencies are installed, run mvn clean package to compile Servlets and package JSP resources.

7. Wrong vs Correct

Wrong

readers/form.jsp -> JDBC -> DELETE FROM readers using request parameters

Correct

readers/form.jsp -> ReaderManagementServlet -> ReaderService -> ReaderDao -> readers.status = inactive

Scenario: Borrowing Circulation Management Slice

1. Scope / Trigger

  • Trigger: borrowing circulation now spans borrow_records, book inventory, reader eligibility, Servlet routes, service transactions, DAO locks, and JSP management/history screens.
  • Schema path: src/main/resources/db/schema.sql.
  • JSP paths: WEB-INF/jsp/borrowing/manage.jsp, borrowing/form.jsp, and reader/loans.jsp.

2. Signatures

  • Entity signatures: BorrowRecord fields are id, readerId, readerIdentifier, readerName, bookId, bookIdentifier, bookTitle, borrowedAt, dueAt, nullable returnedAt, renewalCount, status, createdAt, and updatedAt.
  • Status signature: BorrowRecordStatus enum codes are active and returned; overdue is derived from active, non-returned rows where due_at < CURRENT_TIMESTAMP.
  • Search signature: BorrowRecordSearchCriteria(readerIdentifier, bookIdentifier, statusCode) where statusCode may be empty, active, returned, or the derived filter overdue.
  • DAO signatures: BorrowRecordDao.search(criteria), findByReaderId(readerId), findReaderByUserId(userId), findReaderByIdentifierForUpdate(connection, identifier), findBookByIdentifierForUpdate(connection, identifier), findByIdForUpdate(connection, id), countActiveByReaderId(connection, readerId), create(connection, record), decrementAvailableCopies(...), incrementAvailableCopies(...), markReturned(...), and renew(...).
  • Service signatures: BorrowingService.searchRecords(actor, criteria), borrowBook(actor, readerIdentifier, bookIdentifier), returnBook(actor, recordId), renewLoan(actor, recordId), and listCurrentReaderHistory(actor), all returning ServiceResult<T>.
  • Management routes: GET /borrowing, GET /borrowing/new, POST /borrowing/create, POST /borrowing/return, and POST /borrowing/renew.
  • Reader route: GET /reader/loans.
  • Protected permissions: /borrowing* requires MANAGE_BORROWING; /reader/loans requires BORROW_BOOKS and the reader role because it displays only the signed-in reader's own history.
  • DB signature: borrow_records(id, reader_id, book_id, borrowed_at, due_at, returned_at, renewal_count, status, created_at, updated_at), with foreign keys to readers(id) and books(id), indexes on reader, book, status, and due date, and checks for non-negative renewal count and supported statuses.

3. Contracts

  • Borrow operations require an active reader and a borrowable book with BookStatus.AVAILABLE and available_copies > 0.
  • Reader active-loan count is rows with status = active and returned_at IS NULL; overdue rows still count toward max_borrow_count.
  • Borrowing creates one borrow_records row and decrements books.available_copies in the same transaction.
  • Returning an active loan sets status = returned, stores returned_at, and increments books.available_copies with a cap at total_copies in the same transaction.
  • Renewing an active loan extends due_at, increments renewal_count, and is limited to one renewal per MVP loan.
  • JdbcUtil.executeInTransaction is the local transaction helper for multi-table borrowing workflows. Services decide the workflow boundary; DAOs own SQL and row-lock statements.
  • Demo readers and books seed rows must not overwrite existing rows during schema replay, because resetting reader eligibility or available_copies can corrupt live borrowing state.
  • Servlet controllers set JSP attributes such as criteria, statuses, overdueStatus, maxRenewals, borrowRecords, formValues, errors, errorMessage, and successMessage.
  • JSP pages render JavaBean properties only; they must not call DAOs or embed SQL.

4. Validation & Error Matrix

  • Missing reader ID or book ID -> return to borrowing/form.jsp with field errors.
  • Unknown reader -> field error on readerIdentifier.
  • Inactive or suspended reader -> field error on readerIdentifier.
  • Reader at or above max_borrow_count active loans -> field error on readerIdentifier.
  • Unknown book -> field error on bookIdentifier.
  • Unavailable, archived, or zero-copy book -> field error on bookIdentifier.
  • Missing or non-positive record ID for return/renew -> flash error.
  • Returning or renewing a returned loan -> validation failure on status.
  • Renewing after the renewal limit -> validation failure on renewalCount.
  • DAO/transaction failure -> log server-side details and return Borrowing service is temporarily unavailable. Please try again later.

5. Good/Base/Bad Cases

  • Good: a librarian creates a loan for active reader RD-1000 and available book BK-1000; the loan appears in /borrowing and book availability is decremented.
  • Base: /borrowing?status=overdue lists active, non-returned loans past due without requiring a scheduler or stored overdue status.
  • Bad: a Servlet updates books.available_copies outside the borrowing transaction or a JSP issues SQL to filter loan records.

6. Tests Required

  • Run BorrowingServiceCheck assertions for permission denial, inactive readers, unavailable/no-copy books, max active loan count, successful borrow, return inventory restoration, one-renewal limit, overdue search, reader loan history, and DAO failure fallback.
  • Run PermissionPolicyCheck to confirm readers have BORROW_BOOKS, readers lack MANAGE_BORROWING, and librarians lack reader self-borrow permission. Service or filter checks should also confirm staff use /borrowing, not the reader-only /reader/loans history route.
  • Scan JSPs for scriptlets and SQL/JDBC references.
  • When Maven/Tomcat dependencies are installed, run mvn clean package to compile Servlets and package JSP resources.

7. Wrong vs Correct

Wrong

borrowing/manage.jsp -> JDBC -> UPDATE books SET available_copies = ...

Correct

borrowing/form.jsp -> BorrowingManagementServlet -> BorrowingService -> BorrowRecordDao -> borrow_records + books in one transaction

Scenario: Login And Permission Scaffold Schema

1. Scope / Trigger

  • Trigger: the initial Java Web scaffold introduced a concrete MySQL schema and login contract.
  • Schema path: src/main/resources/db/schema.sql.
  • Example configuration path: src/main/resources/db.properties.example.

2. Signatures

  • DAO signature: UserDao.findActiveByUsername(String username).
  • Service signature: AuthService.authenticate(String username, String password).
  • Permission signature: AuthService.hasPermission(AuthenticatedUser user, Permission permission).
  • Servlet route: POST /login with username, password, and optional same-application redirect.
  • Protected routes: /dashboard, /admin/home, /librarian/home, and /reader/home.
  • Session keys: authenticatedUser, userRole, and userPermissions.
  • DB config keys: db.driver, db.url, db.username, and db.password.
  • Login tables: roles, permissions, role_permissions, users, and system_logs.

3. Contracts

  • users.username: unique login identifier submitted by LoginServlet.
  • users.password_hash: PBKDF2 hash in pbkdf2_sha256$iterations$saltBase64$hashBase64 format.
  • users.role_code: foreign key to roles.code; supported scaffold values are administrator, librarian, and reader.
  • users.active: only rows with active = 1 can authenticate.
  • roles.code, permissions.code, and role_permissions must match the Java Role and Permission enum codes exactly.
  • db.properties must be local configuration. Commit db.properties.example, but do not commit real credentials.
  • Session state stores an AuthenticatedUser snapshot, role code, and permission-code set. It must not store raw passwords or DAO result objects with password hashes.
  • Login redirects must stay inside the current application context. Reject values that do not start with a single / or that contain CR/LF characters.

4. Validation & Error Matrix

  • Missing username or password -> request returns to login JSP with Username and password are required.
  • Unknown user, inactive user, or hash mismatch -> request returns to login JSP with Invalid username or password.
  • Unsafe or blank redirect -> ignore and route to /dashboard after success.
  • Missing db.properties, JDBC failure, or unsupported role code -> request returns a generic service-unavailable message and logs server-side details.
  • Authenticated user missing a required permission -> HTTP 403 and WEB-INF/jsp/auth/unauthorized.jsp.

5. Good/Base/Bad Cases

  • Good: admin resolves to administrator, receives all scaffold permissions, and can access /admin/home.
  • Base: reader resolves to reader, can access /reader/home, and cannot access /admin/home.
  • Bad: a JSP reads SQL or password hashes directly from the database. Keep that logic in DAO/service code.

6. Tests Required

  • Compile service/DAO/entity/util classes with javac when Maven is unavailable.
  • Run PermissionPolicyCheck or equivalent assertions for administrator, librarian, and reader permissions.
  • Run AuthServiceCheck or equivalent assertions for required-field failures, invalid credentials, success, permission checks, and DAO failure fallback.
  • When Maven/Tomcat dependencies are installed, run mvn test or mvn clean package to compile Servlet and JSP integration.

7. Wrong vs Correct

Wrong

// JSP, Servlet, or session code opens JDBC and stores password_hash.

Correct

login.jsp -> LoginServlet -> AuthService -> UserDao -> users/roles tables
session -> AuthenticatedUser snapshot + role/permission codes only