keepcalculator

06 Numbers and Powers

Java Library Management System: DAO and Service Layers

Card No. 06·57000 · Updated · Standard formula, shown below

Structure a Java library management system by keeping database access in DAO classes and application rules in service classes, with controllers or a user interface calling the services. This separation makes rules such as borrowing limits and return handling easier to test without tying them to SQL or a particular interface.

What the Layers Do

A DAO, or data access object, provides a focused way to read and write a particular kind of data. A service coordinates operations and applies the system’s rules. The distinction is important: a DAO can find a book, but the service decides whether a requested loan is allowed.

  • Presentation layer: Accepts requests from a command-line interface, web controller, or other client and formats responses.
  • Service layer: Implements use cases, validates business conditions, coordinates DAOs, and defines transaction boundaries.
  • DAO or repository layer: Executes persistence operations and translates database results into Java objects.
  • Database: Stores durable records and enforces appropriate constraints, such as unique identifiers.

These are logical responsibilities, not necessarily separate servers or projects. A small application can keep them in one Java application while still keeping their code distinct.

Choose the Domain Model First

Model the concepts the library actually manages. A useful starting point distinguishes a title from the physical copies that can be borrowed. For example, Book can represent bibliographic details, while BookCopy represents an individual copy with its own barcode and availability. If a system has no copy-level tracking, it may instead track a count, but it should not mix the two approaches accidentally.

Common entities include Book, BookCopy, Member, and Loan. A loan usually records which copy was issued, which member borrowed it, when it was issued, its due date, and—once returned—the return date. Prefer explicit types such as LocalDate for calendar dates and Instant for timestamps when their semantics fit the application. Avoid storing a changing value such as “available” in several places if it can be derived reliably from active loans; duplicated state can drift out of sync.

Define identifiers and relationships deliberately. A loan should reference a member and a copy by stable IDs or mapped relationships. Database foreign keys help prevent orphaned records, while uniqueness constraints can protect values such as a copy barcode.

Organize Packages and Interfaces

Keep packages understandable and group related code. One possible layout is:

com.example.library
  domain/       Book, BookCopy, Member, Loan
  dao/          BookDao, MemberDao, LoanDao
  service/      LoanService, CatalogService
  web/          controllers and request/response models
  config/       database and dependency wiring

For a small project, this is sufficient; a larger application may organize by feature, such as loan and catalog, with each feature containing its own service and persistence code. Avoid creating packages or interfaces solely to follow a naming fashion. The goal is to make dependencies and responsibilities clear.

A DAO interface might expose operations such as findById, findByBarcode, save, and findActiveLoansForMember. Its implementation can use JDBC, a persistence framework, or another storage mechanism. Keep SQL and persistence-specific details in that implementation rather than leaking them into controllers or business services.

Implement Services Around Use Cases

Design service methods around actions a user can request, not just around individual database calls. A LoanService might offer borrowCopy(memberId, copyId) and returnCopy(copyId). The borrowing method can load the member and copy, check that both exist, verify that the copy is not already on loan, check any applicable member restrictions, and then create the loan.

For example, the core flow could look like this, with transaction and exception details adapted to the persistence technology:

public Loan borrowCopy(long memberId, long copyId) {
    Member member = memberDao.findById(memberId)
        .orElseThrow(MemberNotFoundException::new);
    BookCopy copy = copyDao.findById(copyId)
        .orElseThrow(CopyNotFoundException::new);

    if (loanDao.hasActiveLoan(copyId)) {
        throw new CopyUnavailableException();
    }
    if (!member.canBorrow()) {
        throw new BorrowingNotAllowedException();
    }

    Loan loan = Loan.start(member.id(), copy.id(), LocalDate.now());
    return loanDao.save(loan);
}

The example illustrates separation, not a complete concurrency solution. If two requests attempt to borrow the same copy simultaneously, both may pass an availability check before either saves. The service should run the operation transactionally, and the persistence design should prevent conflicting active loans—for example, through suitable locking or a database constraint where the schema supports it. A check in Java alone does not guarantee exclusivity under concurrent requests.

Transactions, Errors, and Validation

A use case that changes multiple records should usually succeed or fail as a unit. If borrowing inserts a loan and updates copy status, both changes belong in one transaction if both are stored. Otherwise, a failure between writes can leave inconsistent data. Some designs derive availability from active loans and need only create or close a loan record.

Put business validation in the service or domain model, not only in the UI: other clients may call the same service. Validate request shape at the boundary as well, such as rejecting a missing identifier. Use meaningful exceptions or result types so the presentation layer can distinguish “copy unavailable” from “database temporarily unavailable.” Do not silently turn every error into an empty result.

DAO methods should communicate expected absence clearly, often with an optional result, and should not decide user-facing policy. Keep transaction management consistent with the chosen framework; avoid opening nested, unmanaged connections in individual methods when a service-level transaction is expected.

Testing and Common Design Mistakes

Test the layers at the level of the responsibility they own. Unit tests for services can use DAO fakes or mocks to cover rules such as unavailable copies and blocked members. DAO integration tests should exercise actual persistence mappings and queries against a test database when practical. Test transaction behavior and competing borrow attempts if concurrent access matters to the application.

  • Business rules in controllers: This makes behavior hard to reuse and test. Delegate decisions to services.
  • SQL in services: This couples use cases to storage. Move persistence operations into DAOs.
  • One generic DAO for everything: A generic base can reduce repetition, but domain-specific queries still belong in focused interfaces.
  • Returning database entities directly to every client: Persistence models may expose fields or relationships inappropriate for an API. Map to request and response models where useful.
  • Assuming a DAO call makes a workflow atomic: Multiple calls need an explicit transaction strategy.

A DAO and repository are often used for similar purposes, but terminology varies. A DAO conventionally emphasizes persistence operations; a repository often presents a collection-like interface around domain objects. The practical requirement is not the label: keep persistence details behind a boundary and make use-case rules explicit.

Frequently Asked Questions

Should each entity have its own DAO?
Not necessarily. Give persistence code a clear, cohesive responsibility. Separate DAOs are common for books, members, and loans, while tightly related records may be handled together when that makes queries and ownership clearer.

Where should the borrowing rules go?
In the service or domain model, because those rules define whether the use case is allowed. The DAO should retrieve and persist information, not decide library policy.

Should the DAO return entities or DTOs?
It can return domain or persistence objects suited to the application’s design. DTOs are especially useful at API boundaries; they should not be added automatically to every internal call.

Do I need a service layer if the application is small?
A small application can still benefit from a service when it has business rules or multiple entry points. For a simple read-only utility, an extra layer may add little; keep the design proportional while preserving clear boundaries.

How should a system prevent lending the same copy twice?
Use a transaction and a persistence-level concurrency safeguard appropriate to the database and schema. A preliminary availability query by itself can race when simultaneous requests arrive.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →