最终效果
接下来展示的就是我们这次要做成的效果图(并且我们这次的主要目的是学会前后端的数据互通,也就是说将后端的数据通过前后端代码写到前端去,并且显示在页面上,我们这次并不是为了完成这个业务,而是继续mvc的学习)



阅读提示
后面的代码是一层一层嵌套着来阅读的,每一个代码中都会先new下一个类,然后我们就需要来看看下一个类中的代码写的是什么,然后最后我们调转回头来进行理解
用户登录页面前端代码
我们这个网页先是登录的页面,所以我们也从这里讲起
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<div class="container-login">
<div class="container-pic">
<img src="pic/computer.png" width="350px">
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script>
function login() {
var name = $("#userName").val().trim();
var password = $("#password").val();
if (!name || !password.trim()) {
alert("请输入用户名和密码!");
return;
}
$.ajax({
type: "post",
url: "/user/login",
data: {
name: name,
password: password
},
dataType: "json",
success: function(result) {
if (result === true) {
location.href = "book_list.html";
} else {
alert("账号或密码不正确!");
}
},
error: function(xhr) {
alert("登录请求失败,状态码:" + xhr.status);
}
});
}
</script>
</body>
</html>
我们主要看script中的内容,这个主要是我的逻辑部分,我们通过前两行来获取到输入框中的名字以及密码,并且判断内容的正确性,然后依旧通过ajax的方式,将名字以及密码通过JSON的格式传给后端,并且等待后端返回,然后我们来看看后端用于接收用户信息的接口怎么写的
判断用户信息的后端接口
java
package com.example.spring_book2_demo_fixed.controller;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public boolean login(
@RequestParam("name") String name,
@RequestParam("password") String password,
HttpSession session) {
if (!StringUtils.hasText(name)
|| !StringUtils.hasText(password)) {
return false;
}
if ("admin".equals(name)
&& "admin".equals(password)) {
session.setAttribute("userName", name);
return true;
}
return false;
}
}
前面是前端登录页将数据传个后端并且等待后端返回相应,如果信息正确就返回true否则返回false,然后现在再回头看刚刚前端代码的后面部分,如果返回的数据是true,则进行跳转,也就是location.href来进行跳转,错误的话则进行弹窗,那我们接下来就来看跳转的界面的前面代码
跳转后的图书列表展示的前端代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书列表展示</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/list.css">
<!-- 引入 jQuery、Bootstrap 和分页插件 -->
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
<h2>图书列表展示</h2>
<div class="navbar-justify-between">
<div>
<button class="btn btn-outline-info"
type="button"
onclick="location.href='book_add.html'">
添加图书
</button>
<button class="btn btn-outline-info"
type="button"
onclick="batchDelete()">
批量删除
</button>
</div>
</div>
<!-- 图书列表 -->
<table>
<thead>
<tr>
<td>选择</td>
<td class="width100">图书ID</td>
<td>书名</td>
<td>作者</td>
<td>数量</td>
<td>定价</td>
<td>出版社</td>
<td>状态</td>
<td class="width200">操作</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="demo">
<ul id="pageContainer"
class="pagination justify-content-center">
</ul>
</div>
<script>
// 页面加载到这里后,调用方法获取图书列表
getBookList();
// 通过 AJAX 请求后端,获取图书数据
function getBookList() {
$.ajax({
type: "get",
url: "/book/getList",
success: function (result) {
console.log(result);
if (result != null) {
var finalHtml = "";
// 遍历后端返回的每一本书
for (var book of result) {
finalHtml += '<tr>';
// 复选框
finalHtml += '<td><input type="checkbox" name="selectBook" value="'
+ book.id
+ '" id="selectBook" class="book-select"></td>';
// 图书ID
finalHtml += '<td>' + book.id + '</td>';
// 书名
finalHtml += '<td>' + book.bookName + '</td>';
// 作者
finalHtml += '<td>' + book.author + '</td>';
// 数量
finalHtml += '<td>' + book.count + '</td>';
// 定价
finalHtml += '<td>' + book.price + '</td>';
// 出版社
finalHtml += '<td>' + book.publish + '</td>';
// 图书状态
finalHtml += '<td>' + book.statusCN + '</td>';
// 操作列:保留修改和删除入口
finalHtml += '<td><div class="op">';
finalHtml += '<a href="book_update.html?bookId='
+ book.id + '">修改</a>';
finalHtml += '<a href="javascript:void(0)" onclick="deleteBook('
+ book.id + ')">删除</a>';
finalHtml += '</div></td>';
finalHtml += '</tr>';
}
// 将生成的所有图书行放入 tbody
$("tbody").html(finalHtml);
}
}
});
}
//分页设置
$("#pageContainer").jqPaginator({
totalCounts: 100,
pageSize: 10,
visiblePages: 5,
currentPage: 1,
first: '<li class="page-item"><a class="page-link">首页</a></li>',
prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页</a></li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页</a></li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页</a></li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}</a></li>',
// 初始化分页或点击页码时执行
onPageChange: function (page, type) {
console.log("第" + page + "页,类型:" + type);
}
});
// 注意:真正删除数据还需要后端提供对应接口
function deleteBook(id) {
var isDelete = confirm("确认删除?");
if (isDelete) {
$.ajax({
type: "post",
url: "/book/deleteBook",
data: {
id: id,
status: 0
},
success: function (result) {
if (result == "") {
alert("删除成功!");
location.assign("book_list.html");
} else {
alert("删除失败!" + result);
}
}
});
}
}
// 当前不实现批量删除的后端业务
function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
alert("当前已获取选中的图书ID,批量删除功能尚未完成。");
}
}
</script>
</div>
</body>
</html>
这次在看script之前,我们先来看看table标签里面的内容,table标签里面的是先把图中的第一行给生成出来,就是下面这个一系列的表头,但是两个tbody中没有内容,是因为我们tbody中要写的是具体的书的内容,虽然我们没有引入数据库的内容,但是我们也需要暂时先将这一部分空出来,在后面script中的部分进行添加,这里面你会发现好多class以及id但是仔细看又没有实际的内容,这是因为我们后续可能会用到这一块的内容,我们先将这一块地方命一个名,后续需要用到的时候我们通过这个名字来找到这一块地,然后我们现在来看script中的内容,依旧通过ajax的方式,等待从后端的数据,这次我们没有从前端传入数据,而是直接等待后端返回数据,然后进行一个判断,我们先不关心这个后端是怎么工作的,因为这里的代码比较长,所以我们先将这个前端的内容讲完,这里后端返回的其实是一个列表,并且里面的元素就是每一本书的内容,并且是以JSON的格式传过来的,console.log是打印我们传来的数据,可以通过f12的方式进行查看,可以方便我们改错的时候找到对应的位置,然后就是复写框,先定义了这个框的类型名字,value(value是根据我们的书本的id来的,这样就可以方便看我们勾选的是那一本书了),然后最后就是id跟class属性,接下来就是每一本书的每一个内容一个一个拼接到我们的页面上面,这里你还可以看到<td><tr>这样的标签,那这些标签是干嘛用的呢
我们先认识三个 HTML 标签:
| 标签 | 作用 | 你可以理解为 |
|---|---|---|
<table> |
创建表格 | 整张表格 |
<tr> |
创建一行 | 表格的一整行 |
<td> |
创建一个单元格 | 行中的一个格子 |
拼接完成之后我们就可以将这些内容加回刚刚特地空出来的tbody中了,然后下面的是分页以及删除,我们这次的案例中暂时还不涉及到这一块的业务,所以只是简单的空壳代码,接下来我们来看跟图书列表页面连接的后端代码,也就是我们的书本接口,依旧通过url来找到对应的接口,这里拿到了具体的十本书的全部内容,然后在拼接的时候就是将具体的内容加进去,然后完成我们的网页

后端书本接口的代码
java
package com.example.spring_book2_demo_fixed.controller;
import com.example.spring_book2_demo_fixed.entity.BookInfo;
import com.example.spring_book2_demo_fixed.service.BookService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/book")
public class BookController {
@GetMapping("/getList")
public List<BookInfo> getList() {
BookService bookService = new BookService();
return bookService.getBookList();
}
}
这里我们是new了一个BookService,那我们就来看看BookService这个类中是怎么写的,我们这里拿到了最终版的完整的十本书的数据,然后将这个列表进行返回,并且是JSON格式的,因为有@RestController这个注解,就是将我们的对象通过JSON的格式输出
书本业务后端代码(BookService)
java
package com.example.spring_book2_demo_fixed.service;
import com.example.spring_book2_demo_fixed.dao.BookDao;
import com.example.spring_book2_demo_fixed.entity.BookInfo;
import java.util.List;
public class BookService {
public List<BookInfo> getBookList() {
BookDao bookDao = new BookDao();
List<BookInfo> books = bookDao.mockData();
for (BookInfo book : books) {
if (Integer.valueOf(1).equals(book.getStatus())) {
book.setStatusCN("可借阅");
} else {
book.setStatusCN("不可借阅");
}
}
return books;
}
}
这里先是new了一个BookDao,那我们依旧来看看这个BookDao类中是什么内容,我们这里拿到了十本书的具体内容的一个列表,然后我们将状态为1的定义为可借阅,其他的则是不可借阅
书本数据后端代码(BookDao)
java
package com.example.spring_book2_demo_fixed.dao;
import com.example.spring_book2_demo_fixed.entity.BookInfo;
import java.util.List;
import java.util.ArrayList;
import java.math.BigDecimal;
public class BookDao {
public List<BookInfo> mockData() {
List<BookInfo> books = new ArrayList<>();
for (int i = 0; i < 10; i++) {
BookInfo book = new BookInfo();
book.setId(i + 1);
book.setBookName("书籍" + (i + 1));
book.setAuthor("作者" + (i + 1));
book.setCount(i * 5 + 3);
book.setPrice(BigDecimal.valueOf(20 + i * 10));
book.setPublish("出版社" + (i + 1));
book.setStatus(1);
books.add(book);
}
return books;
}
}
这里依然new了一个BookInfo,那我们再来看看BookInfo这个类中写的什么,这个里面写的是书本中具体有的内容,然后我们这里是给十本书通过比较人机一点的方式添加每本书的属性,然后返回装有十本书的一个列表,但是这里我们的状态还是数字
书本定义类后端代码(BookInfo)
java
package com.example.spring_book2_demo_fixed.entity;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class BookInfo {
// 图书ID
private Integer id;
// 图书名称
private String bookName;
// 作者
private String author;
// 图书数量
private Integer count;
// 图书价格
private BigDecimal price;
// 出版社
private String publish;
// 图书状态:1可借阅,其他不可借阅
private Integer status;
// 图书状态的中文描述
private String statusCN;
// 创建时间
private Date createTime;
// 修改时间
private Date updateTime;
}
这里就是定义了一本书的相关属性,有id名字之类的,这里有一个比较有趣的地方就是图书状态有两个,一个是中文的一个是数字的,因为我们的图书状态是变的,可能是可借阅的也可能是不可借阅的,所以我们通过数字状态来修改我们的中文命名
最后是一些其他的前端内容,比如图片之类的
书本更新的前端代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改图书</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/add.css">
</head>
<body>
<div class="container">
<div class="form-inline">
<h2 style="text-align: left; margin-left: 10px;"><svg xmlns="http://www.w3.org/2000/svg" width="40"
fill="#17a2b8" class="bi bi-book-half" viewBox="0 0 16 16">
<path
d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" />
</svg>
<span>修改图书</span>
</h2>
</div>
<form id="updateBook">
<input type="hidden" class="form-control" id="bookId" name="id">
<div class="form-group">
<label for="bookName">图书名称:</label>
<input type="text" class="form-control" id="bookName" name="bookName">
</div>
<div class="form-group">
<label for="bookAuthor">图书作者</label>
<input type="text" class="form-control" id="bookAuthor" name="author"/>
</div>
<div class="form-group">
<label for="bookStock">图书库存</label>
<input type="text" class="form-control" id="bookStock" name="count"/>
</div>
<div class="form-group">
<label for="bookPrice">图书定价:</label>
<input type="number" class="form-control" id="bookPrice" name="price">
</div>
<div class="form-group">
<label for="bookPublisher">出版社</label>
<input type="text" id="bookPublisher" class="form-control" name="publish"/>
</div>
<div class="form-group">
<label for="bookStatus">图书状态</label>
<select class="custom-select" id="bookStatus" name="status">
<option value="1" selected>可借阅</option>
<option value="2">不可借阅</option>
</select>
</div>
<div class="form-group" style="text-align: right">
<button type="button" class="btn btn-info btn-lg" onclick="update()">确定</button>
<button type="button" class="btn btn-secondary btn-lg" onclick="javascript:history.back()">返回</button>
</div>
</form>
</div>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
function update() {
alert("更新成功");
location.href = "book_list.html"
}
</script>
</body>
</html>
书本添加的前端代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>添加图书</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/add.css">
</head>
<body>
<div class="container">
<div class="form-inline">
<h2 style="text-align: left; margin-left: 10px;"><svg xmlns="http://www.w3.org/2000/svg" width="40"
fill="#17a2b8" class="bi bi-book-half" viewBox="0 0 16 16">
<path
d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" />
</svg>
<span>添加图书</span>
</h2>
</div>
<form id="addBook">
<div class="form-group">
<label for="bookName">图书名称:</label>
<input type="text" class="form-control" placeholder="请输入图书名称" id="bookName" name="bookName">
</div>
<div class="form-group">
<label for="bookAuthor">图书作者</label>
<input type="text" class="form-control" placeholder="请输入图书作者" id="bookAuthor" name="author" />
</div>
<div class="form-group">
<label for="bookStock">图书库存</label>
<input type="text" class="form-control" placeholder="请输入图书库存" id="bookStock" name="count"/>
</div>
<div class="form-group">
<label for="bookPrice">图书定价:</label>
<input type="number" class="form-control" placeholder="请输入价格" id="bookPrice" name="price">
</div>
<div class="form-group">
<label for="bookPublisher">出版社</label>
<input type="text" id="bookPublisher" class="form-control" placeholder="请输入图书出版社" name="publish" />
</div>
<div class="form-group">
<label for="bookStatus">图书状态</label>
<select class="custom-select" id="bookStatus" name="status">
<option value="1" selected>可借阅</option>
<option value="2">不可借阅</option>
</select>
</div>
<div class="form-group" style="text-align: right">
<button type="button" class="btn btn-info btn-lg" onclick="add()">确定</button>
<button type="button" class="btn btn-secondary btn-lg" onclick="javascript:history.back()">返回</button>
</div>
</form>
</div>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
function add() {
alert("添加成功");
location.href = "book_list.html";
}
</script>
</body>
</html>
前端的全部内容将在文章前面的资源里可以进行下载,并且主要的目录设置如下(分层来设计)

为什么要进行分层设计?
在开发 Spring Web MVC 图书管理系统时,我们没有把所有代码都写在一个类中,而是按照不同的职责,将代码划分为 Controller、Service、Dao 和 Entity 四个部分。这种组织方式就是分层设计。
1. 四个部分分别负责什么?
-
Controller(控制层):负责接收前端发送的请求,调用业务方法,并将处理结果返回给前端。
-
Service(业务层):负责处理具体的业务逻辑,例如查询图书、添加图书、删除图书等。
-
Dao(数据访问层):负责与数据库进行交互,执行数据查询、添加、修改和删除等操作。
-
Entity(实体类) :负责定义数据对象,例如
BookInfo类,用于描述一本图书具有哪些属性,如图书 ID、书名、作者等。
2. 为什么要采用四层结构?
(1)职责清晰,降低代码耦合度
每个部分都有自己的工作,不需要把接收请求、处理业务和操作数据库全部写在一起。当某一部分发生变化时,通常只需要修改对应的代码,减少对其他部分的影响。
(2)方便维护和排查问题
当程序出现问题时,可以根据问题所在的环节进行排查。例如,前端请求没有正确返回数据,可以先检查 Controller;业务处理结果不正确,可以检查 Service;数据库查询出现问题,则可以检查 Dao。
(3)提高代码的复用性
同一个 Service 方法可以被不同的 Controller 方法调用,而不需要重复编写相同的业务逻辑。同样,Dao 中的数据访问方法也可以供多个业务方法使用。
(4)方便后续扩展
随着图书管理系统功能增加,例如加入图书分类、借阅记录和用户管理,我们可以在现有结构上扩展对应的业务代码,而不必将所有代码混在一起。
3. 四个部分是如何协作的?
以查询图书列表为例,程序的调用过程如下:
前端页面 → Controller → Service → Dao → 数据库
数据库查询完成后,数据再通过相应的调用链返回给 Controller,最终由 Controller 将结果交给前端展示。
其中,Entity 用于描述和承载图书数据,在各部分之间传递时可以使用相应的实体对象。
总结: 分层设计的核心目的不是单纯地把代码放进不同的文件夹,而是让每部分代码各司其职,使程序更容易理解、维护、复用和扩展。这也是我们在学习 Spring Web MVC 时,需要逐步建立的重要开发习惯。