SpringBoot 图书管理系统(删除、强制登录、更新图书)详细代码

 更新时间:2024年09月28日 13:46:35   作者:昭著  
在企业开发中,通常不采用delete语句进行物理删除,而是使用逻辑删除,逻辑删除通过修改标识字段来表示数据已被删除,方便数据恢复,本文给大家介绍SpringBoot 图书管理系统实例代码,感兴趣的朋友跟随小编一起看看吧

一、删除图书

1.并不使用delete语句:

  • 原因:企业开发中,因为数据就意味着金钱,所以我们不会使用delete去删除(delete删除是物理删除,找不回来那种)
  • delete使用场景:delete 语句通常在进行数据修复时才会使用,比如测试人员如果要进行测试,是需要手工造一些数据的。当测试完毕后,这些数据就是脏数据(假数据)了,是没有任何价值的,此时就可

2.以使用delete把数据删掉

  • 删除的分类:逻辑删除 + 物理删除
  • 物理删除:直接把数据删掉
  • 逻辑删除:软删除/假删除,通过字段的标识来表示这个数据被删除了
    • 很多地方都在使用,比如软件开发、硬盘删除上
    • 删除时不是说把这块内容给清空了,而是把标识改了,这也是我们可以找回的原因(再把标识改回来)
  • 物理删除+存档 或 逻辑删除+存档:
    • 什么是存档:
      • 建一个和原表字段一致的表 ,如果原表要删除一个数据,就把这个数据移到存档表里
      • 存档表我们大多数情况不使用,查找时是从原表里查,当需要数据恢复时,才会从存档表里查
    • 为什么都使用逻辑删除了,还要使用存档表:存档表可以认为是一个流水表

3.删除图书功能的实现方法:因为我们此处我们使用逻辑删除,所以没必要搞一个deleteBook,沿用修改图书的接口updateBook即可
4.代码:由于后端代码updateBook已经实现了,所以此处只要写前端代码即可(把id传过去,status固定为0)

function deleteBook(bookId){
    var isDelete = confirm("确认删除?");
    if (isDelete){
        $.ajax({
            type: "post",
            url: "/book/updateBook",
            data: {
                id: bookId,
                status: 0  //接口设计中,0表示被删除的
            },
            success: function (result){
                if (result == ""){
                    location = "book_list.html";
                }else{
                    alert(result);
                }
            }
        });
    }
}

二、批量删除

1.思路解析:因为我们使用了逻辑删除,所以批量删除就等于批量更新,但我们不能像删除单个图书一样,使用updateBook,因为此处我们需要更新多个,updateBook只能更新一个

2.后端代码
Controller 层

  • @RequestParam:因为我们使用下面的方式发请求,参数是在查询字符串上。且后端设置的接口是List,默认接收是用数组(如果使用的是数组,可以不加该注解),所以要使用@RequestParam
    • @RequestParam:请求参数是查询字符串上的参数
    • @RequestBody:请求参数是body正文,需要把请求正文的内容转换为对象
    • @ResponseBody:返回的内容是响应正文
  • @RequestParam:关于参数的设计
    • 我们可以使用各种方法去接收前端发来的参数,可以设置参数在正文、url……里,接收的是数组、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)为【主逻辑方法】,但是代码有可能会执行失败,所以要【try-catch】
  • 关于日志的打印:
    • 可以方便我们后续找错,因为会有【需求没有实现 + 一条日志都没有的情况】,如SQL正确运行了,但影响的行数为0
    • 此时如果要排除错误,就需要一点一点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 层

  • 因为会涉及到动态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:获取所有的复选框
  • name=‘selectBook’:复选框有很多,此时我们要获取名字为selectBook的
  • checked:表示已经被选中的
  • each(function):对每一个选中的复选框进行某个操作
  • ids.push($(this).val()):把复选框的值放到ids这个数组里
function batchDelete(){
    var isDelete = confirm("确认批量删除?");
    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);
                }
            }
        });
    }
}

三、强制登录

3.1 不使用拦截器

  • 需求介绍:我们希望后端能检查有无登录,如果没有登录跳转到登录页面的操作
  • 新增业务状态码和错误信息:
    • 业务状态码:用来表示后端是否正确响应了,和http状态码是两个概念(业务状态码表示的是业务的情况,http状态码则是连接的情况,http请求成功才可能有业务状态码)
      • 案例:如果业务状态码表示成功,但是返回的数据为0,表示当前没有数据。如果业务状态码表示失败,数据也为0,此时就表示后端请求失败了
      • 数字的定义:业务状态码由程序员自定义(什么数字是什么情况),不过,我们一般会把失败定义为的数,把成功定义为>0的数
      • 前端的情况:哪怕业务状态码返回的那个数字表示的是失败,依旧是http成功连接的情况,前端走的是【success:funcation】,如果http状态码表示的是失败,走的是【error:function】
  • 错误信息:根据code知道具体错误是什么,然后前端把这个具体错误反馈给用户
@Data
public class PageResult<T> {
    private List<BookInfo> records;
    private Integer total;
    //此处设置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类

  • 原因:
    • 其他接口都需要强制登录等统一操作,如果都写在代码里,要写很多份,而且一旦要改需求,改动量大,耦合性太高
    • 故我们可以把之前的返回结果进行一个封装,封装为Result类,即每一个Controller接口返回的数据都是Result类,前端根据Result里的信息进行不同的反馈

  • 优化tip ---->使用枚举:
    • 原因:与其状态码这边使用Integer,还不如使用枚举,因为使用Integer还需要我们专门去查文档来看各个数字的是什么意思
    • Getter 和 Setter:因为枚举是不能用@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】,但这依然有耦合性太高的可能性,而且是个【硬编码问题】,所以我们可以把他提取为一个常量
    • 使用方法:

  • 其他:关于硬编码问题 ----> 我们要避免字符串直接出现在代码中
    • 如果这个常量在很多地方要用,那就提到一个专门用来放常量的类里
    • 如果这个常量,只在当前这个类中使用,也可以通过【private static final xxx = xxx】的提上去

提取成构造函数:

优化tip:使用泛型Result<>

  • Obejct的时机:方法里我们用的是static,表示【静态】,静态的执行时机是要比类早的,但是泛型是要类执行了才能拿到类型是什么。所以当前最多给成员属性设置为泛型,方法中还是要使用Object
  • 代码:需要在方法里面再声明一下
@Data
public class Result<T> {
    /**
     * 业务状态码
     */
    private ResultCode code;
    /**
     * 错误信息
     */
    private String errMsg;
    /**
     * 把所有的返回数据都塞到这里
     */
    private T data;
    /**
     * 成功时执行的方法
     * @return
     */
    public static <T> Result<T> seccess(Object data){
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }
    /**
     * 失败时执行的方法
     * 有错误,无数据
     * @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;
    }
    /**
     * 失败时执行的方法
     * 有错误,有
     * @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;
    }
    /**
     * 未登录时执行的方法
     * @return
     */
    public static <T> Result<T> unlogin(){
        Result result = new Result();
        result.setCode(ResultCode.UNLOGIN);
        result.setErrMsg("用户未登录");
        result.setData(null);
        return result;
    }
}

测试

  • 需要先登录才能访问到列表页面
  • 如果已经登陆了,但是修改sessionId的值,依旧需要重新登录(找不到服务器里对应的session了)

3.2 使用拦截器

  • 关于写法:写法有很多,重点是实现需求
  • response.setStatus(401):设置返回的http状态码为401
    • 401:未认证登录,或者提供的认证没被认可
    • 业务状态码由开发人员自定义,http状态码其实也是由http开发人员自定义的,但现在这已经成为了易总规范,大家都需要遵守
  • 后端代码:
    • response.setStatus(401):设置返回的http状态码为401
      • 401:未认证登录,或者提供的认证没被认可
      • 业务状态码由开发人员自定义,http状态码其实也是由http开发人员自定义的,但现在这已经成为了易总规范,大家都需要遵守
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("用户登录校验开始");
        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("目标方法执行后");
    }
}
@Configuration
public class webConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login");  //在执行登录操作时,不要拦截
    }
}

4. 前端代码

如果前端出现了错误,可以一行行注掉代码后,通过console.log打印日志来判断错误在哪

//http连接失败时执行的方法
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("更新图书失败,请联系管理员");
        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>

到此这篇关于SpringBoot 图书管理系统(删除、强制登录、更新图书)详细代码的文章就介绍到这了,更多相关SpringBoot 图书管理系统内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java_IO向文件中写入和读取内容代码实例

    java_IO向文件中写入和读取内容代码实例

    这篇文章主要介绍了java_IO向文件中写入和读取内容,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Java手机号码工具类示例详解(判断运营商、获取归属地)

    Java手机号码工具类示例详解(判断运营商、获取归属地)

    这篇文章主要介绍了Java手机号码工具类示例详解,通过手机号码来判断运营商获取归属地,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • java实现选课系统

    java实现选课系统

    这篇文章主要为大家详细介绍了java实现选课系统,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • Mybatis增强版MyBatis-Flex的具体使用

    Mybatis增强版MyBatis-Flex的具体使用

    Mybatis-Flex一个用于增强MyBatis的框架,本文主要介绍了Mybatis增强版MyBatis-Flex的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • java匿名内部类实例简析

    java匿名内部类实例简析

    匿名类是不能有名称的类,所以没办法引用它们,必须在创建时,作为new语句的一部分来声明它们,需要了解更多的可以参考本文
    2012-11-11
  • Mybatis框架搭建与简单查询详解

    Mybatis框架搭建与简单查询详解

    本文主要介绍了Mybatis框架搭建与简单查询的相关知识,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • 创建动态代理对象bean,并动态注入到spring容器中的操作

    创建动态代理对象bean,并动态注入到spring容器中的操作

    这篇文章主要介绍了创建动态代理对象bean,并动态注入到spring容器中的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Java Mybatis批量修改封装详解

    Java Mybatis批量修改封装详解

    这篇文章主要介绍了Mybatis批量修改封装的相关内容,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java 整合模板彻底解决ssm配置难题

    Java 整合模板彻底解决ssm配置难题

    SSM框架是spring MVC ,spring和mybatis框架的整合,是标准的MVC模式,将整个系统划分为表现层,controller层,service层,DAO层四层,使用spring MVC负责请求的转发和视图管理,spring实现业务对象管理,mybatis作为数据对象的持久化引擎
    2021-10-10
  • Java上传文件进度条的实现方法(附demo源码下载)

    Java上传文件进度条的实现方法(附demo源码下载)

    这篇文章主要介绍了Java上传文件进度条的实现方法,可简单实现显示文件上传比特数及进度的功能,并附带demo源码供读者下载参考,需要的朋友可以参考下
    2015-12-12

最新评论