From 18fcfe1e201dad9e2ba9110704859703483f20dd Mon Sep 17 00:00:00 2001 From: Miuroro Date: Wed, 3 Jun 2026 22:44:16 +0200 Subject: [PATCH 1/3] task-1: Added normalized 3NF PostgreSQL schema for library --- task-1/schema.sql | 72 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/task-1/schema.sql b/task-1/schema.sql index a3b443b..0603b92 100644 --- a/task-1/schema.sql +++ b/task-1/schema.sql @@ -1 +1,71 @@ --- Write here your DDL statements to create the tables for the database. \ No newline at end of file +CREATE TABLE members ( + member_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + member_email TEXT NOT NULL UNIQUE, + member_name TEXT NOT NULL, + member_phone TEXT NOT NULL, + member_address TEXT NOT NULL +); + +CREATE TABLE library_cards ( + member_id BIGINT PRIMARY KEY REFERENCES members(member_id) ON DELETE CASCADE, + card_number TEXT NOT NULL UNIQUE, + card_issued_on DATE NOT NULL, + card_expires_on DATE NOT NULL, + CONSTRAINT library_cards_dates_chk CHECK (card_expires_on > card_issued_on) +); + +CREATE TABLE publishers ( + publisher_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + publisher_name TEXT NOT NULL UNIQUE +); + +CREATE TABLE books ( + book_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + book_isbn TEXT NOT NULL UNIQUE, + book_title TEXT NOT NULL, + publisher_id BIGINT NOT NULL REFERENCES publishers(publisher_id) ON DELETE RESTRICT +); + +CREATE TABLE authors ( + author_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + author_name TEXT NOT NULL UNIQUE +); + +CREATE TABLE book_authors ( + book_id BIGINT NOT NULL REFERENCES books(book_id) ON DELETE CASCADE, + author_id BIGINT NOT NULL REFERENCES authors(author_id) ON DELETE CASCADE, + PRIMARY KEY (book_id, author_id) +); + +CREATE TABLE tags ( + tag_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + tag_name TEXT NOT NULL UNIQUE +); + +CREATE TABLE book_tags ( + book_id BIGINT NOT NULL REFERENCES books(book_id) ON DELETE CASCADE, + tag_id BIGINT NOT NULL REFERENCES tags(tag_id) ON DELETE CASCADE, + PRIMARY KEY (book_id, tag_id) +); + +CREATE TABLE copies ( + copy_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + copy_barcode TEXT NOT NULL UNIQUE, + book_id BIGINT NOT NULL REFERENCES books(book_id) ON DELETE RESTRICT, + shelf_code TEXT NOT NULL +); + +CREATE TABLE borrowings ( + 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, + due_date TIMESTAMP NOT NULL, + returned_at TIMESTAMP NULL, + fine_eur NUMERIC(10, 2) NOT NULL DEFAULT 0, + CONSTRAINT borrowings_dates_chk CHECK (due_date > borrowed_at), + CONSTRAINT borrowings_returned_chk CHECK (returned_at IS NULL OR returned_at >= borrowed_at), + CONSTRAINT borrowings_fine_chk CHECK (fine_eur >= 0) +); + + \ No newline at end of file From d7a4e6ffae4bf57590fbc5323a5a45a874b0889c Mon Sep 17 00:00:00 2001 From: Miuroro Date: Wed, 3 Jun 2026 22:45:25 +0200 Subject: [PATCH 2/3] task-2: Added Java JDBC demo (insertData & fetchData) using PreparedStatement --- task-2/src/main/java/org/hyf/Main.java | 200 ++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/task-2/src/main/java/org/hyf/Main.java b/task-2/src/main/java/org/hyf/Main.java index 7be95f2..ad6b771 100644 --- a/task-2/src/main/java/org/hyf/Main.java +++ b/task-2/src/main/java/org/hyf/Main.java @@ -1,7 +1,205 @@ package org.hyf; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + public class Main { + + private static final String URL = "jdbc:postgresql://localhost:5432/library_db"; + private static final String USER = "postgres"; + private static final String PASSWORD = "postgres"; + public static void main(String[] args) { - System.out.println("You code goes here :) You can overwrite this entire method."); + try { + insertData(); + List records = fetchData(); + for (BorrowingRecord r : records) { + System.out.println(r); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static void insertData() throws SQLException { + try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { + conn.setAutoCommit(false); + try { + long tomId = insertMember(conn, "tom@example.com", "Tom", "+31645678901", "Biltstraat 88, Utrecht"); + long samId = insertMember(conn, "sam@example.com", "Sam", "+31612345678", "Oudegracht 10, Utrecht"); + + insertLibraryCard(conn, tomId, "C-1004", LocalDate.of(2023,11,20), LocalDate.of(2026,11,20)); + insertLibraryCard(conn, samId, "C-1001", LocalDate.of(2024,1,10), LocalDate.of(2027,1,10)); + + long addisonId = insertPublisher(conn, "Addison-Wesley"); + long oreillyId = insertPublisher(conn, "O'Reilly"); + + long ejId = insertBook(conn, "9780134685991", "Effective Java", addisonId); + long hfId = insertBook(conn, "9780596009205", "Head First Java", oreillyId); + + long jb = insertAuthor(conn, "Joshua Bloch"); + long ks = insertAuthor(conn, "Kathy Sierra"); + + long tagJava = insertTag(conn, "java"); + long tagBeg = insertTag(conn, "beginners"); + + insertBookAuthor(conn, ejId, jb); + insertBookAuthor(conn, hfId, ks); + + insertBookTag(conn, ejId, tagJava); + insertBookTag(conn, hfId, tagBeg); + + long c1 = insertCopy(conn, "BC-0001", ejId, "A-01"); + long c2 = insertCopy(conn, "BC-0006", hfId, "B-03"); + + insertBorrowing(conn, tomId, c1, LocalDateTime.of(2026,3,9,3,18), LocalDateTime.of(2026,3,30,3,18), LocalDateTime.of(2026,3,28,3,18), new BigDecimal("0.00")); + insertBorrowing(conn, samId, c2, LocalDateTime.of(2025,12,9,9,8), LocalDateTime.of(2025,12,30,9,8), LocalDateTime.of(2026,1,4,9,8), new BigDecimal("1.25")); + + conn.commit(); + } catch (SQLException ex) { + conn.rollback(); + throw ex; + } + } + } + + public static List fetchData() throws SQLException { + String 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"; + + try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); + PreparedStatement preparedStatement = conn.prepareStatement(sql); + ResultSet resultSet = preparedStatement.executeQuery()) { + + List list = new ArrayList<>(); + while (resultSet.next()) { + Timestamp retTs = resultSet.getTimestamp("returned_at"); + list.add(new BorrowingRecord( + resultSet.getString("member_name"), + resultSet.getString("member_email"), + resultSet.getString("book_title"), + resultSet.getString("copy_barcode"), + resultSet.getTimestamp("borrowed_at").toLocalDateTime(), + resultSet.getTimestamp("due_date").toLocalDateTime(), + retTs == null ? null : retTs.toLocalDateTime(), + resultSet.getBigDecimal("fine_eur") + )); + } + return list; + } + } + + // Helper insert methods using PreparedStatement everywhere + private static long insertMember(Connection conn, String email, String name, String phone, String address) throws SQLException { + String sql = "INSERT INTO members (member_email, member_name, member_phone, member_address) VALUES (?, ?, ?, ?) ON CONFLICT (member_email) DO UPDATE SET member_name=EXCLUDED.member_name RETURNING member_id"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, email); + preparedStatement.setString(2, name); + preparedStatement.setString(3, phone); + preparedStatement.setString(4, address); + try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); } + } } + + private static void insertLibraryCard(Connection conn, long memberId, String cardNumber, LocalDate issued, LocalDate expires) throws SQLException { + String sql = "INSERT INTO library_cards (member_id, card_number, card_issued_on, card_expires_on) VALUES (?, ?, ?, ?) ON CONFLICT (member_id) DO UPDATE SET card_number=EXCLUDED.card_number"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setLong(1, memberId); + preparedStatement.setString(2, cardNumber); + preparedStatement.setObject(3, issued); + preparedStatement.setObject(4, expires); + preparedStatement.executeUpdate(); + } + } + + private static long insertPublisher(Connection conn, String name) throws SQLException { + String sql = "INSERT INTO publishers (publisher_name) VALUES (?) ON CONFLICT (publisher_name) DO UPDATE SET publisher_name=EXCLUDED.publisher_name RETURNING publisher_id"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, name); + try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); } + } + } + + private static long insertBook(Connection conn, String isbn, String title, long publisherId) throws SQLException { + String sql = "INSERT INTO books (book_isbn, book_title, publisher_id) VALUES (?, ?, ?) ON CONFLICT (book_isbn) DO UPDATE SET book_title=EXCLUDED.book_title RETURNING book_id"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, isbn); + preparedStatement.setString(2, title); + preparedStatement.setLong(3, publisherId); + try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); } + } + } + + private static long insertAuthor(Connection conn, String name) throws SQLException { + String sql = "INSERT INTO authors (author_name) VALUES (?) ON CONFLICT (author_name) DO UPDATE SET author_name=EXCLUDED.author_name RETURNING author_id"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, name); + try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); } + } + } + + private static long insertTag(Connection conn, String name) throws SQLException { + String sql = "INSERT INTO tags (tag_name) VALUES (?) ON CONFLICT (tag_name) DO UPDATE SET tag_name=EXCLUDED.tag_name RETURNING tag_id"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, name); + try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); } + } + } + + private static void insertBookAuthor(Connection conn, long bookId, long authorId) throws SQLException { + String sql = "INSERT INTO book_authors (book_id, author_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setLong(1, bookId); + preparedStatement.setLong(2, authorId); + preparedStatement.executeUpdate(); + } + } + + private static void insertBookTag(Connection conn, long bookId, long tagId) throws SQLException { + String sql = "INSERT INTO book_tags (book_id, tag_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setLong(1, bookId); + preparedStatement.setLong(2, tagId); + preparedStatement.executeUpdate(); + } + } + + private static long insertCopy(Connection conn, String barcode, long bookId, String shelfCode) throws SQLException { + String sql = "INSERT INTO copies (copy_barcode, book_id, shelf_code) VALUES (?, ?, ?) ON CONFLICT (copy_barcode) DO UPDATE SET book_id=EXCLUDED.book_id RETURNING copy_id"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + preparedStatement.setString(1, barcode); + preparedStatement.setLong(2, bookId); + preparedStatement.setString(3, shelfCode); + try (ResultSet resultSet = preparedStatement.executeQuery()) { resultSet.next(); return resultSet.getLong(1); } + } + } + + private static void insertBorrowing(Connection conn, long memberId, long copyId, LocalDateTime borrowedAt, LocalDateTime dueDate, LocalDateTime returnedAt, BigDecimal fine) throws SQLException { + String sql = "INSERT INTO borrowings (member_id, copy_id, borrowed_at, due_date, returned_at, fine_eur) VALUES (?, ?, ?, ?, ?, ?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setLong(1, memberId); + ps.setLong(2, copyId); + ps.setTimestamp(3, Timestamp.valueOf(borrowedAt)); + ps.setTimestamp(4, Timestamp.valueOf(dueDate)); + ps.setTimestamp(5, returnedAt == null ? null : Timestamp.valueOf(returnedAt)); + ps.setBigDecimal(6, fine); + ps.executeUpdate(); + } + } + + public static record BorrowingRecord(String memberName, String memberEmail, String bookTitle, String copyBarcode, LocalDateTime borrowedAt, LocalDateTime dueDate, LocalDateTime returnedAt, BigDecimal fine) {} + } From 1e8694fc9b97986ddd31afeb0e1e8cf8e0a173a1 Mon Sep 17 00:00:00 2001 From: Miuroro Date: Wed, 3 Jun 2026 22:48:04 +0200 Subject: [PATCH 3/3] added README run instructions (Docker + Maven) --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 4b110d6..8d28cc5 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,28 @@ Write your DDL statements to create the tables for the database in the `task-1/s ### Task 2: Java CRUD Write your Java code to perform CRUD in the `task-2` folder. + +## Run demo (local) + +1. Start Postgres and apply the schema (PowerShell): + +```powershell +docker run -d --rm --name library_db -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=library_db -v "${PWD}\task-1:/schema" -p 5432:5432 postgres:16-alpine +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 +``` + +2. Compile and run the Java demo: + +```powershell +mvn -f task-2\pom.xml compile +mvn -f task-2\pom.xml exec:java +``` + +3. When finished, stop the container: + +```powershell +docker rm -f library_db +``` + +Expected output: two `BorrowingRecord[...]` lines printed to console from the demo.