Оглавление
1. Создайте проект Spring Boot.
2. Многоуровневое кодирование проекта
3. Проектируйте и создавайте классы сущностей
4. Создайте уровень доступа к данным (репозиторий).
5. Создайте сервисный слой (Service)
5. Создаем контроллер (Контроллер)
7. Настройте подключение к базе данных
8. Запустите автозагрузочное приложение.
Предисловие
В таких местах, как библиотеки и книжные магазины, важной задачей является управление большим количеством книг. Чтобы повысить эффективность и точность управления библиотекой, мы можем разработать систему управления библиотекой на основе Spring Boot и JPA. В этом блоге будет описано, как использовать эти технологии для разработки и внедрения полнофункциональной системы управления библиотекой.
Выбор технологии
В этом проекте используются следующие технологии:
- Spring Boot: быстрое создание приложений на основе Spring.
- JPA: API персистентности Java упрощает операции по сохранению данных
- Thymeleaf: движок шаблонов для рендеринга внешних страниц.
- MySQL: реляционная база данных, используемая для хранения данных, связанных с книгами.
- Bootstrap: для дизайна и верстки страниц.
Функциональные требования
Наша система управления библиотекой должна реализовывать следующие функции:
- Управление информацией о книге: включая добавление, удаление, изменение и запрос названия книги, автора, издателя и других атрибутов.
- Добавить книги: добавьте в систему информацию о новой книге.
- Удалить книгу: удалить указанную информацию о книге из системы.
- Обновить книги: изменить существующую информацию о книгах в системе.
- Поиск книг: поиск книг, соответствующих требованиям, по ключевым словам или другим условиям.
- Функция пейджинга: отображение информации о нескольких книгах на страницах для повышения эффективности просмотра.
- Функция сортировки: сортируйте информацию о книге по различным условиям для облегчения поиска и сравнения.
Системный дизайн
Мы храним информацию о книгах в базе данных MySQL и используем JPA для управления операциями с базой данных. Создавая классы сущностей и соответствующие интерфейсы репозитория, мы можем легко выполнять операции добавления, удаления, изменения и запроса. В то же время механизм шаблонов Thymeleaf используется для рендеринга интерфейсной страницы и отображения информации о книге.
Дисплей интерфейса
Этапы реализации
1. Создайте проект Spring Boot.
Создайте новый проект Spring Boot с помощью Spring Initializr (https://start.spring.io/).
Добавьте необходимые зависимости, включая 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. Создайте уровень доступа к данным (репозиторий).
Создайте интерфейс 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 для идентификации.
Вставьте CategoryRepository в CategoryServiceImpl и реализуйте методы, определенные интерфейсом.
@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 для идентификации.
Вставьте CategoryRepository и BookRepository в BookServiceImpl и реализуйте методы, определенные интерфейсом.
@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. Создаем контроллер (Контроллер)
Создайте класс CategoryController и используйте аннотацию @Controller для его идентификации.
Внедрите CategoryService в CategoryController и реализуйте методы обработки 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 для его идентификации.
Внедрите BookService в BookController и реализуйте методы обработки 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-адресу и выполните операции классификации и управления книгами.
Подведем итог
В последнем проекте этого семестра у меня была возможность самостоятельно завершить веб- проект, что было для меня очень ценным и интересным опытом. Благодаря этому проекту я не только изучил правильные стандарты кодирования, но также освоил процесс работы Git и навыки управления кодом в командной работе.
Кроме того, я также глубоко изучил структуру данных Java , двоичный ввод-вывод , многопоточность и потоковое программирование, что дало мне прочную основу для написания более эффективных, параллельных и гибких программ.
Наконец, учитель научил методу многоуровневой разработки проектов, и я решил использовать Spring Boot , JPA , Thymeleaf , MySQL и Bootstrap для создания своего веб- проекта. Такое сочетание технологий позволило мне легко реализовать все аспекты проекта, включая сохранение данных, обработку бизнес-логики, интерфейсную презентацию и взаимодействие с пользователем.
Благодаря этому независимо завершенному итоговому проекту я не только углубил свое понимание и применение технических знаний, но также улучшил свои способности решать проблемы и развиваться самостоятельно. Это окажет положительное влияние на мое дальнейшее карьерное развитие.
Я очень доволен результатами обучения в этом семестре и надеюсь продолжить расширять свои технические знания и опыт реализации проектов в будущем.