目录
一、简介
本文介绍了如何使用Jpa和Thymelaf创建一个增删改查的示例。
让我首先和你们谈谈为什么我喜欢写这种类型的脚手架项目。当我学习一项新技术时,我总是想快速构建一个演示来尝试它的效果。演示越简单,就越容易上手,而且效果最好。在网上搜索相关信息时,总是很麻烦。有些文章写得很好,但没有源代码,而另一些文章有源代码,但文章介绍不太清楚,这使得查找信息有点困难。因此,当我学习Spring Boot时,我会写一些最简单和基本的示例项目。一方面,方便其他朋友以最快的方式了解它。另一方面,如果我的项目需要使用相关技术,我可以在这个示例版本中直接修改或集成它。
现在有很多类型的技术博客,有些喜欢分析源代码,而另一些则倾向于关注基本原理。我最喜欢的是写一些小而漂亮的例子,方便自己和他人。
什么是三层架构?
三层架构是指:视图层View、业务逻辑层Service、数据访问层DAO。
他们分别完成不同的功能。
View层:用于接收用户提交请求的代码
Service层:系统的业务逻辑主要在这里完成
DAO层:直接操作数据库的代码
为了更好的降低各层之间的耦合度,系统的复杂度,在三层架构程序设计中,采用面向抽象编程。即上层对下层的调用,是通过接口实现的。而下层对上层的真正服务提供者,是下层接口的实现类。服务标准(接口)是相同的,服务提供者(实现类)可以更换。 这就实现了层间解耦合。 发生在哪一层的变化,只需更改该层,不需要更改整个系统。层次清晰,分工明确,每层之间耦合度低——提高了效率,适应需求变化,可维护性高,可扩展性高。
Controller只负责管理,而Service负责实施。
Controller是管理业务调度和管理跳转的。
Service是管理具体的功能的。
DAO只完成增删改查,虽然可以1-n,n-n,1-1关联,模糊、动态、子查询都可以。但是无论多么复杂的查询,dao只是封装增删改查。至于增删查改如何去实现一个功能,dao是不管的。
dao层:数据访问层
dao层属于一种比较底层,比较基础的操作,具体到对于某个表的增删改查,也就是说某个DAO一定是和数据库的某一张表一一对应的,其中封装了增删改查基本操作,建议DAO只做原子操作,增删改查。
负责与数据库进行联络的一些任务都封装在此,dao层的设计首先是设计dao层的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可以再模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,dao层的数据源配置,以及有关数据库连接参数都在Spring配置文件中进行配置。
service层:服务层
粗略的理解就是对一个或多个DAO进行的再次封装,封装成一个服务,所以这里也就不会是一个原子操作了,需要事物控制。
service层主要负责业务模块的应用逻辑应用设计。同样是首先设计接口,再设计其实现类,接着再Spring的配置文件中配置其实现的关联。这样我们就可以在应用中调用service接口来进行业务处理。service层的业务实,具体要调用已经定义的dao层接口,封装service层业务逻辑有利于通用的业务逻辑的独立性和重复利用性。程序显得非常简洁。
controller层
Controler负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面。
controller层负责具体的业务模块流程的控制,在此层要调用service层的接口来控制业务流程,控制的配置也同样是在Spring的配置文件里进行,针对具体的业务流程,会有不同的控制器。我们具体的设计过程可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块。这样不仅使程序结构变得清晰,也大大减少了代码量。
二、配置文件
pom.xml 包里面添加 Jpa 和 Thymeleaf 还有SpringBoot的相关包引用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>s05-mvc-employee</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>demo</description>
<properties>
<java.version>20</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>true</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
在application.properties中添加配置
注意数据库和用户名密码需要更改
spring.datasource.url=jdbc:mysql://localhost:3306/gdka?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
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto= update
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
三、代码的实现过程
1.启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2.数据库层代码
在model层下创建一个图书对象实体类映射数据库表
package com.example.demo.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
//图书对象实体
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Entity
@Table(name = "books")//数据库表
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "Name")
private String name; //图书名
@Column(name = "Author")
private String author; //作者
@Column(name = "Publisher")
private String publisher; //出版社
@Column(name = "BookType")
private String bookType; //图书类型
@Column(name = "Price")
private float price; //价格
}
3.继承 JpaRepository 类
repository层下创建一个继承 JpaRepository 类的BookRepository接口内置的方法可以在里面实现,包括增删改查。
package com.example.demo.repository;
import com.example.demo.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findByAuthor(String author);
}
4.业务层处理
service层下创建图书业务类和图书接口,在实体类中添加方法实现,服务调用Jpa来实现相关的添加、删除、修改和查询。在实际项目中,服务层处理特定的业务代码。封装service层业务逻辑有利于通用的业务逻辑的独立性和重复利用性,程序显得非常简洁。
package com.example.demo.service.impl;
import com.example.demo.model.Book;
import com.example.demo.repository.BookRepository;
import com.example.demo.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
//业务层
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookRepository bookRepository;
//获取所有图书
@Override
public List<Book> getAllBook() {
return bookRepository.findAll();
}
//新增图书
@Override
public void addBook(Book book) {
this.bookRepository.save(book);
}
//更新图书
@Override
public void updateBook(Book book) {
bookRepository.saveAndFlush(book);
}
//获取指定id的图书
@Override
public Book getBookById(long id) {
//调用数据访问层查找指定id的图书,返回Optional对象
Optional<Book> optional = bookRepository.findById(id);
Book book = null;
if (optional.isPresent()){ //如果指定的id存在
book = optional.get(); //从Optional对象中获取图书
}else { //否则提示找不到图书
throw new RuntimeException("找不到图书id :" + id);
}
return book;
}
//删除指定id的图书
@Override
public void deleteBookById(long id) {
this.bookRepository.deleteById(id);
}
//分页
@Override
public Page<Book> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) {
//设置排序参数,升序ASC;降序DESC
Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name())
? Sort.by(sortField).ascending()
: Sort.by(sortField).descending();
//根据页号/每页记录数/排序依据返回某指定页面数据
Pageable pageable = PageRequest.of(pageNo - 1,pageSize, sort);
return this.bookRepository.findAll(pageable);
}
//获取指定的作者
@Override
public Book findByAuthor(String author) {
return (Book) bookRepository.findByAuthor(author);
}
}
4.1 BookService接口
service层下的BookService接口对象的方法定义
package com.example.demo.service;
import com.example.demo.model.Book;
import com.example.demo.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import java.util.List;
import java.util.Optional;
public interface BookService {
//获取所有图书
List<Book> getAllBook();
//新增图书
void addBook(Book book);
//更新图书
void updateBook(Book book);
//获取指定id的图书
Book getBookById(long id);
//删除指定id的图书
void deleteBookById(long id);
//分页
Page<Book> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection);
//获取指定的作者的图书
Book findByAuthor(String author);
}
5.Controller层中添加接受请求的方法
Controller 负责接收请求,处理完后将页面内容返回给前端。
package com.example.demo.controller;
import com.example.demo.model.Book;
import com.example.demo.repository.BookRepository;
import com.example.demo.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
public class BookController {
@Autowired
private BookService bookService;
//首页
@GetMapping("/")
public String index(Model model){
return findPaginated(1,"name","asc", model);
}
//新增图书页
@GetMapping("/showNewBookForm")
public String showNewBookForm(Model model){
//创建模型属性来绑定表单数据
Book book = new Book();
model.addAttribute("book",book);
return "increase_book";
}
//新增图书页
@PostMapping("/addBook")
public String addBook(@ModelAttribute("book") Book book){
//将图书保存到数据库
bookService.addBook(book);
return "redirect:/";
}
//更新图书信息页
@GetMapping("/updateBook/{id}")
public String updateBook(@PathVariable(value = "id") long id, Model model){
//从BookService中获取员工
Book book = bookService.getBookById(id);
//将book设置为模型属性预填充表单
model.addAttribute("book", book);
return "update_book";
}
//删除指定id的图书
@GetMapping("/deleteBook/{id}")
public String deleteBook(@PathVariable(value = "id") long id){
this.bookService.deleteBookById(id);
return "redirect:/";
}
//获取分页数据
@GetMapping("/page/{pageNo}")
public String findPaginated(@PathVariable(value = "pageNo") int pageNo, //页数
@RequestParam("sortField") String sortField, //排序的字段
@RequestParam("sortDir") String sortDir, //排序的方式,升序或降序
Model model){
int pageSize = 7; //每页7条数据
Page<Book> page = bookService.findPaginated(pageNo, pageSize, sortField, sortDir);
List<Book> listBook = page.getContent();
model.addAttribute("currentPage", pageNo);
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("sortField", sortField);
model.addAttribute("sortDir", sortDir);
model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc");
model.addAttribute("listBook",listBook);
return "index";
}
@Autowired
private BookRepository bookRepository;
//获取所有书
@GetMapping("/books")
public String getBooks(Model model){
List<Book> books = bookRepository.findAll();
model.addAttribute("books",books);
return "search";
}
// //查询指定的作者
@PostMapping("/books/search")
public String searchBook(@RequestParam("author") String author, Model model) {
List<Book> books = bookRepository.findByAuthor(author);
model.addAttribute("books",books);
return "search";
}
}
四、网页搭建
1.主页面搭建
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>图书管理系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
</head>
<body>
<div class="container my-2">
<h1 class="text-center text-success">图书管理系统</h1>
<form class="form-inline" th:action="@{/books/search}" method="post">
<div class="form-group mb-2">
<a th:href="@{/showNewBookForm}" class="btn btn-primary mb-2">添加图书信息</a>
</div>
<div class="form-group mx-sm-3 mb-2">
<input type="text" class="form-control" name="author" placeholder="作者">
</div>
<button type="submit" class="btn btn-info mb-2" style="width:100px">查询</button>
</form>
<table border="1" class="table table-striped table-responsive-md table-active">
<thead class="thead-light">
<tr>
<th><a th:href="@{'/page/' + ${currentPage} + '?sortField=id&sortDir=' + ${reverseSortDir}}">id</a></th>
<th width="150"><a th:href="@{'/page/' + ${currentPage} + '?sortField=name&sortDir=' + ${reverseSortDir}}">书名</a></th>
<th width="200"><a th:href="@{'/page/' + ${currentPage} + '?sortField=author&sortDir=' + ${reverseSortDir}}">作者</a></th>
<th width="200"><a th:href="@{'/page/' + ${currentPage} + '?sortField=publisher&sortDir=' + ${reverseSortDir}}">出版社</a></th>
<th width="200"><a th:href="@{'/page/' + ${currentPage} + '?sortField=bookType&sortDir=' + ${reverseSortDir}}">图书类型</a></th>
<th><a th:href="@{'/page/' + ${currentPage} + '?sortField=price&sortDir=' + ${reverseSortDir}}">价格</a></th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${listBook}">
<td th:text="${book.id}"></td>
<td th:text="${book.name}"></td>
<td th:text="${book.author}"></td>
<td th:text="${book.publisher}"></td>
<td th:text="${book.bookType}"></td>
<td th:text="${book.price}"></td>
<td>
<a th:href="@{/updateBook/{id}(id=${book.id})}" class="btn btn-warning btn-xs">修改</a>
<a th:href="@{/deleteBook/{id}(id=${book.id})}" class="btn btn-success btn-xs">删除</a>
</td>
</tr>
</tbody>
</table>
<div th:if = "${totalPages > 1}">
<div class = "row col-sm-10" >
<div class = "col-sm-3">
Total Rows: [[${totalItems}]]
</div>
<div class="pagination justify-content-end" >
<span th:each="i: ${#numbers.sequence(1, totalPages)}" class="page-item">
<a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}" class="page-link">[[${i}]]</a>
<span th:unless="${currentPage != i}" class="page-link">[[${i}]]</span>
</span>
</div>
<div >
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}" class="page-link">下一页</a>
<span th:unless="${currentPage < totalPages}" class="page-link">下一页</span>
</div>
<div >
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}" class="page-link">最后一页</a>
<span th:unless="${currentPage < totalPages}" class="page-link">最后一页</span>
</div>
</div>
</div>
</div>
</body>
</html>
2.查询书籍的跳转页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>查询图书</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
</head>
<body>
<div class="container my-2">
<h1 class="text-center text-success">图书管理系统</h1>
<form class="form-inline" th:action="@{/books/search}" method="post">
<div class="form-group mb-2">
<a th:href="@{/showNewBookForm}" class="btn btn-primary mb-2">添加图书信息</a>
</div>
<div class="form-group mx-sm-3 mb-2">
<input type="text" class="form-control" name="author" placeholder="作者">
</div>
<button type="submit" class="btn btn-info mb-2" style="width:100px">查询</button>
<a type="submit" class="btn btn-primary mb-2" th:href="@{/}">重置</a>
</form>
<table border="1" class="table table-striped table-responsive-md table-active">
<thead class="thead-light">
<tr>
<th><a th:href="@{'/page/' + ${currentPage} + '?sortField=id&sortDir=' + ${reverseSortDir}}">id</a></th>
<th width="150"><a th:href="@{'/page/' + ${currentPage} + '?sortField=name&sortDir=' + ${reverseSortDir}}">书名</a></th>
<th width="200"><a th:href="@{'/page/' + ${currentPage} + '?sortField=author&sortDir=' + ${reverseSortDir}}">作者</a></th>
<th width="200"><a th:href="@{'/page/' + ${currentPage} + '?sortField=publisher&sortDir=' + ${reverseSortDir}}">出版社</a></th>
<th width="200"><a th:href="@{'/page/' + ${currentPage} + '?sortField=bookType&sortDir=' + ${reverseSortDir}}">图书类型</a></th>
<th><a th:href="@{'/page/' + ${currentPage} + '?sortField=price&sortDir=' + ${reverseSortDir}}">价格</a></th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${books}">
<td th:text="${book.id}"></td>
<td th:text="${book.name}"></td>
<td th:text="${book.author}"></td>
<td th:text="${book.publisher}"></td>
<td th:text="${book.bookType}"></td>
<td th:text="${book.price}"></td>
<td>
<a th:href="@{/updateBook/{id}(id=${book.id})}" class="btn btn-warning btn-xs">修改</a>
<a th:href="@{/deleteBook/{id}(id=${book.id})}" class="btn btn-success btn-xs">删除</a>
</td>
</tr>
</tbody>
</table>
<div th:if = "${totalPages > 1}">
<div class = "row col-sm-10">
<div class = "col-sm-3">
Total Rows: [[${totalItems}]]
</div>
<div class = "col-sm-5">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span>
</span>
</div>
<div class = "col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">下一页</a>
<span th:unless="${currentPage < totalPages}">下一页</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">最后一页</a>
<span th:unless="${currentPage < totalPages}">最后一页</span>
</div>
</div>
</div>
</div>
</body>
</html>
3.添加图书的页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>添加图书</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1>添加图书页面</h1>
<hr>
<h2>添加图书</h2>
<form action="#" th:action="@{/addBook}" th:object="${book}" method="POST">
<input type="text" th:field="*{name}" placeholder="书名" class="form-control mb-4 col-4">
<input type="text" th:field="*{author}" placeholder="作者" class="form-control mb-4 col-4">
<input type="text" th:field="*{publisher}" placeholder="出版社" class="form-control mb-4 col-4">
<input type="text" th:field="*{bookType}" placeholder="图书类型" class="form-control mb-4 col-4">
<input type="text" th:field="*{price}" placeholder="价格" class="form-control mb-4 col-4">
<button type="submit" class="btn btn-info col-2">添加图书</button>
</form>
<hr>
<a th:href="@{/}">返回图书列表</a>
</div>
</body>
</html>
4.修改图书页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>修改图书</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1>修改图书信息页面</h1>
<hr>
<h2>修改信息</h2>
<form action="#" th:action="@{/addBook}" th:object="${book}" method="POST">
<input type="hidden" th:field="*{id}" />
<input type="text" th:field="*{name}" placeholder="书名" class="form-control mb-4 col-4">
<input type="text" th:field="*{author}" placeholder="作者" class="form-control mb-4 col-4">
<input type="text" th:field="*{publisher}" placeholder="出版社" class="form-control mb-4 col-4">
<input type="text" th:field="*{bookType}" placeholder="图书类型" class="form-control mb-4 col-4">
<input type="text" th:field="*{price}" placeholder="价格" class="form-control mb-4 col-4">
<button type="submit" class="btn btn-info col-2">修改图书信息</button>
</form>
<a th:href="@{/}">返回首页</a>
</div>
</body>
</html>
五、总结
以上就是我用java+Springboot +JPA+thymeleaf+MySQL技术框架来创建图书管理系统的全部过程。虽然这些技术比较过时,语法很冗余,Thymeleaf其实就是一个模块引擎 直接套就好了 Jpa 是spring家族自己的持久层框架,可以理解为类似于mybatis,但是本人的学识比较浅还掌握不了这门技术,如果能实现Springboot+vue+mybatis+mysql技术来创建我的图书管理系统就很好。