SpringBoot 圖書管理系統(tǒng)(刪除、強(qiáng)制登錄、更新圖書)詳細(xì)代碼
一、刪除圖書
1.并不使用delete語句:
- 原因:企業(yè)開發(fā)中,因?yàn)閿?shù)據(jù)就意味著金錢,所以我們不會使用delete去刪除(delete刪除是物理刪除,找不回來那種)
- delete使用場景:delete 語句通常在進(jìn)行數(shù)據(jù)修復(fù)時才會使用,比如測試人員如果要進(jìn)行測試,是需要手工造一些數(shù)據(jù)的。當(dāng)測試完畢后,這些數(shù)據(jù)就是臟數(shù)據(jù)(假數(shù)據(jù))了,是沒有任何價值的,此時就可
2.以使用delete把數(shù)據(jù)刪掉
- 刪除的分類:邏輯刪除 + 物理刪除
- 物理刪除:直接把數(shù)據(jù)刪掉
- 邏輯刪除:軟刪除/假刪除,通過字段的標(biāo)識來表示這個數(shù)據(jù)被刪除了
- 很多地方都在使用,比如軟件開發(fā)、硬盤刪除上
- 刪除時不是說把這塊內(nèi)容給清空了,而是把標(biāo)識改了,這也是我們可以找回的原因(再把標(biāo)識改回來)
- 物理刪除+存檔 或 邏輯刪除+存檔:
- 什么是存檔:
- 建一個和原表字段一致的表 ,如果原表要刪除一個數(shù)據(jù),就把這個數(shù)據(jù)移到存檔表里
- 存檔表我們大多數(shù)情況不使用,查找時是從原表里查,當(dāng)需要數(shù)據(jù)恢復(fù)時,才會從存檔表里查
- 為什么都使用邏輯刪除了,還要使用存檔表:存檔表可以認(rèn)為是一個流水表
- 什么是存檔:
3.刪除圖書功能的實(shí)現(xiàn)方法:因?yàn)槲覀兇颂幬覀兪褂眠壿媱h除,所以沒必要搞一個deleteBook,沿用修改圖書的接口updateBook即可
4.代碼:由于后端代碼updateBook已經(jīng)實(shí)現(xiàn)了,所以此處只要寫前端代碼即可(把id傳過去,status固定為0)
function deleteBook(bookId){
var isDelete = confirm("確認(rèn)刪除?");
if (isDelete){
$.ajax({
type: "post",
url: "/book/updateBook",
data: {
id: bookId,
status: 0 //接口設(shè)計中,0表示被刪除的
},
success: function (result){
if (result == ""){
location = "book_list.html";
}else{
alert(result);
}
}
});
}
}二、批量刪除
1.思路解析:因?yàn)槲覀兪褂昧诉壿媱h除,所以批量刪除就等于批量更新,但我們不能像刪除單個圖書一樣,使用updateBook,因?yàn)榇颂幬覀冃枰露鄠€,updateBook只能更新一個
2.后端代碼:
Controller 層
- @RequestParam:因?yàn)槲覀兪褂孟旅娴姆绞桨l(fā)請求,參數(shù)是在查詢字符串上。且后端設(shè)置的接口是List,默認(rèn)接收是用數(shù)組(如果使用的是數(shù)組,可以不加該注解),所以要使用@RequestParam
- @RequestParam:請求參數(shù)是查詢字符串上的參數(shù)
- @RequestBody:請求參數(shù)是body正文,需要把請求正文的內(nèi)容轉(zhuǎn)換為對象
- @ResponseBody:返回的內(nèi)容是響應(yīng)正文
- @RequestParam:關(guān)于參數(shù)的設(shè)計
- 我們可以使用各種方法去接收前端發(fā)來的參數(shù),可以設(shè)置參數(shù)在正文、url……里,接收的是數(shù)組、List、其他類……只要合乎邏輯,能完成需求即可

@RequestMapping("/batchDelete")
public String batchDeleteBook(@RequestParam List<Integer> ids){
log.info("接收到的ids:{}", ids);
Integer res = bookService.batchDeleteBook(ids);
if (res <= 0){
log.error("批量刪除失敗,ids:{}", ids);
return "失敗";
}
return "";
}Service 層
- try-catch:bookInfo.batchDelete(ids)為【主邏輯方法】,但是代碼有可能會執(zhí)行失敗,所以要【try-catch】
- 關(guān)于日志的打印:
- 可以方便我們后續(xù)找錯,因?yàn)闀小拘枨鬀]有實(shí)現(xiàn) + 一條日志都沒有的情況】,如SQL正確運(yùn)行了,但影響的行數(shù)為0
- 此時如果要排除錯誤,就需要一點(diǎn)一點(diǎn)debug,十分麻煩。有了日志,可以快速定位是哪里有問題
public Integer batchDeleteBook(@RequestParam List<Integer> ids){
Integer res = null;
try{
res = bookInfoMapper.batchDelete(ids);
}catch (Exception e){
log.error("批量刪除失敗, ids:{}", ids);
}
return res;
}Mapper 層
- 因?yàn)闀婕暗絼討B(tài)SQL,為了方便,此處使用xml來編寫
Integer batchDelete(List<Integer> ids);
<update id="batchDelete">
update book_info
set status = 0
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</update>3.前端代碼:
- input:checkbox:獲取所有的復(fù)選框
- name=‘selectBook’:復(fù)選框有很多,此時我們要獲取名字為selectBook的
- checked:表示已經(jīng)被選中的
- each(function):對每一個選中的復(fù)選框進(jìn)行某個操作
- ids.push($(this).val()):把復(fù)選框的值放到ids這個數(shù)組里
function batchDelete(){
var isDelete = confirm("確認(rèn)批量刪除?");
if (isDelete){
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function (){
ids.push($(this).val());
})
$.ajax({
type: "post",
url: "/book/batchDelete?ids=" + ids,
success:function (result){
if (result == ""){
location.href = "book_list.html";
}else{
alert(result);
}
}
});
}
}三、強(qiáng)制登錄
3.1 不使用攔截器
- 需求介紹:我們希望后端能檢查有無登錄,如果沒有登錄跳轉(zhuǎn)到登錄頁面的操作
- 新增業(yè)務(wù)狀態(tài)碼和錯誤信息:
- 業(yè)務(wù)狀態(tài)碼:用來表示后端是否正確響應(yīng)了,和http狀態(tài)碼是兩個概念(業(yè)務(wù)狀態(tài)碼表示的是業(yè)務(wù)的情況,http狀態(tài)碼則是連接的情況,http請求成功才可能有業(yè)務(wù)狀態(tài)碼)
- 案例:如果業(yè)務(wù)狀態(tài)碼表示成功,但是返回的數(shù)據(jù)為0,表示當(dāng)前沒有數(shù)據(jù)。如果業(yè)務(wù)狀態(tài)碼表示失敗,數(shù)據(jù)也為0,此時就表示后端請求失敗了
- 數(shù)字的定義:業(yè)務(wù)狀態(tài)碼由程序員自定義(什么數(shù)字是什么情況),不過,我們一般會把失敗定義為的數(shù),把成功定義為>0的數(shù)
- 前端的情況:哪怕業(yè)務(wù)狀態(tài)碼返回的那個數(shù)字表示的是失敗,依舊是http成功連接的情況,前端走的是【success:funcation】,如果http狀態(tài)碼表示的是失敗,走的是【error:function】
- 業(yè)務(wù)狀態(tài)碼:用來表示后端是否正確響應(yīng)了,和http狀態(tài)碼是兩個概念(業(yè)務(wù)狀態(tài)碼表示的是業(yè)務(wù)的情況,http狀態(tài)碼則是連接的情況,http請求成功才可能有業(yè)務(wù)狀態(tài)碼)
- 錯誤信息:根據(jù)code知道具體錯誤是什么,然后前端把這個具體錯誤反饋給用戶
@Data
public class PageResult<T> {
private List<BookInfo> records;
private Integer total;
//此處設(shè)置0表示成功,-1為失敗
private Integer code;
//錯誤信息
private String errMsg;
private PageRequest request;
public PageResult(List<BookInfo> records, Integer total, PageRequest request) {
this.records = records;
this.total = total;
this.request = request;
}
}3.新增Result類:
- 原因:
- 其他接口都需要強(qiáng)制登錄等統(tǒng)一操作,如果都寫在代碼里,要寫很多份,而且一旦要改需求,改動量大,耦合性太高
- 故我們可以把之前的返回結(jié)果進(jìn)行一個封裝,封裝為Result類,即每一個Controller接口返回的數(shù)據(jù)都是Result類,前端根據(jù)Result里的信息進(jìn)行不同的反饋

- 優(yōu)化tip ---->使用枚舉:
- 原因:與其狀態(tài)碼這邊使用Integer,還不如使用枚舉,因?yàn)槭褂肐nteger還需要我們專門去查文檔來看各個數(shù)字的是什么意思
- Getter 和 Setter:因?yàn)槊杜e是不能用@Data的,所以此處我們要自己寫Getter和Setter方法
public enum ResultCode {
SUCCESS(0),
FAIL(-1),
UNLOGIN(-2);
private int code;
ResultCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}4.提取Session:
- 原因:
- 此時的Session是通過【session.setAttribute(“session_user_key”,userInfo)】存在【session_user_key】,但這依然有耦合性太高的可能性,而且是個【硬編碼問題】,所以我們可以把他提取為一個常量
- 使用方法:

- 其他:關(guān)于硬編碼問題 ----> 我們要避免字符串直接出現(xiàn)在代碼中
- 如果這個常量在很多地方要用,那就提到一個專門用來放常量的類里
- 如果這個常量,只在當(dāng)前這個類中使用,也可以通過【private static final xxx = xxx】的提上去

提取成構(gòu)造函數(shù):


優(yōu)化tip:使用泛型Result<>
- Obejct的時機(jī):方法里我們用的是static,表示【靜態(tài)】,靜態(tài)的執(zhí)行時機(jī)是要比類早的,但是泛型是要類執(zhí)行了才能拿到類型是什么。所以當(dāng)前最多給成員屬性設(shè)置為泛型,方法中還是要使用Object
- 代碼:需要在方法里面再聲明一下
@Data
public class Result<T> {
/**
* 業(yè)務(wù)狀態(tài)碼
*/
private ResultCode code;
/**
* 錯誤信息
*/
private String errMsg;
/**
* 把所有的返回數(shù)據(jù)都塞到這里
*/
private T data;
/**
* 成功時執(zhí)行的方法
* @return
*/
public static <T> Result<T> seccess(Object data){
Result result = new Result();
result.setCode(ResultCode.SUCCESS);
result.setErrMsg("");
result.setData(data);
return result;
}
/**
* 失敗時執(zhí)行的方法
* 有錯誤,無數(shù)據(jù)
* @param errMsg
* @return
*/
public static <T> Result<T> fail(String errMsg){
Result result = new Result();
result.setCode(ResultCode.FAIL);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
/**
* 失敗時執(zhí)行的方法
* 有錯誤,有
* @param data
* @param errMsg
* @return
*/
public static <T> Result<T> fail(Object data, String errMsg){
Result result = new Result();
result.setCode(ResultCode.FAIL);
result.setErrMsg(errMsg);
result.setData(data);
return result;
}
/**
* 未登錄時執(zhí)行的方法
* @return
*/
public static <T> Result<T> unlogin(){
Result result = new Result();
result.setCode(ResultCode.UNLOGIN);
result.setErrMsg("用戶未登錄");
result.setData(null);
return result;
}
}測試
- 需要先登錄才能訪問到列表頁面
- 如果已經(jīng)登陸了,但是修改sessionId的值,依舊需要重新登錄(找不到服務(wù)器里對應(yīng)的session了)

3.2 使用攔截器
- 關(guān)于寫法:寫法有很多,重點(diǎn)是實(shí)現(xiàn)需求
- response.setStatus(401):設(shè)置返回的http狀態(tài)碼為401
- 401:未認(rèn)證登錄,或者提供的認(rèn)證沒被認(rèn)可
- 業(yè)務(wù)狀態(tài)碼由開發(fā)人員自定義,http狀態(tài)碼其實(shí)也是由http開發(fā)人員自定義的,但現(xiàn)在這已經(jīng)成為了易總規(guī)范,大家都需要遵守
- 后端代碼:
- response.setStatus(401):設(shè)置返回的http狀態(tài)碼為401
- 401:未認(rèn)證登錄,或者提供的認(rèn)證沒被認(rèn)可
- 業(yè)務(wù)狀態(tài)碼由開發(fā)人員自定義,http狀態(tài)碼其實(shí)也是由http開發(fā)人員自定義的,但現(xiàn)在這已經(jīng)成為了易總規(guī)范,大家都需要遵守
- response.setStatus(401):設(shè)置返回的http狀態(tài)碼為401
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("用戶登錄校驗(yàn)開始");
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(Constants.SESSION_USER_KEY) != null){
UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
if (userInfo != null && userInfo.getId() > 0){
return true;
}
}
response.setStatus(401);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("目標(biāo)方法執(zhí)行后");
}
}@Configuration
public class webConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login"); //在執(zhí)行登錄操作時,不要攔截
}
}
4. 前端代碼:
如果前端出現(xiàn)了錯誤,可以一行行注掉代碼后,通過console.log打印日志來判斷錯誤在哪
//http連接失敗時執(zhí)行的方法
error: function (error) {
console.log(error);
if (error.status == 401) {
console.log("401");
location.href = "login.html";
}
}四、更新圖書
后端代碼:
Controller層
@RequestMapping("/updateBook")
public String updateBook(BookInfo bookInfo){
log.info("接收到的bookInfo:{}", bookInfo);
Integer result = bookService.updateBook(bookInfo);
if (result == 0){
log.error("更新圖書失敗,請聯(lián)系管理員");
return "失敗";
}
return "";
}Service層
public Integer updateBook(BookInfo bookInfo){
Integer res = 0;
try{
res = bookInfoMapper.updateBook(bookInfo);
}catch (Exception e){
log.error("更新圖書失敗,e:{}", e);
}
return res;
}Mapper層
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.book_test.Mapper.BookInfoMapper">
<update id="updateBook">
update book_info
<set>
<if test="bookName != null">
book_name = #{bookName},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="count != null">
count = #{count},
</if>
<if test="price != null">
price = #{price},
</if>
<if test="publish != null">
publish = #{publish},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id};
</update>
</mapper>前端代碼:
<script>
$.ajax({
type: "get",
url: "/book/queryBookInfoById" + location.search,
success: function (book){
if (book != null) {
//頁面輸入框的填充
$("#bookId").val(book.id);
$("#bookName").val(book.bookName);
$("#bookAuthor").val(book.author);
$("#bookStock").val(book.count);
$("#bookPrice").val(book.price);
$("#bookPublisher").val(book.publish);
$("#bookStatus").val(book.status)
} else {
alert("圖書不存在")
}
}
});
function update() {
$.ajax({
type: "post",
url: "/book/updateBook",
data: $("#updateBook").serialize(),
success: function (result) {
if (result != null) {
location.href = "book_list.html";
} else {
alert(result);
}
}
});
}
</script>到此這篇關(guān)于SpringBoot 圖書管理系統(tǒng)(刪除、強(qiáng)制登錄、更新圖書)詳細(xì)代碼的文章就介紹到這了,更多相關(guān)SpringBoot 圖書管理系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java字符串日期類Date和Calendar相互轉(zhuǎn)化及相關(guān)常用方法
Java語言的Calendar(日歷),Date(日期),和DateFormat(日期格式)組成了Java標(biāo)準(zhǔn)的一個基本但是非常重要的部分,下面這篇文章主要給大家介紹了關(guān)于java字符串日期類Date和Calendar相互轉(zhuǎn)化及相關(guān)常用方法的相關(guān)資料,需要的朋友可以參考下2023-12-12
如何將復(fù)雜SQL轉(zhuǎn)換成Java對象的實(shí)例講解
轉(zhuǎn)換復(fù)雜SQL到Java代碼,我們需要確定數(shù)據(jù)庫連接方式和工具,使用JDBC的API來連接數(shù)據(jù)庫、執(zhí)行SQL語句,復(fù)雜SQL語句可以被拆分為多個步驟,每個步驟執(zhí)行一個特定的操作,通過將SQL語句拆分為多個步驟,我們可以更好地理解復(fù)雜SQL的邏輯,并且更容易將其轉(zhuǎn)換為Java代碼2024-05-05
解決get請求入?yún)NotNull驗(yàn)證不生效問題
這篇文章主要介紹了解決get請求入?yún)NotNull驗(yàn)證不生效問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot集成Swagger使用SpringSecurity控制訪問權(quán)限問題
這篇文章主要介紹了SpringBoot集成Swagger使用SpringSecurity控制訪問權(quán)限問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringBoot實(shí)現(xiàn)HTTP調(diào)用的7 種方式
本文主要介紹了SpringBoot實(shí)現(xiàn)HTTP調(diào)用的7 種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
SpringMVC @GetMapping注解路徑?jīng)_突問題解決
MD5對密碼進(jìn)行加密存儲是常見的一種加密方式,本文主要介紹了Java雙重MD5加密實(shí)現(xiàn)安全登錄,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
spring boot項(xiàng)目application.properties文件存放及使用介紹
這篇文章主要介紹了spring boot項(xiàng)目application.properties文件存放及使用介紹,我們的application.properties文件中會有很多敏感信息,大家在使用過程中要多加小心2021-06-06
HttpMessageConverter報文信息轉(zhuǎn)換器的深入講解
在Spring中內(nèi)置了大量的HttpMessageConverter,通過請求頭信息中的MIME類型,選擇相應(yīng)的HttpMessageConverter,這篇文章主要給大家介紹了關(guān)于HttpMessageConverter報文信息轉(zhuǎn)換器的相關(guān)資料,需要的朋友可以參考下2022-01-01

