From 52fe1624661780325e14872bfb9598a91abadf67 Mon Sep 17 00:00:00 2001 From: Yusuprozimemet Date: Wed, 3 Jun 2026 19:25:45 +0200 Subject: [PATCH] Add 3NF library schema (Task 1) and JDBC CRUD demo (Task 2) --- task-1/schema.sql | 173 +++++++++++++++++- task-2/src/main/java/org/hyf/Database.java | 17 ++ .../main/java/org/hyf/LibraryRepository.java | 173 ++++++++++++++++++ task-2/src/main/java/org/hyf/Main.java | 28 ++- task-2/target/classes/org/hyf/Database.class | Bin 0 -> 719 bytes .../classes/org/hyf/LibraryRepository.class | Bin 0 -> 6996 bytes task-2/target/classes/org/hyf/Main.class | Bin 0 -> 2278 bytes 7 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 task-2/src/main/java/org/hyf/Database.java create mode 100644 task-2/src/main/java/org/hyf/LibraryRepository.java create mode 100644 task-2/target/classes/org/hyf/Database.class create mode 100644 task-2/target/classes/org/hyf/LibraryRepository.class create mode 100644 task-2/target/classes/org/hyf/Main.class diff --git a/task-1/schema.sql b/task-1/schema.sql index a3b443b..cb9066d 100644 --- a/task-1/schema.sql +++ b/task-1/schema.sql @@ -1 +1,172 @@ --- Write here your DDL statements to create the tables for the database. \ No newline at end of file +-- ============================================================= +-- Library — Database Schema +-- PostgreSQL — 3NF normalised +-- Parent tables first, then children, then N:M bridges. +-- ============================================================= + + +-- ------------------------------------------------------------- +-- publishers +-- A publishing house. One publisher publishes many books (1:N). +-- ------------------------------------------------------------- +CREATE TABLE publishers ( + publisher_id SERIAL PRIMARY KEY, + publisher_name VARCHAR(100) NOT NULL UNIQUE +); + + +-- ------------------------------------------------------------- +-- books +-- A book (the abstract work, not a physical copy). +-- ------------------------------------------------------------- +CREATE TABLE books ( + book_id SERIAL PRIMARY KEY, + book_isbn VARCHAR(13) NOT NULL UNIQUE, + book_title VARCHAR(100) NOT NULL, + publisher_id INT NOT NULL, + + FOREIGN KEY (publisher_id) + REFERENCES publishers (publisher_id) + ON DELETE RESTRICT +); + + +-- ------------------------------------------------------------- +-- authors +-- A single author. Books <-> Authors is N:M (see book_authors). +-- ------------------------------------------------------------- +CREATE TABLE authors ( + author_id SERIAL PRIMARY KEY, + author_name VARCHAR(100) NOT NULL UNIQUE +); + + +-- ------------------------------------------------------------- +-- tags +-- A single tag/topic. Books <-> Tags is N:M (see book_tags). +-- ------------------------------------------------------------- +CREATE TABLE tags ( + tag_id SERIAL PRIMARY KEY, + tag_name VARCHAR(100) NOT NULL UNIQUE +); + + +-- ------------------------------------------------------------- +-- members +-- A person with a library account. +-- ------------------------------------------------------------- +CREATE TABLE members ( + member_id SERIAL PRIMARY KEY, + member_name VARCHAR(100) NOT NULL, + member_email VARCHAR(100) NOT NULL UNIQUE, + member_phone VARCHAR(20) NOT NULL, + member_address VARCHAR(200) NOT NULL +); + + +-- ------------------------------------------------------------- +-- cards +-- A library card. A member has exactly one card (1:1), +-- enforced by UNIQUE on member_id. +-- ------------------------------------------------------------- +CREATE TABLE cards ( + card_id SERIAL PRIMARY KEY, + card_number VARCHAR(20) NOT NULL UNIQUE, + card_issued_on DATE NOT NULL, + card_expires_on DATE NOT NULL, + member_id INT NOT NULL UNIQUE, + + CONSTRAINT chk_card_dates CHECK (card_expires_on > card_issued_on), + + FOREIGN KEY (member_id) + REFERENCES members (member_id) + ON DELETE CASCADE +); + + +-- ------------------------------------------------------------- +-- copies +-- A physical copy of a book. One book has many copies (1:N). +-- ------------------------------------------------------------- +CREATE TABLE copies ( + copy_id SERIAL PRIMARY KEY, + copy_barcode VARCHAR(20) NOT NULL UNIQUE, + shelf_code VARCHAR(20) NOT NULL, + book_id INT NOT NULL, + + FOREIGN KEY (book_id) + REFERENCES books (book_id) + ON DELETE RESTRICT +); + + +-- ------------------------------------------------------------- +-- loans +-- A borrowing event: one member borrows one copy. +-- Has its own data (dates + fine), so it is an entity table, +-- not a pure bridge. +-- ------------------------------------------------------------- +CREATE TABLE loans ( + loan_id SERIAL PRIMARY KEY, + member_id INT NOT NULL, + copy_id INT NOT NULL, + borrowed_at TIMESTAMP NOT NULL, + due_date TIMESTAMP NOT NULL, + returned_at TIMESTAMP, -- NULL = not returned yet + fine_eur NUMERIC(6,2) NOT NULL DEFAULT 0, + + CONSTRAINT chk_fine CHECK (fine_eur >= 0), + CONSTRAINT chk_returned CHECK (returned_at IS NULL OR returned_at >= borrowed_at), + + FOREIGN KEY (member_id) + REFERENCES members (member_id) + ON DELETE RESTRICT, + + FOREIGN KEY (copy_id) + REFERENCES copies (copy_id) + ON DELETE RESTRICT +); + + +-- ------------------------------------------------------------- +-- book_authors +-- N:M bridge between books and authors. +-- UNIQUE (book_id, author_id) prevents duplicate pairings. +-- ------------------------------------------------------------- +CREATE TABLE book_authors ( + book_author_id SERIAL PRIMARY KEY, + book_id INT NOT NULL, + author_id INT NOT NULL, + + UNIQUE (book_id, author_id), + + FOREIGN KEY (book_id) + REFERENCES books (book_id) + ON DELETE CASCADE, + + FOREIGN KEY (author_id) + REFERENCES authors (author_id) + ON DELETE CASCADE +); + + +-- ------------------------------------------------------------- +-- book_tags +-- N:M bridge between books and tags. +-- UNIQUE (book_id, tag_id) prevents duplicate pairings. +-- ------------------------------------------------------------- +CREATE TABLE book_tags ( + book_tag_id SERIAL PRIMARY KEY, + book_id INT NOT NULL, + tag_id INT NOT NULL, + + UNIQUE (book_id, tag_id), + + FOREIGN KEY (book_id) + REFERENCES books (book_id) + ON DELETE CASCADE, + + FOREIGN KEY (tag_id) + REFERENCES tags (tag_id) + ON DELETE CASCADE +); diff --git a/task-2/src/main/java/org/hyf/Database.java b/task-2/src/main/java/org/hyf/Database.java new file mode 100644 index 0000000..10ec30b --- /dev/null +++ b/task-2/src/main/java/org/hyf/Database.java @@ -0,0 +1,17 @@ +package org.hyf; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + + +public class Database { + + private static final String URL = "jdbc:postgresql://localhost:5432/library_db"; + private static final String USER = "hyfuser"; + private static final String PASSWORD = "hyfpassword"; + + public static Connection getConnection() throws SQLException { + return DriverManager.getConnection(URL, USER, PASSWORD); + } +} diff --git a/task-2/src/main/java/org/hyf/LibraryRepository.java b/task-2/src/main/java/org/hyf/LibraryRepository.java new file mode 100644 index 0000000..94d9570 --- /dev/null +++ b/task-2/src/main/java/org/hyf/LibraryRepository.java @@ -0,0 +1,173 @@ +package org.hyf; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +public class LibraryRepository { + + private final Connection conn; + + public LibraryRepository(Connection conn) { + this.conn = conn; + } + + public void insertData() throws SQLException { + // --- publishers (ids 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO publishers (publisher_name) VALUES (?)")) { + ps.setString(1, "O'Reilly"); + ps.executeUpdate(); + ps.setString(1, "Addison-Wesley"); + ps.executeUpdate(); + } + + // --- members (ids 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO members (member_name, member_email, member_phone, member_address) VALUES (?, ?, ?, ?)")) { + ps.setString(1, "Tom"); + ps.setString(2, "tom@example.com"); + ps.setString(3, "+31645678901"); + ps.setString(4, "Biltstraat 88, Utrecht"); + ps.executeUpdate(); + ps.setString(1, "Sam"); + ps.setString(2, "sam@example.com"); + ps.setString(3, "+31612345678"); + ps.setString(4, "Oudegracht 10, Utrecht"); + ps.executeUpdate(); + } + + // --- authors (ids 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO authors (author_name) VALUES (?)")) { + ps.setString(1, "Douglas Crockford"); + ps.executeUpdate(); + ps.setString(1, "Joshua Bloch"); + ps.executeUpdate(); + } + + // --- tags (ids 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO tags (tag_name) VALUES (?)")) { + ps.setString(1, "javascript"); + ps.executeUpdate(); + ps.setString(1, "java"); + ps.executeUpdate(); + } + + // --- books (ids 1, 2; publisher_id -> publishers 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO books (book_isbn, book_title, publisher_id) VALUES (?, ?, ?)")) { + ps.setString(1, "9780596517748"); + ps.setString(2, "JavaScript: The Good Parts"); + ps.setInt(3, 1); + ps.executeUpdate(); + ps.setString(1, "9780134685991"); + ps.setString(2, "Effective Java"); + ps.setInt(3, 2); + ps.executeUpdate(); + } + + // --- cards (member_id -> members 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO cards (card_number, card_issued_on, card_expires_on, member_id) VALUES (?, ?, ?, ?)")) { + ps.setString(1, "C-1004"); + ps.setDate(2, Date.valueOf("2023-11-20")); + ps.setDate(3, Date.valueOf("2026-11-20")); + ps.setInt(4, 1); + ps.executeUpdate(); + ps.setString(1, "C-1001"); + ps.setDate(2, Date.valueOf("2024-01-10")); + ps.setDate(3, Date.valueOf("2027-01-10")); + ps.setInt(4, 2); + ps.executeUpdate(); + } + + // --- copies (ids 1, 2; book_id -> books 1, 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO copies (copy_barcode, shelf_code, book_id) VALUES (?, ?, ?)")) { + ps.setString(1, "BC-0011"); + ps.setString(2, "B-03"); + ps.setInt(3, 1); + ps.executeUpdate(); + ps.setString(1, "BC-0001"); + ps.setString(2, "A-01"); + ps.setInt(3, 2); + ps.executeUpdate(); + } + + // --- loans (member_id + copy_id) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO loans (member_id, copy_id, borrowed_at, due_date, returned_at, fine_eur) VALUES (?, ?, ?, ?, ?, ?)")) { + ps.setInt(1, 1); + ps.setInt(2, 1); + ps.setTimestamp(3, Timestamp.valueOf("2025-12-02 08:08:00")); + ps.setTimestamp(4, Timestamp.valueOf("2025-12-23 08:08:00")); + ps.setTimestamp(5, Timestamp.valueOf("2025-12-13 08:08:00")); + ps.setBigDecimal(6, new BigDecimal("0.00")); + ps.executeUpdate(); + ps.setInt(1, 2); + ps.setInt(2, 2); + ps.setTimestamp(3, Timestamp.valueOf("2026-03-09 03:18:00")); + ps.setTimestamp(4, Timestamp.valueOf("2026-03-30 03:18:00")); + ps.setTimestamp(5, Timestamp.valueOf("2026-03-28 03:18:00")); + ps.setBigDecimal(6, new BigDecimal("1.25")); + ps.executeUpdate(); + } + + // --- book_authors bridge (book 1 + author 1, book 2 + author 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO book_authors (book_id, author_id) VALUES (?, ?)")) { + ps.setInt(1, 1); + ps.setInt(2, 1); + ps.executeUpdate(); + ps.setInt(1, 2); + ps.setInt(2, 2); + ps.executeUpdate(); + } + + // --- book_tags bridge (book 1 + tag 1, book 2 + tag 2) --- + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO book_tags (book_id, tag_id) VALUES (?, ?)")) { + ps.setInt(1, 1); + ps.setInt(2, 1); + ps.executeUpdate(); + ps.setInt(1, 2); + ps.setInt(2, 2); + ps.executeUpdate(); + } + } + + /** + * Fetches all borrowings and returns them as a list. + * Uses joins across loans -> members -> copies -> books. + */ + public List fetchData() throws SQLException { + String sql = + "SELECT m.member_name, b.book_title, c.copy_barcode, l.borrowed_at, l.returned_at " + + "FROM loans l " + + "JOIN members m ON m.member_id = l.member_id " + + "JOIN copies c ON c.copy_id = l.copy_id " + + "JOIN books b ON b.book_id = c.book_id " + + "ORDER BY l.borrowed_at"; + + List result = new ArrayList<>(); + try (PreparedStatement ps = conn.prepareStatement(sql); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + result.add(rs.getString("member_name") + + " -> " + rs.getString("book_title") + + " (copy " + rs.getString("copy_barcode") + ")" + + ", borrowed " + rs.getTimestamp("borrowed_at") + + ", returned " + rs.getTimestamp("returned_at")); + } + } + return result; + } +} diff --git a/task-2/src/main/java/org/hyf/Main.java b/task-2/src/main/java/org/hyf/Main.java index 7be95f2..c55da9b 100644 --- a/task-2/src/main/java/org/hyf/Main.java +++ b/task-2/src/main/java/org/hyf/Main.java @@ -1,7 +1,33 @@ package org.hyf; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +/** + * Entry point. Wires the pieces together: + * get a connection from Database, hand it to LibraryRepository, + * then print what fetchData() returns. + */ public class Main { + public static void main(String[] args) { - System.out.println("You code goes here :) You can overwrite this entire method."); + // The connection is opened once here and shared with the repository. + try (Connection conn = Database.getConnection()) { + System.out.println("Connected! " + conn.getCatalog()); + + LibraryRepository repo = new LibraryRepository(conn); + + repo.insertData(); + System.out.println("\nData inserted.\n"); + + List borrowings = repo.fetchData(); + System.out.println("Current borrowings (member -> book):"); + for (String row : borrowings) { + System.out.println(" " + row); + } + } catch (SQLException e) { + e.printStackTrace(); + } } } diff --git a/task-2/target/classes/org/hyf/Database.class b/task-2/target/classes/org/hyf/Database.class new file mode 100644 index 0000000000000000000000000000000000000000..a850526d11ea9ec1dc1e82f0532a6dd582a2c20c GIT binary patch literal 719 zcma)4U2oGc6g_UcB}?0N3v7e&F~*0~4GBwpycC3l*7DRbXjpkfPHIwD!6}YA*uR1& z5D)wSeiY(5gLMKS@nYxPbA5cyjlci+`VBx2w>(rBR&tZ_@$nnp6;{NekREDOorojB zGoe#{5Km-c8LEeaK10}_Gm$k)rw0RY-(ijF6(ff z<%!6~ROvjpfA22ORBVJfem#mAYKKv8;G@Cdk(F5?4TJyuNff<07<7HK80u7?ilTU* zn-Rm}Bc+vnOs3j9Lpr{fk7R%<&Nr}vRfbkyY58pSHkRh4h%@PYyW5AtD2IO)Yj&)P zlKW!@E>kKk#nO~r<(grmy>k+s!tgmkok8zIBB!pa@Nk7;t(=f+i(d3kxd2<(_Hd11 z{gm$-btKKc&?1$lfs44#u;2cB!v9nGr@JVhnM6KSZVJucKlzUHfNjtO=`B>Ti3KbY z@(5Mn6AsScI^pISyi7gxMzBh2NGCY$jP`}y&p7v~lvtzH34+AdX>FV|U;`HjEm77@ p61ha*HDWH8GP|W2APD`i9xi{u)sMtiFxP68S{14?+$g!5KLH<Sp literal 0 HcmV?d00001 diff --git a/task-2/target/classes/org/hyf/LibraryRepository.class b/task-2/target/classes/org/hyf/LibraryRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..70c5a2dd35e12780a7fabd7575aed6421f2ed5c2 GIT binary patch literal 6996 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%0DO zrSQ6mH#8ffmhCCm*F_5BO(r|4U*k=wDjkYykH<{Bg|`JFH{^I_s$oD>LK zzH%i4(nM?VBd|H&lC!|GIAh>6fvkIIYfYTRd!(wZ?vf$hm2I|3#=!Y*W(5o$g21?T zoi&le9LrIW-dx~?-E>W}>XM1eI&rm1zaWNWvP^J z)a$PDJXKmROt*1Rm_=MSaE--ZV{QI&_RQU)s_Sks@lnvh0Y~z_EH2Hvvd9h&Nw-WQ z4sX(vR2JE8YmM>(8>n{49)VqJjlO|YtZH<>vR_d^?*8 zeRpR(JUH4tdzS0%`7`mtCC4}Vp5giaAPqTL55|^(8^oG-8g5aYwe)O@=uvq{!y@ie zj^lg2E9*JsSDca;#cg;7$g+ub;Ff*ISyJid1ZejASy|N0r3V4|^i5}Dwyc{D&q=#f zRo+CFByZF+8xeO%x(?1W?oG@)%h}>>s~Lh6w_ME1YBkS>$!zOGJq*^tBy2rkRcT*b zgt5%}#N&eU^Ax+YRY@K)?k@!PZ-{%dNDSN;*!%yc{O~lx#y&Pt4+kf(g>U@k@M&y_JTZ3m{z+FFg5e#5k^RW1dF2j`lAhII&3kK(Y#^}^j>=t;2 zSAZ29_!X(esV6uz9g2lk@EY|)M<~{tqtm^5JFU09PjOse+IWQBG2r>_j|eAIE4Y$;o=vXc z!^Z(u8~2Degs=J2f8X)9$i0sFXb|5ZBifD_hB1y2BruA@7{f72PVftN6}wR4YIEp3 z#9n;QkCx8S3^T?&;A7n28peHmf=_wlI4(e literal 0 HcmV?d00001