package com.mzh.library.dao.impl; import com.mzh.library.dao.BookDao; import com.mzh.library.entity.Book; import com.mzh.library.entity.BookCategory; import com.mzh.library.entity.BookSearchCriteria; import com.mzh.library.entity.BookStatus; import com.mzh.library.exception.DaoException; import com.mzh.library.util.JdbcUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class JdbcBookDao implements BookDao { private static final String BOOK_COLUMNS = "" + "b.id, b.book_identifier, b.title, b.author, b.category_id, c.name AS category_name, " + "b.total_copies, b.available_copies, b.status, b.created_at, b.updated_at "; private static final String BOOK_FROM = "" + "FROM books b " + "JOIN book_categories c ON c.id = b.category_id "; private static final String FIND_ALL_CATEGORIES = "" + "SELECT id, name, description " + "FROM book_categories " + "ORDER BY name"; private static final String FIND_CATEGORY_BY_ID = "" + "SELECT id, name, description " + "FROM book_categories " + "WHERE id = ?"; private static final String FIND_CATEGORY_BY_NAME = "" + "SELECT id, name, description " + "FROM book_categories " + "WHERE name = ?"; private static final String CREATE_CATEGORY = "" + "INSERT INTO book_categories (name, description) " + "VALUES (?, ?)"; private static final String UPDATE_CATEGORY = "" + "UPDATE book_categories " + "SET name = ?, description = ? " + "WHERE id = ?"; private static final String DELETE_CATEGORY = "DELETE FROM book_categories WHERE id = ?"; private static final String COUNT_BOOKS_BY_CATEGORY = "" + "SELECT COUNT(*) " + "FROM books " + "WHERE category_id = ?"; private static final String FIND_BY_ID = "SELECT " + BOOK_COLUMNS + BOOK_FROM + "WHERE b.id = ?"; private static final String FIND_BY_IDENTIFIER = "SELECT " + BOOK_COLUMNS + BOOK_FROM + "WHERE b.book_identifier = ?"; private static final String CREATE = "" + "INSERT INTO books " + "(book_identifier, title, author, category_id, total_copies, available_copies, status) " + "VALUES (?, ?, ?, ?, ?, ?, ?)"; private static final String UPDATE = "" + "UPDATE books " + "SET book_identifier = ?, title = ?, author = ?, category_id = ?, total_copies = ?, " + "available_copies = ?, status = ? " + "WHERE id = ?"; private static final String DELETE = "DELETE FROM books WHERE id = ?"; @Override public List findAllCategories() { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(FIND_ALL_CATEGORIES); ResultSet resultSet = statement.executeQuery()) { List categories = new ArrayList<>(); while (resultSet.next()) { categories.add(mapCategory(resultSet)); } return categories; } catch (SQLException ex) { throw new DaoException("Unable to load book categories", ex); } } @Override public Optional findCategoryById(long id) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(FIND_CATEGORY_BY_ID)) { statement.setLong(1, id); try (ResultSet resultSet = statement.executeQuery()) { return resultSet.next() ? Optional.of(mapCategory(resultSet)) : Optional.empty(); } } catch (SQLException ex) { throw new DaoException("Unable to load book category by id", ex); } } @Override public Optional findCategoryByName(String name) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(FIND_CATEGORY_BY_NAME)) { statement.setString(1, name); try (ResultSet resultSet = statement.executeQuery()) { return resultSet.next() ? Optional.of(mapCategory(resultSet)) : Optional.empty(); } } catch (SQLException ex) { throw new DaoException("Unable to load book category by name", ex); } } @Override public long createCategory(BookCategory category) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(CREATE_CATEGORY, Statement.RETURN_GENERATED_KEYS)) { bindCategory(statement, category); statement.executeUpdate(); try (ResultSet generatedKeys = statement.getGeneratedKeys()) { if (generatedKeys.next()) { return generatedKeys.getLong(1); } } throw new DaoException("Unable to read generated book category id", null); } catch (SQLException ex) { throw new DaoException("Unable to create book category", ex); } } @Override public boolean updateCategory(BookCategory category) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(UPDATE_CATEGORY)) { bindCategory(statement, category); statement.setLong(3, category.getId()); return statement.executeUpdate() == 1; } catch (SQLException ex) { throw new DaoException("Unable to update book category", ex); } } @Override public boolean deleteCategory(long id) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(DELETE_CATEGORY)) { statement.setLong(1, id); return statement.executeUpdate() == 1; } catch (SQLException ex) { throw new DaoException("Unable to delete book category", ex); } } @Override public int countBooksByCategoryId(long categoryId) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(COUNT_BOOKS_BY_CATEGORY)) { statement.setLong(1, categoryId); try (ResultSet resultSet = statement.executeQuery()) { return resultSet.next() ? resultSet.getInt(1) : 0; } } catch (SQLException ex) { throw new DaoException("Unable to count books by category", ex); } } @Override public List search(BookSearchCriteria criteria) { List parameters = new ArrayList<>(); StringBuilder sql = new StringBuilder("SELECT ") .append(BOOK_COLUMNS) .append(BOOK_FROM) .append("WHERE 1 = 1 "); appendLike(sql, parameters, "b.book_identifier", criteria.getIdentifier()); appendLike(sql, parameters, "b.title", criteria.getTitle()); appendLike(sql, parameters, "b.author", criteria.getAuthor()); if (criteria.getCategoryId() != null) { sql.append("AND b.category_id = ? "); parameters.add(criteria.getCategoryId()); } sql.append("ORDER BY b.title, b.author, b.book_identifier"); try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(sql.toString())) { bind(statement, parameters); try (ResultSet resultSet = statement.executeQuery()) { List books = new ArrayList<>(); while (resultSet.next()) { books.add(mapBook(resultSet)); } return books; } } catch (SQLException | IllegalArgumentException ex) { throw new DaoException("Unable to search books", ex); } } @Override public Optional findById(long id) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(FIND_BY_ID)) { statement.setLong(1, id); try (ResultSet resultSet = statement.executeQuery()) { return resultSet.next() ? Optional.of(mapBook(resultSet)) : Optional.empty(); } } catch (SQLException | IllegalArgumentException ex) { throw new DaoException("Unable to load book by id", ex); } } @Override public Optional findByIdentifier(String identifier) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(FIND_BY_IDENTIFIER)) { statement.setString(1, identifier); try (ResultSet resultSet = statement.executeQuery()) { return resultSet.next() ? Optional.of(mapBook(resultSet)) : Optional.empty(); } } catch (SQLException | IllegalArgumentException ex) { throw new DaoException("Unable to load book by identifier", ex); } } @Override public long create(Book book) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(CREATE, Statement.RETURN_GENERATED_KEYS)) { bindBook(statement, book); statement.executeUpdate(); try (ResultSet generatedKeys = statement.getGeneratedKeys()) { if (generatedKeys.next()) { return generatedKeys.getLong(1); } } throw new DaoException("Unable to read generated book id", null); } catch (SQLException ex) { throw new DaoException("Unable to create book", ex); } } @Override public boolean update(Book book) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(UPDATE)) { bindBook(statement, book); statement.setLong(8, book.getId()); return statement.executeUpdate() == 1; } catch (SQLException ex) { throw new DaoException("Unable to update book", ex); } } @Override public boolean delete(long id) { try (Connection connection = JdbcUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(DELETE)) { statement.setLong(1, id); return statement.executeUpdate() == 1; } catch (SQLException ex) { throw new DaoException("Unable to delete book", ex); } } private void appendLike(StringBuilder sql, List parameters, String column, String value) { if (value == null || value.trim().isEmpty()) { return; } sql.append("AND ").append(column).append(" LIKE ? "); parameters.add("%" + value.trim() + "%"); } private void bind(PreparedStatement statement, List parameters) throws SQLException { for (int i = 0; i < parameters.size(); i++) { Object value = parameters.get(i); if (value instanceof Long) { statement.setLong(i + 1, (Long) value); } else { statement.setString(i + 1, value.toString()); } } } private void bindBook(PreparedStatement statement, Book book) throws SQLException { statement.setString(1, book.getIdentifier()); statement.setString(2, book.getTitle()); statement.setString(3, book.getAuthor()); statement.setLong(4, book.getCategoryId()); statement.setInt(5, book.getTotalCopies()); statement.setInt(6, book.getAvailableCopies()); statement.setString(7, book.getStatus().getCode()); } private void bindCategory(PreparedStatement statement, BookCategory category) throws SQLException { statement.setString(1, category.getName()); statement.setString(2, category.getDescription()); } private Book mapBook(ResultSet resultSet) throws SQLException { Book book = new Book(); book.setId(resultSet.getLong("id")); book.setIdentifier(resultSet.getString("book_identifier")); book.setTitle(resultSet.getString("title")); book.setAuthor(resultSet.getString("author")); book.setCategoryId(resultSet.getLong("category_id")); book.setCategoryName(resultSet.getString("category_name")); book.setTotalCopies(resultSet.getInt("total_copies")); book.setAvailableCopies(resultSet.getInt("available_copies")); book.setStatus(BookStatus.fromCode(resultSet.getString("status"))); book.setCreatedAt(toLocalDateTime(resultSet.getTimestamp("created_at"))); book.setUpdatedAt(toLocalDateTime(resultSet.getTimestamp("updated_at"))); return book; } private BookCategory mapCategory(ResultSet resultSet) throws SQLException { BookCategory category = new BookCategory(); category.setId(resultSet.getLong("id")); category.setName(resultSet.getString("name")); category.setDescription(resultSet.getString("description")); return category; } private LocalDateTime toLocalDateTime(Timestamp timestamp) { return timestamp == null ? null : timestamp.toLocalDateTime(); } }