前提条件:
具备基础的springboot 知识
Java基础
废话不多说!
创建项目
配置所需环境
将application.properties==>application.yml 配置以下环境
数据库连接MySQL
自己创建的数据库名称为book_test
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/book_test?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mvc:
hiddenmethod:
filter:
enabled: true
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#一直出现的问题:引入依赖包的问题
追加两个依赖使得可以使用mapper-locations
数据库准备
图书以及简单的用户信息
CREATE TABLE `book_info` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`book_name` VARCHAR(127) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
`author` VARCHAR(127) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
`count` INT(10) NULL DEFAULT NULL,
`price` DECIMAL(7,2) NOT NULL,
`publish` VARCHAR(256) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
`status` TINYINT(3) NULL DEFAULT '1' COMMENT '0-⽆效, 1-正常, 2-不允许借阅',
`create_time` DATETIME NULL DEFAULT 'CURRENT_TIMESTAMP',
`update_time` DATETIME NULL DEFAULT 'CURRENT_TIMESTAMP' ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
AUTO_INCREMENT=25
;
CREATE TABLE `user_info` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(128) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
`password` VARCHAR(128) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
`delete_flag` TINYINT(3) NULL DEFAULT '0',
`create_time` DATETIME NULL DEFAULT 'CURRENT_TIMESTAMP',
`update_time` DATETIME NULL DEFAULT 'CURRENT_TIMESTAMP' ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `user_name_UNIQUE` (`user_name`) USING BTREE
)
COMMENT='用户表'
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3
;
spring MVC 运行逻辑
Mapper ==> Service ==> Controller
mapper接口层代码:
@Insert("INSERT INTO book_info (book_name, author, count, price, publish, status, create_time, update_time) VALUES " +
"(#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status}, #{createTime}, #{updateTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertBook(BookInfo book);
@Select("SELECT id, book_name AS bookName, author, count, price, publish, status, " +
"CASE status WHEN 0 THEN '无效' WHEN 1 THEN '允许借阅' ELSE '不允许借阅' END AS statusCN, " +
"create_time, update_time FROM book_info WHERE id = #{id}")
@Results(id = "bookResultMap", value = {
@Result(property = "id", column = "id"),
@Result(property = "bookName", column = "bookName"),
@Result(property = "author", column = "author"),
@Result(property = "count", column = "count"),
@Result(property = "price", column = "price"),
@Result(property = "publish", column = "publish"),
@Result(property = "status", column = "status"),
@Result(property = "statusCN", column = "statusCN"),
@Result(property = "createTime", column = "create_time"),
@Result(property = "updateTime", column = "update_time")
})
BookInfo selectBookById(Integer id);
@Update("UPDATE book_info SET book_name = #{bookName}, author = #{author}, count = #{count}, price = #{price}, " +
"publish = #{publish}, status = #{status}, update_time = #{updateTime} WHERE id = #{id}")
int updateBook(BookInfo book);
@Delete("DELETE FROM book_info WHERE id = #{id}")
int deleteBook(Integer id);
@Select("SELECT * FROM book_info")
List<BookInfo> selectAllBooks();
List<BookInfo> mockData();
创建用户bookinfo
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/login")
public boolean login(String name, String password, HttpSession session){
//账号或密码为空
if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)){
return false;
}
//模拟验证数据, 账号密码正确
if("admin".equals(name) && "admin".equals(password)){
session.setAttribute("userName",name);
return true;
}
//账号密码错误
return false;
}
@Data
public class BookInfo {
//图书ID
private Integer id;
//书名
private String bookName;
//作者
private String author;
//数量
private Integer count;
//定价
private BigDecimal price;
//出版社
private String publish;
//状态 0-⽆效 1-允许借阅 2-不允许借阅
private Integer status;
private String statusCN;
//创建时间
private Date createTime;
//更新时间
private Date updateTime;
}
service层面:
/**
* 添加图书
* @param book 图书信息
* @return 操作结果,成功返回true,失败返回false
*/
boolean addBook(BookInfo book);
/**
* 根据ID查询图书
* @param id 图书ID
* @return 图书信息
*/
BookInfo getBookById(Integer id);
/**
* 更新图书信息
* @param book 图书信息
* @return 操作结果,成功返回true,失败返回false
*/
boolean updateBook(BookInfo book);
/**
* 删除图书
* @param id 图书ID
* @return 操作结果,成功返回true,失败返回false
*/
boolean deleteBook(Integer id);
/**
* 获取所有图书列表
* @return 图书列表
*/
List<BookInfo> getAllBooks();
接口层的实现
private BookInfoMapper bookMapper;
@Override
public boolean addBook(BookInfo book) {
// 可以在这里添加业务逻辑校验,例如检查图书信息是否完整
int rowsAffected = bookMapper.insertBook(book);
return rowsAffected > 0;
}
@Override
public BookInfo getBookById(Integer id) {
return bookMapper.selectBookById(id);
}
@Override
public boolean updateBook(BookInfo book) {
// 业务逻辑校验,例如确保ID不为空且存在对应的图书记录
int rowsAffected = bookMapper.updateBook(book);
return rowsAffected > 0;
}
@Override
public boolean deleteBook(Integer id) {
// 可以添加逻辑判断,比如检查图书是否已被借阅不能删除
int rowsAffected = bookMapper.deleteBook(id);
return rowsAffected > 0;
}
@Override
public List<BookInfo> getAllBooks() {
return bookMapper.selectAllBooks();
}
控制层面:
@Autowired
private BookService bookService;
// 添加图书
@PostMapping("/add")
public ResponseEntity<ResponseMessage> addBook(@RequestBody BookInfo book) {
// boolean isAdded = bookService.addBook(book);
// if (isAdded) {
// return ResponseEntity.status(200).body(book);
// } else {
// return ResponseEntity.badRequest().build();
// }
try {
boolean isAdded = bookService.addBook(book);
if (isAdded) {
// 创建ResponseMessage实例,表示成功
ResponseMessage response = new ResponseMessage(200, "图书添加成功", book);
return ResponseEntity.ok(response); // 注意这里返回的是ResponseEntity<ResponseMessage>
} else {
// 创建ResponseMessage实例,表示失败
ResponseMessage response = new ResponseMessage(400, "图书添加失败,请检查输入信息", null);
return ResponseEntity.badRequest().body(response); // 同样返回ResponseEntity<ResponseMessage>
}
} catch (Exception e) {
// 异常处理,返回错误信息
ResponseMessage response = new ResponseMessage(500, "添加图书时发生系统错误: " + e.getMessage(), null);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
// 根据ID获取图书信息
@GetMapping("/{id}")
public ResponseEntity<BookInfo> getBookById(@PathVariable Integer id) {
BookInfo book = bookService.getBookById(id);
if (book != null) {
return ResponseEntity.ok(book);
} else {
return ResponseEntity.notFound().build();
}
}
// 更新图书信息
@PutMapping("/{id}")
public ResponseEntity<Void> updateBook(@PathVariable Integer id, @RequestBody BookInfo book) {
book.setId(id); // 确保请求体中的ID与路径变量ID一致
boolean isUpdated = bookService.updateBook(book);
if (isUpdated) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
// 删除图书
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteBook(@PathVariable Integer id) {
boolean isDeleted = bookService.deleteBook(id);
if (isDeleted) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
// 获取所有图书列表
@GetMapping
public ResponseEntity<List<BookInfo>> getAllBooks() {
List<BookInfo> books = bookService.getAllBooks();
return ResponseEntity.ok(books);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public ResponseMessage(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
private int code; // 状态码,200表示成功,非200表示各种错误
private String message; // 描述信息
private Object data; // 可选,成功时返回的数据或错误时的额外信息
后端结束
前台交互
增加图书:
<!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="history.back()">返回</button>
</div>
</form>
</div>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
function add() {
// 收集表单数据
const formData = {
bookName: $("#bookName").val(),
author: $("#bookAuthor").val(),
count: $("#bookStock").val(),
price: $("#bookPrice").val(),
publish: $("#bookPublisher").val(),
status: $("#bookStatus").val()
};
// 使用Ajax发送数据到后端
$.ajax({
type: "POST",
url: "/book/add", //请替换为您的后端接口地址
contentType: "application/json; charset=utf-8",
data: JSON.stringify(formData),
success: function(response) {
if(response.code === 200 || response.code === 201) { // 假设200或201都表示成功
// alert(response.code);
alert("添加成功");
location.href = "book_list.html"; // 添加成功后跳转到列表页
} else {
alert("添加失败:" + response.message);
}
},
error: function(xhr, status, error) {
alert("添加图书时发生错误: " + error);
}
});
}
</script>
</body>
</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">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" 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 class="table">
<thead>
<tr>
<th><label for="selectAllBooks"></label><input type="checkbox" id="selectAllBooks"></th>
<th>ID</th>
<th>书名</th>
<th>作者</th>
<th>数量</th>
<th>定价</th>
<th>出版社</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="demo">
<ul id="pageContainer" class="pagination justify-content-center"></ul>
</div>
<script>
$(document).ready(function() {
getBookList();
$("#selectAllBooks").click(function() {
$("input[name='selectBook']").prop('checked', this.checked);
});
});
function getBookList() {
$.ajax({
type: "get",
url: "/book"+location.search,
success: function (result) {
console.log(result);
if (result != null) {
let finalHtml = "";
for (const book of result) {
finalHtml += '<tr>';
finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '"></td>'; // 修正了value属性的遗漏
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 + '" class="btn btn-sm btn-primary">编辑</a>'; // 修正了链接的闭合
finalHtml += '<button class="btn btn-sm btn-danger" onclick="deleteBook(' + book.id + ')">删除</button>';
finalHtml += '</div></td>';
finalHtml += '</tr>';
}
$("tbody").html(finalHtml);
}
}
});
}
function deleteBook(id) {
const isDelete = confirm("确认删除?");
if (isDelete) {
$.ajax({
type: "DELETE",
url: "/book/" + id,
success: function() {
alert("删除成功");
// 刷新页面以反映删除后的数据变化
getBookList();
location.reload();
},
error: function(xhr, status, error) {
if (xhr.status === 404) {
alert("图书不存在");
} else {
alert("删除失败: " + error);
}
}
});
}
}
function batchDelete() {
const isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
const ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
alert("批量删除成功");
}
}
</script>
</div>
</body>
</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="history.back()">返回</button>
</div>
</form>
</div>
<script type="text/javascript" src="js/jquery.min.js"></script>
<!-- HTML 表单保持不变 -->
<script>
function update() {
// 收集表单数据
const formData = {
id: location.search.split("=")[1], // 确保隐藏字段包含图书ID
bookName: $("#bookName").val(),
author: $("#bookAuthor").val(),
count: $("#bookStock").val(),
price: $("#bookPrice").val(),
publish: $("#bookPublisher").val(),
status: $("#bookStatus").val()
};
// 对id进行Base64编码
// 使用Ajax发送PUT请求到Spring Boot后端更新图书,URL中包含Base64编码的id
// 构建正确的URL,使用模板字符串动态插入ID
console.log("--------------"+formData)
const urls = `/book/${formData.id}`;
// 使用Ajax发送PUT请求到Spring Boot后端更新图书
$.ajax({
type: "PUT",
url: urls, // 使用上面构建的正确URL
contentType: "application/json; charset=utf-8",
data: JSON.stringify(formData),
success: function(response, textStatus, jqXHR) {
if(jqXHR.status === 204) {
alert("更新成功");
location.href = "book_list.html";
} else if(response.status === 'SUCCESS') {
alert("更新成功");
location.href = "book_list.html";
} else {
alert("更新失败:" + response.message);
}
},
error: function(xhr, status, error) {
alert("更新图书时发生错误: " + xhr.responseText || error);
}
});
}
</script>
</body>
</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="713" alt="">
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<label for="userName"></label><input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<label for="password"></label><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() {
$.ajax({
type: "post",
url: "/user/login",
data: {
name: $("#userName").val(),
password: $("#password").val()
},
success: function (result) {
if (result) {
location.href = "book_list.html";
} else {
alert("账号或密码不正确!");
}
}
});
}
</script>
</body>
</html>
总结:
src/main/java/bao/book_again
目录下的BookAgainApplication.java
是项目的启动类,它继承了Spring Boot的SpringBootApplication
,并使用@SpringBootApplication
注解开启自动配置和扫描。
Controller
目录下有多个控制器类,如BasicController.java
和PathVariableController.java
,它们使用Spring MVC的@RestController
或@Controller
注解,处理来自客户端的HTTP请求,并返回相应的响应。
Domain
目录下有领域模型类,如User.java
和BookInfo.java
,它们代表了应用中的实体对象。
Exception
目录下有异常处理类,如ResponseMessage.java
,可能是用于封装错误信息和响应状态的类。
Mapper
目录下有MyBatis的相关接口,如BookService.java
,它们定义了数据库操作的接口。
Service
目录下有服务层实现,如BookServiceImpl.java
,实现了业务逻辑。
resources/static
目录下有静态资源,如HTML文件和图片。
application.yml
是项目的配置文件,用于配置Spring Boot应用的各种属性。
test
目录下可能有单元测试或集成测试代码。这个项目使用了Spring Boot和Spring MVC,结合MyBatis作为持久层框架,实现了基本的CRUD操作。用户界面使用HTML模板,通过RESTful API与后端交互。整体来看,这是一个典型的MVC架构的Web应用,其中
Controller
负责处理HTTP请求,Service
层处理业务逻辑,Mapper
层处理数据库操作,Domain
层定义了领域模型,Exception
层处理异常,static
目录下存放了前端资源