Skip to content

Monerh A#4

Open
Miuroro wants to merge 3 commits into
HackYourAssignment:mainfrom
Miuroro:main
Open

Monerh A#4
Miuroro wants to merge 3 commits into
HackYourAssignment:mainfrom
Miuroro:main

Conversation

@Miuroro

@Miuroro Miuroro commented Jun 3, 2026

Copy link
Copy Markdown

This PR adds the library schema, a Java JDBC demo, and run instructions.

Schema

  • Normalized to 3NF covering members, library cards, publishers, books, authors, book↔author and book↔tag relations, copies and borrowings.
  • Includes primary keys, foreign keys, UNIQUE constraints and CHECK constraints to enforce data integrity.

Java demo

  • Console program with insertData() (inserts sample rows — ≥2 where applicable) and fetchData() (retrieves borrowings using JOINs).
  • Uses PreparedStatement for all DB access and explicit transaction handling (commit / rollback).
  • Prints borrowing records to the console.

Run instructions

  • Minimal, copyable Docker + Maven commands included so reviewers can apply the schema and run the demo locally.

@cometbroom cometbroom left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The schema is normalized cleanly, all required relationship types are present, and the assignment satisfies the JDBC requirements with prepared statements and transaction handling.
Did add a few comments to look into. Optional to implement them on this PR ofcourse.

Comment on lines +81 to +82
"ORDER BY br.borrowed_at";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You can use a text block for the SQL statements. Also recommend setting your statement to a final constant:

""";

private static  final String BORROW_DATA_SQL = """
    SELECT m.member_name, m.member_email, b.book_title, c.copy_barcode, br.borrowed_at, br.due_date, br.returned_at, br.fine_eur
    FROM borrowings br
    JOIN members m ON br.member_id = m.member_id
    JOIN copies c ON br.copy_id = c.copy_id
    JOIN books b ON c.book_id = b.book_id
    ORDER BY br.borrowed_at
    """;

Comment thread task-1/schema.sql
borrowing_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
member_id BIGINT NOT NULL REFERENCES members(member_id) ON DELETE RESTRICT,
copy_id BIGINT NOT NULL REFERENCES copies(copy_id) ON DELETE RESTRICT,
borrowed_at TIMESTAMP NOT NULL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TIMESTAMP stores the time given to it without accounting for timezone which can cause mismatch in timing if you have servers running in different timezones or you have libraries in different timezones.
The type TIMESTAMPTZ converts the value to UTC and standardizes how time is stored on your database. Then any library that views this can decide to show UTC, local time or both times.

Comment on lines +113 to +114
try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

While this is not too many lines of code. It is still repeated a few times. Extracting it out can make refactoring the code later easier and gives you an opportunity to document the code with code by naming the method.

Please be aware though, not every piece of code should be extracted as it can reduce readability. Only extract when you see a clear, reusable concept (like "execute an insert that returns an ID"). If extracting would split business logic across multiple methods and make the flow harder to follow, it’s better to keep it together.

In this case, the extraction is justified because:

  • The pattern appears multiple times
  • The concept ("run this SQL and give me back the generated ID") is cohesive

List<BorrowingRecord> list = new ArrayList<>();
while (resultSet.next()) {
Timestamp retTs = resultSet.getTimestamp("returned_at");
list.add(new BorrowingRecord(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Transforming a ResultSet into your domain type is something that will most likely be reused, is a concept and is more a concern of your record BorrowingRecord.
You can add a static from method in your record that parses from a ResultSet instead.

import org.hyf.Main;

import java.sql.ResultSet;

record BorrowingRecord(String memberName, String memberEmail, String bookTitle, String copyBarcode,
                       LocalDateTime borrowedAt, LocalDateTime dueDate, LocalDateTime returnedAt, BigDecimal fine) {

  public static BorrowingRecord from(ResultSet fetchedData) throws SQLException {
    return new BorrowingRecord(
        fetchedData.getString("member_name"),
        fetchedData.getString("member_email"),
        fetchedData.getString("book_title"),
        fetchedData.getString("copy_barcode"),
        fetchedData.getObject("borrowed_at", LocalDateTime.class),
        fetchedData.getObject("due_date", LocalDateTime.class),
        fetchedData.getObject("returned_at", LocalDateTime.class),
        fetchedData.getObject("fine_eur", BigDecimal.class)
    );
  }
}

Then you could use it like so:

list.add(BorrowingRecord.from(resultSet));

While it's possible to move the while block also in the record's static method, that could harm readability and reusability. Because then the record is worrying about arranging the data and is less reusable for different fetch scenarios which need different arrangment including having just a single record.

Comment thread README.md
Comment on lines +18 to +19
while (-not (docker exec library_db pg_isready -U postgres -d postgres 2>$null)) { Start-Sleep -Milliseconds 100 }
docker exec library_db psql -U postgres -d library_db -v ON_ERROR_STOP=1 -f /schema/schema.sql

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The powershell script doesn't work the first time it's run for me.
On the second run it does. Which is because of a truthy value coming from docker exec library_db pg_isready -U postgres -d postgres 2>$null
2>$null only silences the stderr channel but pg_isready prints status text to the stdout (1) channel. Even if the container hasn't started yet (Eg: rejecting connections). So this evaluates to true.

You can use the below check instead to determine if your database is ready. $LASTEXITCODE retrieves the last process exist number from powershell, docker exec command returns non-zero until the db is accepting connections.

do {
    Start-Sleep -Milliseconds 250
    docker exec library_db pg_isready -h localhost -U postgres -d library_db 2> $null
    $ready = $LASTEXITCODE -eq 0
} until ($ready)


private static final String URL = "jdbc:postgresql://localhost:5432/library_db";
private static final String USER = "postgres";
private static final String PASSWORD = "postgres";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can understand hardcoded local credentials in an assignment. Just a hint, the next professional step is to read connection details from environment variables so secrets do not live in source code:

private static final String URL = System.getenv().getOrDefault(
        "DB_URL",
        "jdbc:postgresql://localhost:5432/library_db"
);
private static final String USER = System.getenv().getOrDefault("DB_USER", "postgres");
private static final String PASSWORD = System.getenv("DB_PASSWORD");

resultSet.getString("copy_barcode"),
resultSet.getTimestamp("borrowed_at").toLocalDateTime(),
resultSet.getTimestamp("due_date").toLocalDateTime(),
retTs == null ? null : retTs.toLocalDateTime(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You can use the getObject() method with LocalDateTime.class type (See my other comment on fetchData). Then no null-check is needed.

Comment thread task-1/schema.sql
);

CREATE TABLE library_cards (
member_id BIGINT PRIMARY KEY REFERENCES members(member_id) ON DELETE CASCADE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice structural pattern to prevent a second card for the same member. Also proper use of ON DELETE CASCADE. Well done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants