Library management system practice based on Spring Boot and JPA

Table of contents

Preface

Technology selection

Functional Requirements

system design

Interface display

Implementation steps 

1. Create a Spring Boot project

2. Project code layering

3. Design and create entity classes

4. Create data access layer (Repository)

5. Create a service layer (Service)

5. Create a controller (Controller) 

 6. Create page templates

7. Configure database connection

8. Run the startup application

Summarize


Preface

In places such as libraries and bookstores, managing a large number of books is an important task. In order to improve the efficiency and accuracy of library management, we can develop a library management system based on Spring Boot and JPA. This blog will describe how to use these technologies to design and implement a fully functional library management system.


Technology selection

This project uses the following technologies:

  • Spring Boot: Quickly build Spring-based applications
  • JPA: Java persistence API simplifies data persistence operations
  • Thymeleaf: template engine for front-end page rendering
  • MySQL: relational database, used to store book-related data
  • Bootstrap: for page design and layout

Functional Requirements

Our library management system needs to implement the following functions:

  1. Book information management: including addition, deletion, modification and query of book title, author, publisher and other attributes
  2. Add books: Add new book information to the system
  3. Delete book: Delete the specified book information from the system
  4. Update books: Modify existing book information in the system
  5. Search books: Search for books that meet the requirements based on keywords or other conditions
  6. Paging function: Display multiple book information in pages to improve browsing efficiency
  7. Sorting function: Sort book information according to various conditions to facilitate search and comparison.

system design

We store book information in a MySQL database and use JPA to manage database operations. By creating entity classes and corresponding Repository interfaces, we can easily perform addition, deletion, modification and query operations. At the same time, the Thymeleaf template engine is used to render the front-end page and display book information.


Interface display


Implementation steps 

1. Create a Spring Boot project

Create a new Spring Boot project using Spring Initializr (https://start.spring.io/).

Add required dependencies including Spring Web, Spring Data JPA, Thymeleaf, MySQL driver, etc.

2. Project code layering

3. Design and create entity classes

Create a Book entity class, including id, title, author, publishDate and categoryId attributes, and use entity and table mapping annotations.

@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(name = "books")
public class Book {

    // 表示该字段是实体类的主键
    @Id
    // 表示该主键的生成策略是自增长的方式生成。
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String title;

    private String author;

    // 定义一个ManyToOne(多对一)的关联关系
    @ManyToOne
    private Category category;

    @Column(name = "publish_date")
    private YearMonth publishDate;

}

Create a Category entity class, including id and name attributes, and use @Entity and @Table annotations on the class for entity mapping and table mapping.

@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(name = "categories")
public class Category {

    // 表示该字段是实体类的主键
    @Id
    // 表示该主键的生成策略是自增长的方式生成。
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;

    // 这是一个一对多的关系,即一个类别对应多个图书
    @OneToMany(mappedBy = "category")
    private List<Book> books;

}

4. Create data access layer (Repository)

Create the BookRepository interface, inherit JpaRepository, and use the @Repository annotation for identification.

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
    // 定义了两个方法用于实现根据关键字进行标题和作者模糊搜索,并返回一个带有分页结果的Page对象
    Page<Book> findByTitleContaining(String keyword, Pageable pageable);
    Page<Book> findByAuthorContaining(String keyword, Pageable pageable);
}

Create the CategoryRepository interface, inherit JpaRepository, and use the @Repository annotation for identification.

@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {

}

5. Create a service layer (Service)

Create a CategoryService interface and define methods for adding, deleting, modifying and querying classification information.

public interface BookService {
    // 获取所有图书的方法
    List<Book> getAllBooks();

    // 接受一个  `Book`  类型的参数,并将其保存
    void saveBook(Book book);

    // 通过图书ID获取图书的方法
    Book getBookById(long id);

    // 通过图书ID删除图书的方法
    void deleteBookById(long id);

    // 根据关键字和搜索条件进行分页搜索图书的方法
    Page<Book> searchBooks(String keyword, String searchBy, Pageable pageable);

    // 分页和排序
    Page<Book> findPaginated(int pageNo, int pageSize,StringsortField,StringsortDirection);

}

Create the CategoryServiceImpl class, implement the CategoryService interface, and use the @Service annotation for identification.

Inject CategoryRepository into CategoryServiceImpl and implement the methods defined by the interface.

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookRepository bookRepository;

    @Override
    // 从数据库中获取所有图书的列表
    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }

    @Override
    // 将图书对象保存到数据库中
    public void saveBook(Book book) {
        bookRepository.save(book);
    }

    @Override
    // 根据id查找并返回对应的图书对象。如果找不到对应的图书,则返回`null`
    public Book getBookById(long id) {
        return bookRepository.findById(id).orElse(null);
    }

    @Override
    // 根据id从数据库中删除对应的图书
    public void deleteBookById(long id) {
        bookRepository.deleteById(id);
    }

    @Override
    // 在数据库中根据标题或作者进行模糊查找,并返回一个分页的结果
    public Page<Book> searchBooks(String keyword, String searchBy, Pageable pageable) {
        switch (searchBy) {
            case "title":
                return bookRepository.findByTitleContaining(keyword, pageable);
            case "author":
                return bookRepository.findByAuthorContaining(keyword, pageable);
            default:
                throw new IllegalArgumentException("Invalid searchBy parameter");
        }
    }

    // 分页查询图书列表
    @Override
    public Page<Book> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) {
        Sort sort = sortDirection.equals("asc") ? Sort.by(sortField).ascending() : Sort.by(sortField).descending();
        PageRequest pageable = PageRequest.of(pageNo - 1, pageSize, sort);
        return bookRepository.findAll(pageable);
    }
}

Create the BookService interface and define methods for adding, deleting, modifying, and paging query of book information.

public interface CategoryService {
    // 获取所有分类的信息,并以列表的形式返回
    List<Category> getAllCategories();
    // 保存一个分类的信息
    void saveCategory(Category category);
    // 根据分类的ID获取对应的分类信息
    Category getCategoryById(long id);
    // 根据分类的ID删除对应的分类信息
    void deleteCategoryById(long id);
    // 根据分类的ID获取该分类下的图书,并根据分页设置进行分页处理,并以页面对象的形式返回
    Page<Book> getBooksByCategoryId(long categoryId, Pageable pageable);
    
}

Create the BookServiceImpl class, implement the BookService interface, and use the @Service annotation for identification.

Inject CategoryRepository and BookRepository into BookServiceImpl and implement the methods defined by the interface.

@Repository
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    private CategoryRepository categoryRepository;

    @Override
    // 获取数据库中的所有Category记录,然后返回一个Category列表
    public List<Category> getAllCategories() {
        return categoryRepository.findAll();
    }

    @Override
    // 将传入的Category对象保存到数据库中
    public void saveCategory(Category category) {
        categoryRepository.save(category);
    }

    @Override
    // 通过调用categoryRepository的findById()方法查询对应的Category记录,并返回该记录。如果查询不到对应的记录,则返回null
    public Category getCategoryById(long id) {
        return categoryRepository.findById(id).orElse(null);
    }

    @Override
    // 通过调用categoryRepository的deleteById()方法删除对应的Category记录
    public void deleteCategoryById(long id) {
        categoryRepository.deleteById(id);
    }

    @Override
    // 如果查到了该记录,则获取其关联的Book列表,并根据传入的分页参数进行分页处理,最后返回一个Page<Book>对象。如果未查到对应的Category记录,则返回一个空的Page对象
    public Page<Book> getBooksByCategoryId(long categoryId, Pageable pageable) {
        Optional<Category> categoryOptional = categoryRepository.findById(categoryId);
        if (categoryOptional.isPresent()) {
            Category category = categoryOptional.get();
            List<Book> books = category.getBooks();

            // 创建一个分页请求
            int pageSize = pageable.getPageSize();
            int currentPage = pageable.getPageNumber();
            int startItem = currentPage * pageSize;

            List<Book> pagedBooks;
            if (books.size() < startItem) {
                pagedBooks = Collections.emptyList();
            } else {
                int toIndex = Math.min(startItem + pageSize, books.size());
                pagedBooks = books.subList(startItem, toIndex);
            }

            return new PageImpl<>(pagedBooks, pageable, books.size());
        } else {
            return Page.empty();
        }
    }

}

5. Create a controller (Controller) 

Create a CategoryController class and use the @Controller annotation to identify it.

Inject CategoryService into CategoryController and implement classification-related HTTP request processing methods, including querying all categories, adding categories, querying categories, deleting categories, etc.

@Controller
@RequestMapping("/categories")
public class CategoryController {

    @Autowired
    private CategoryServiceImpl categoryRepository;

    @GetMapping("/list")
    public String getAllCategories(Model model) {
        List<Category> categories = categoryRepository.getAllCategories();
        model.addAttribute("categories", categories);
        return "category-list";
    }

    @GetMapping("/add")
    public String showAddCategoryForm(Model model) {
        Category category = new Category();
        model.addAttribute("category", category);
        return "add-category";
    }

    @PostMapping("/add")
    public String addCategory(@ModelAttribute("category") Category category) {
        categoryRepository.saveCategory(category);
        return "redirect:/categories/list";
    }

    @GetMapping("/edit/{id}")
    public String showEditCategoryForm(@PathVariable long id, Model model) {
        Category category = categoryRepository.getCategoryById(id);
        model.addAttribute("category", category);
        return "edit-category";
    }

    @PostMapping("/edit/{id}")
    public String editCategory(@PathVariable long id, @ModelAttribute("category") Category category) {
        category.setId(id);
        categoryRepository.saveCategory(category);
        return "redirect:/categories/list";
    }

    @GetMapping("/delete/{id}")
    public String deleteCategory(@PathVariable long id) {
        categoryRepository.deleteCategoryById(id);
        return "redirect:/categories/list";
    }

    @GetMapping("/{categoryId}/books")
    public String getBooksByCategoryId(@PathVariable long categoryId,
                                       @RequestParam(value = "page", defaultValue = "1") int page,
                                       @RequestParam(value = "pageSize", defaultValue = "999") int pageSize,
                                       Model model) {
        Page<Book> bookPage = categoryRepository.getBooksByCategoryId(categoryId, PageRequest.of(page - 1, pageSize));
        List<Book> books = bookPage.getContent();

        model.addAttribute("books", books);
        model.addAttribute("totalPages", bookPage.getTotalPages());
        model.addAttribute("totalItems", bookPage.getTotalElements());
        model.addAttribute("currentPage", page);

        return "book-list";
    }

}

Create the BookController class and use the @Controller annotation to identify it.

Inject BookService into BookController and implement book-related HTTP request processing methods, including operations such as querying books, adding books, and querying books under categories.

@Controller
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @Autowired
    private CategoryService categoryService;

    @GetMapping("/list")
    public String getAllBooks(Model model) {
        return findPaginated(1, 5,"id","desc", model);
    }

    @GetMapping("/add")
    public String showAddBookForm(Model model) {
        Book book = new Book();
        model.addAttribute("book", book);

        // 从数据库或其他数据源获取分类列表
        List<Category> categories = categoryService.getAllCategories();
        // 将分类列表传递给前端模板
        model.addAttribute("categories", categories);
        return "add-book";
    }

    @PostMapping("/add")
    public String addBook(@ModelAttribute("book") Book book) {
        bookService.saveBook(book);
        return "redirect:/books/list";
    }

    @GetMapping("/edit/{id}")
    public String showEditBookForm(@PathVariable long id, Model model) {
        Book book = bookService.getBookById(id);
        model.addAttribute("book", book);
        // 从数据库或其他数据源获取分类列表
        List<Category> categories = categoryService.getAllCategories();
        // 将分类列表传递给前端模板
        model.addAttribute("categories", categories);
        return "edit-book";
    }

    @PostMapping("/edit/{id}")
    public String editBook(@PathVariable long id, @ModelAttribute("book") Book book) {
        book.setId(id);
        bookService.saveBook(book);
        return "redirect:/books/list";
    }

    @GetMapping("/delete/{id}")
    public String deleteBook(@PathVariable long id) {
        bookService.deleteBookById(id);
        return "redirect:/books/list";
    }


    @PostMapping("/search")
    public String searchBooks(@RequestParam("keyword") String keyword,
                              @RequestParam("searchBy") String searchBy,
                              @PageableDefault(size = 999, sort = "id") Pageable pageable,
                              Model model) {
        Page<Book> bookPage = bookService.searchBooks(keyword, searchBy, pageable);
        model.addAttribute("books", bookPage.getContent());
        model.addAttribute("totalItems", bookPage.getTotalElements());
        model.addAttribute("currentPage", bookPage.getNumber() + 1);
        model.addAttribute("totalPages", bookPage.getTotalPages());
        return "book-list";
    }

    @GetMapping("/page/{pageNo}")
    public String findPaginated(@PathVariable(value = "pageNo") int pageNo,
                                @RequestParam("pageSize") int pageSize,
                                @RequestParam("sortField") String sortField,
                                @RequestParam("sortDir") String sortDir,
                                Model model) {
        Page<Book> page = bookService.findPaginated(pageNo, pageSize, sortField, sortDir);
        List<Book> books = page.getContent();
        model.addAttribute("currentPage", pageNo);
        model.addAttribute("totalPages", page.getTotalPages());
        model.addAttribute("totalItems", page.getTotalElements());
        model.addAttribute("books", books);
        model.addAttribute("sortField", sortField);
        model.addAttribute("sortDir", sortDir);
        return "book-list";
    }

}

 6. Create page templates

Use Thymeleaf or other template engines to create HTML page templates, including category lists, book lists, forms for adding/editing categories, forms for adding/editing books, etc.

7. Configure database connection

Configure database connection-related information in the application.properties (or yml) file, such as database URL, user name and password.

spring.datasource.url=jdbc:mysql://localhost:3306/librarydb?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username= root
spring.datasource.password= 123456

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# for Spring Boot 2
# spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect

# for Spring Boot 3
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE

8. Run the startup application

Start the application, access the corresponding URL, and perform classification and book management operations.


Summarize

In the final project of this semester, I had the opportunity to complete a web project independently, which was a very valuable and challenging experience for me. Through this project, I not only learned the correct coding standards, but also mastered the Git operation process and code management skills in team collaboration.

In addition, I also deeply studied Java 's data structure, binary IO , multi-threading and stream programming, which provided me with a strong foundation for writing more efficient, concurrent and flexible programs.

Finally, the teacher taught the method of layered project development, and I chose to use Spring Boot , JPA , Thymeleaf , MySQL and Bootstrap to build my Web project. This combination of technologies allowed me to easily implement all aspects of the project, including data persistence, business logic processing, front-end presentation, and user interaction.

Through this independently completed final project, I not only deepened my understanding and application of technical knowledge, but also improved my ability to solve problems and develop independently. This will have a positive impact on my future career development.

I am very pleased with my learning outcomes this semester and look forward to continuing to expand my technical knowledge and project experience in the future.

Guess you like

Origin blog.csdn.net/m0_69496245/article/details/131546393