目录
前言
在图书馆、书店等场所,管理大量图书是一项重要的任务。为了提高图书管理的效率和精确性,我们可以开发一个基于Spring Boot和JPA的图书管理系统。本博客将介绍如何使用这些技术来设计和实现一个功能齐全的图书管理系统。
技术选型
本项目使用以下技术:
- Spring Boot:快速搭建基于Spring的应用程序
- JPA:Java持久化API,简化了数据持久化的操作
- Thymeleaf:模板引擎用于前端页面渲染
- MySQL:关系型数据库,用于存储图书相关数据
- Bootstrap:用于页面设计和布局
功能需求
我们的图书管理系统需要实现以下功能:
- 图书信息管理:包括图书名称、作者、出版社等属性的增删改查
- 添加图书:向系统中添加新的图书信息
- 删除图书:从系统中删除指定的图书信息
- 更新图书:对系统中已有的图书信息进行修改
- 查询图书:根据关键字或其他条件查询符合要求的图书
- 分页功能:将多条图书信息分页显示,提高浏览效率
- 排序功能:按照各种条件对图书信息进行排序,方便查找和比较
系统设计
我们将图书信息存储在MySQL数据库中,使用JPA来管理数据库操作。通过创建实体类和对应的Repository接口,我们可以轻松地进行增删改查操作。同时,使用Thymeleaf模板引擎来渲染前端页面,展示图书信息。
界面展示
实现步骤
1.创建Spring Boot项目
使用Spring Initializr(https://start.spring.io/)创建一个新的Spring Boot项目。
添加所需的依赖项,包括Spring Web、Spring Data JPA、Thymeleaf、MySQL驱动程序等。
2.项目代码分层
3.设计并创建实体类
创建Book实体类,包含id、title、author、publishDate和categoryId属性,并使用实体和表映射注解。
@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;
}
创建Category实体类,包含id和name属性,并在类上使用@Entity和@Table注解进行实体映射和表映射。
@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.创建数据访问层(Repository)
创建BookRepository接口,继承JpaRepository,并使用@Repository注解进行标识。
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
// 定义了两个方法用于实现根据关键字进行标题和作者模糊搜索,并返回一个带有分页结果的Page对象
Page<Book> findByTitleContaining(String keyword, Pageable pageable);
Page<Book> findByAuthorContaining(String keyword, Pageable pageable);
}
创建CategoryRepository接口,继承JpaRepository,并使用@Repository注解进行标识。
@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
}
5.创建服务层(Service)
创建CategoryService接口,定义对分类信息的增删改查方法。
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);
}
创建CategoryServiceImpl类,实现CategoryService接口,使用@Service注解进行标识。
在CategoryServiceImpl中注入CategoryRepository,并实现接口定义的方法。
@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);
}
}
创建BookService接口,定义对图书信息的增删改查和分页查询方法。
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);
}
创建BookServiceImpl类,实现BookService接口,使用@Service注解进行标识。
在BookServiceImpl中注入CategoryRepository和BookRepository,并实现接口定义的方法。
@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.创建控制器(Controller)
创建CategoryController类,使用@Controller注解进行标识。
在CategoryController中注入CategoryService,并实现与分类相关的HTTP请求处理方法,包括查询所有分类、添加分类、查询分类、删除分类等操作。
@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";
}
}
创建BookController类,使用@Controller注解进行标识。
在BookController中注入BookService,并实现与图书相关的HTTP请求处理方法,包括查询图书、添加图书、查询分类下的图书等操作。
@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.创建页面模板
使用Thymeleaf或其他模板引擎创建HTML页面模板,包括分类列表、图书列表、添加/编辑分类的表单、添加/编辑图书的表单等。
7.配置数据库连接
在application.properties(或yml)文件中配置数据库连接的相关信息,如数据库URL、用户名和密码等。
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. 运行启动应用程序
启动应用程序,访问相应的URL,进行分类和图书管理的操作。
总结
在本学期的期末项目中,我有机会独立完成一个Web项目,这对我来说是个非常有价值和挑战性的经历。通过这个项目,我不仅学习到了正确的代码规范,还掌握了Git的操作过程和团队协作中的代码管理技巧。
此外,我还深入学习了Java的数据结构,二进制IO,多线程和流式编程,这为我编写更高效、并发和灵活的程序提供了强大的基础。
最后,老师教授了项目分层开发的方法,我选择了使用Spring Boot、JPA、Thymeleaf、MySQL和Bootstrap来构建我的Web项目。这些技术组合使我能够轻松地实现项目的各个层面,包括数据持久化、业务逻辑处理、前端展示和用户交互。
通过这个独立完成的期末项目,我不仅加深了对技术知识的理解和应用,还提高了自己的解决问题和独立开发的能力。这将对我未来的职业发展产生积极的影响。
我对自己在本学期的学习成果感到非常满意,并期待将来能继续扩展我的技术知识和项目经验。