У меня есть проект веб на Tomcat. Всё работает, хочу добавить страницу, имитирующую книжную полку, на ней в каком-то порядке должны стоять книги, можно не замарачиваться и отрисовывать всё символами типа \ | и тд. На этих полках рисуются корешки книг, на которых написано только название, щёлкая на название переходишь на страницу, которая указывает всю информацию о книге, а описание генерируется по api какой-нибудь нейросетью
package com.bookshelf.dao;

import com.bookshelf.model.Author;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class AuthorDAO {
    public List<Author> getAllAuthors() {
        List<Author> authors = new ArrayList<>();
        try (Connection conn = DatabaseUtil.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM authors")) {
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                authors.add(new Author(id, name));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return authors;
    }

    public Author getAuthorById(int id) {
        Author author = null;
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("SELECT * FROM authors WHERE id = ?")) {
            stmt.setInt(1, id);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    String name = rs.getString("name");
                    author = new Author(id, name);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return author;
    }

    public void addAuthor(Author author) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("INSERT INTO authors (name) VALUES (?)")) {
            stmt.setString(1, author.getName());
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void updateAuthor(Author author) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("UPDATE authors SET name = ? WHERE id = ?")) {
            stmt.setString(1, author.getName());
            stmt.setInt(2, author.getId());
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void deleteAuthor(int id) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("DELETE FROM authors WHERE id = ?")) {
            stmt.setInt(1, id);
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
package com.bookshelf.dao;

import com.bookshelf.model.Book;
import com.bookshelf.model.Author;
import com.bookshelf.model.Genre;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class BookDAO {
    public void addBook(Book book) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("INSERT INTO books (title, author_id, genre_id, description) VALUES (?, ?, ?, ?)")) {
            stmt.setString(1, book.getTitle());
            stmt.setInt(2, book.getAuthor().getId());
            stmt.setInt(3, book.getGenre().getId());
            stmt.setString(4, book.getDescription());
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("Ошибка при добавлении книги: " + book.getTitle(), e);
        }
    }

    public void updateBook(Book book) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("UPDATE books SET title = ?, author_id = ?, genre_id = ?, description = ? WHERE id = ?")) {
            stmt.setString(1, book.getTitle());
            stmt.setInt(2, book.getAuthor().getId());
            stmt.setInt(3, book.getGenre().getId());
            stmt.setString(4, book.getDescription());
            stmt.setInt(5, book.getId());
            int rowsAffected = stmt.executeUpdate();
            if (rowsAffected == 0) {
                throw new SQLException("Книга с ID " + book.getId() + " не найдена");
            }
        } catch (SQLException e) {
            throw new RuntimeException("Ошибка при обновлении книги: " + book.getTitle(), e);
        }
    }

    public void deleteBook(int id) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("DELETE FROM books WHERE id = ?")) {
            stmt.setInt(1, id);
            int rowsAffected = stmt.executeUpdate();
            if (rowsAffected == 0) {
                throw new SQLException("Книга с ID " + id + " не найдена");
            }
        } catch (SQLException e) {
            throw new RuntimeException("Ошибка при удалении книги с ID: " + id, e);
        }
    }

    public Book getBookById(int id) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("SELECT b.id, b.title, b.author_id, b.genre_id, b.description, a.name AS author_name, g.name AS genre_name " +
                     "FROM books b " +
                     "JOIN authors a ON b.author_id = a.id " +
                     "JOIN genres g ON b.genre_id = g.id " +
                     "WHERE b.id = ?")) {
            stmt.setInt(1, id);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    Author author = new Author(rs.getInt("author_id"), rs.getString("author_name"));
                    Genre genre = new Genre(rs.getInt("genre_id"), rs.getString("genre_name"));
                    return new Book(rs.getInt("id"), rs.getString("title"), author, genre, rs.getString("description"));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Ошибка при получении книги по ID: " + id, e);
        }
        return null;
    }

    public List<Book> getAllBooks() {
        List<Book> books = new ArrayList<>();
        try (Connection conn = DatabaseUtil.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT b.id, b.title, b.author_id, b.genre_id, b.description, a.name AS author_name, g.name AS genre_name " +
                     "FROM books b " +
                     "JOIN authors a ON b.author_id = a.id " +
                     "JOIN genres g ON b.genre_id = g.id")) {
            while (rs.next()) {
                Author author = new Author(rs.getInt("author_id"), rs.getString("author_name"));
                Genre genre = new Genre(rs.getInt("genre_id"), rs.getString("genre_name"));
                books.add(new Book(rs.getInt("id"), rs.getString("title"), author, genre, rs.getString("description")));
            }
        } catch (SQLException e) {
            throw new RuntimeException("Ошибка при получении списка книг", e);
        }
        return books;
    }
}
package com.bookshelf.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseUtil {
    private static final String JDBC_URL = "jdbc:h2:/Users/intheseshadows/Desktop/library_java/src/main/java/com/bookshelf/db/bookshelf";
    private static final String USER = "sa";
    private static final String PASSWORD = "";

    static {
        try {
            Class.forName("org.h2.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Failed to load H2 driver", e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
    }
}
package com.bookshelf.dao;

import com.bookshelf.model.Genre;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class GenreDAO {
    public List<Genre> getAllGenres() {
        List<Genre> genres = new ArrayList<>();
        try (Connection conn = DatabaseUtil.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM genres")) {
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                genres.add(new Genre(id, name));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return genres;
    }

    public Genre getGenreById(int id) {
        Genre genre = null;
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("SELECT * FROM genres WHERE id = ?")) {
            stmt.setInt(1, id);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    String name = rs.getString("name");
                    genre = new Genre(id, name);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return genre;
    }

    public void addGenre(Genre genre) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("INSERT INTO genres (name) VALUES (?)")) {
            stmt.setString(1, genre.getName());
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void updateGenre(Genre genre) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("UPDATE genres SET name = ? WHERE id = ?")) {
            stmt.setString(1, genre.getName());
            stmt.setInt(2, genre.getId());
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void deleteGenre(int id) {
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement("DELETE FROM genres WHERE id = ?")) {
            stmt.setInt(1, id);
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
package com.bookshelf.model;

public class Author {
    private int id;
    private String name;

    public Author(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
package com.bookshelf.model;

public class Book {
    private int id;
    private String title;
    private Author author;
    private Genre genre;
    private String description;

    public Book(int id, String title, Author author, Genre genre) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.genre = genre;
        this.description = null;
    }

    public Book(int id, String title, Author author, Genre genre, String description) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.genre = genre;
        this.description = description;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    public Genre getGenre() {
        return genre;
    }

    public void setGenre(Genre genre) {
        this.genre = genre;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
package com.bookshelf.model;

public class Genre {
    private int id;
    private String name;

    public Genre(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }
}

package com.bookshelf.servlets;

import com.bookshelf.dao.AuthorDAO;
import com.bookshelf.model.Author;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/addAuthor")
public class AddAuthorServlet extends HttpServlet {
    private AuthorDAO authorDAO;

    public void init() {
        authorDAO = new AuthorDAO();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher dispatcher = request.getRequestDispatcher("/author-form.jsp");
        dispatcher.forward(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        if (name == null || name.trim().isEmpty()) {
            request.setAttribute("error", "Имя автора обязательно");
            request.getRequestDispatcher("/error.jsp").forward(request, response);
            return;
        }
        try {
            Author newAuthor = new Author(0, name);
            authorDAO.addAuthor(newAuthor);
            response.sendRedirect("authors");
        } catch (Exception e) {
            request.setAttribute("error", "Ошибка при добавлении автора: " + e.getMessage());
            request.getRequestDispatcher("/error.jsp").forward(request, response);
        }
    }
}
package com.bookshelf.servlets;

import com.bookshelf.dao.AuthorDAO;
import com.bookshelf.model.Author;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/authors")
public class AuthorServlet extends HttpServlet {
    private AuthorDAO authorDAO;

    public void init() {
        authorDAO = new AuthorDAO();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Author> authors = authorDAO.getAllAuthors();
        request.setAttribute("authors", authors);
        RequestDispatcher dispatcher = request.getRequestDispatcher("/author-list.jsp");
        dispatcher.forward(request, response);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<nav>
  <a href="books">Книжная полка</a> |
  <a href="authors">Авторы</a> |
  <a href="genres">Жанры</a>
</nav>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>
<html>
<head>
  <title>Книжная полка</title>
</head>
<body>
<jsp:include page="nav.jsp"/>
<h1>Книжная полка</h1>
<table border="1">
  <tr>
    <th>Название</th>
    <th>Автор</th>
    <th>Жанр</th>
    <th>Действия</th>
  </tr>
  <c:forEach var="book" items="${books}">
    <tr>
      <td><a href="bookDetails?id=${book.id}">${book.title}</a></td>
      <td>${book.author.name}</td>
      <td>${book.genre.name}</td>
      <td>
        <a href="editBook?id=${book.id}">Редактировать</a>
        <a href="deleteBook?id=${book.id}" onclick="return confirm('Вы уверены?');">Удалить</a>
      </td>
    </tr>
  </c:forEach>
</table>
<br>
<a href="addBook">Добавить книгу</a>
</body>
</html>