Spring Boot -- 图书管理系统(登录、展示+翻页、添加/修改图书)

文章目录

  • 一、应用分层
  • 二、数据库的设计
  • 三、登录功能
  • 四、展示列表(使用虚构的数据)
  • 五、翻页 + 展示功能
  • 六、添加图书
  • 七、修改图书

一、应用分层

  1. 为什么我们需要应用分层:当代码量很多时,将其全部放在一起查找起来就会很麻烦,而且不利于开发。
  2. 分层思想:两种的侧重点不同,三层架构侧重于对数据的处理
    • MVC:Controller层接收返回数据、Model层进行具体的业务处理、View层返回视图。但随着前后端分离,MVC分层思想已经不适用了。
    • 三层架构:是目前比较主流的分层方法。如果项目十分复杂,也可以在该架构的基础上分得再细一点
      • 表现层:接收并返回请求
      • 业务逻辑层:处理业务逻辑
      • 数据层:处理数据,包括数据的存储与获取
  3. 如何分层:Spring支持了三层架构,我们只需要用文件夹分层即可
    • 表现层 <------> Controller ---------》接收请求 + 数据的初步校验 + 结果响应
    • 业务逻辑层 <------> Service ---------》真正干活的部分
    • 数据层 <------> Dao ---------》用来进行DB处理
  4. 关于实体类:一般把实体类都放在【model】文件夹里

二、数据库的设计

  1. 表通常分为实体类和关系表
    • 实体类:根据需求中会出现的的名词进行设计,比如用户表、博客表、图书表
      • 注意,除了需求的字段,建议加上id、update_time、create_time、deleteFlaf着几个字段
    • 关系表:表示了实体类之间的关联关系
      • 如果关系比较简单,也是可以放在实体表中的
  2. user_info表
    • 表名和字段名要用反引号(`),而不是单引号(')
DROP TABLE IF EXISTS book_info;
CREATE TABLE `book_info` (
         `id` INT(11) NOT NULL AUTO_INCREMENT,
         `book_name` VARCHAR(127) NOT NULL,
         `author` VARCHAR(127) NOT NULL,
         `count` INT(11),
         `price` DECIMAL(7,2) NOT NULL,
         `publish` VARCHAR(256),
         `status` TINYINT(4) DEFAULT 1 COMMENT '0-无效,1-正常,2-不允许借阅,类似于delete_flag',
         `create_time` DATETIME DEFAULT now(),
         `update_time` DATETIME DEFAULT now() ON UPDATE now() COMMENT '创建默认用当前时间,更新时用当前时间更新',
         PRIMARY KEY(`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

三、登录功能

  1. 前端代码
<script>
    function login() {
        var userName = $('#userName').val();
        var password = $('#password').val();
        if (userName=='' || password==''){
            return;
        }

        $.ajax({
            url: "/user/login",
            type: "post",
            data: {
                "userName": userName,
                "password": password
            },

            success: function (result){
                if (result){
                    location.href = "/book_list.html";
                }else{
                    alert("用户名或密码错误");
                }
            }
        });
    }
</script>
  1. 后端代码

Controller层

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public boolean login(String userName, String password, HttpSession session){
        //账号或密码为空
        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return false;
        }
        UserInfo userInfo = userService.queryUserByName(userName);
        if (userInfo == null || userInfo.getId() <= 0){
            //如果找不到或者结果不对,就返回false
            return false;
        }
        //账号密码正确
        if(userInfo!=null && password.equals(userInfo.getPassword())){
            //存储在Session中
            userInfo.setPassword("");
            //如果不指向存储用户名字,想知道更多信息,可以把整个userInfo存进去
            session.setAttribute("session_user_key",userInfo);
            return true;
        }
        return false;
    }
}

Service层

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public UserInfo queryUserByName(String name) {
        return userInfoMapper.queryUserByName(name);
    }
}

model 层

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Integer deleteFlag;
    private Date creatTime;
    private Date updateTime;
}

mapper层

@Mapper
public interface UserInfoMapper {
    @Select("select * from user_info where delete_flag=0 and user_name=#{name}")
    UserInfo queryUserByName(String name);
}

四、展示列表(使用虚构的数据)

  1. 实体类设置
    • BigDecimal:价格相关的一般会涉及到精度,故而不用float或double
    • status VS statusCN
      • 状态我们一般不存储中文,因为案例中总共也只有【已借阅】和【未借阅】这两种状态,与其存中文,我们干脆直接存数字。到时候根据状态的数字,设置中文描述
      • 而且万一修改了状态的需求,那就需要修改整个数据库(比如将所有"已借阅"改为"审批中")。企业中数据库的修改由于数据量大,其数据修改是一件很麻烦的事
        • 也正因如此,我们定义数据库时,无需节约,尽量往大了定义。
    • 关于状态的中文描述设置:这个工作前后端都可以完成,此处我们由后端完成
@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer count;
    private BigDecimal price;
    private String publish;
    private Integer status;
    private String statusCN;
}
  1. 后端代码

Controller层
(1)为什么不在方法内部设置BookService属性:这个类该Controller层的所有方法都要用,提出来就不需要每个方法反复创建了

@RestController
@RequestMapping("/book")
public class BookController {
	@Autowired
    private BookService bookService;

    @RequestMapping("getList")
    public List<BookInfo> getList(){
        return bookService.getList();
    }
}

Service层

@Component
public class BookService {
	@Autowired
    private BookDao bookDao;

    public List<BookInfo> getList(){
        List<BookInfo> bookInfos = bookDao.mockData();
        for (BookInfo book : bookInfos){
            if (book.getStatus() == 1){
                book.setStatusCN("已借阅");
            }else{
                book.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }
}

Dao层
(1)优化tip:如果知道要创建list的具体长度,创建时直接写上,这样后面就不需要再扩容了
(2)mockData:虚构数据

@Component
public class BookDao {
    private List<BookInfo> bookInfos = new ArrayList<>(10);
    public List<BookInfo> mockData(){
        for (int i = 0; i < 10; i++){
            BookInfo book = new BookInfo();
            book.setId(i);
            book.setBookName("图书" + i);
            book.setAuthor("作者" + i);
            //随机生成一个200以下的整数
            book.setCount(new Random().nextInt(200));
            //随机生成一个100以下的小数
            book.setPrice(new BigDecimal(new Random().nextInt(100)));
            book.setPublish("出版社" + i);
            book.setStatus(i % 2 == 0?2:1);

            bookInfos.add(book);
        }

        return bookInfos;
    }
}
  1. 前端代码
    • 代码的执行:JS代码是从上往下执行的,前面语法之类的出错了,后面的代码就不会执行了
    • 关于引号:因为前端代码一些class是用双引号括起来,所以我们用单引号构造HTML,以便区分
    • 关于添加数值:先写一对单引号,让它们前后去闭合,然后添加book.id之类的数值
function getBookList() {
$.ajax({
   url: "/book/getList",
   type: "get",
   success: function (books){
       var divE = '';
       for (var book of books){
           console.log("打印");
           divE += '<tr>';
           divE += '<td><input type=\"checkbox\" name=\"selectBook\" value=\"' + book.id+ '\" id=\"selectBook\" class=\"book-select\"></td>';
           divE += '<td>'+ book.id + '</td>';
           divE += '<td>'+book.bookName +'</td>';
           divE += '<td>'+ book.author + '</td>';
           divE += '<td>'+ book.count + '</td>';
           divE += '<td>'+ book.price + '</td>';
           divE += '<td>'+ book.publish + '</td>';
           divE += '<td>'+ book.statusCN + '</td>';
           divE += '<td>';
           divE += '<div class="op">';
           divE += '<a href=\"book_update.html?bookId='+ book.id + '\">修改</a>';
           divE += '<a href=\"book_update.html?bookId='+ book.id + '\">修改</a>';
           divE += '</div>';
           divE += '</td>';
           divE += '</tr>';

       }
       $("tbody").html(divE);
   }
});

五、翻页 + 展示功能

  1. 功能的实现:可以由后端完成,也可以由前端完成
    • 前端完成:可以一次性拿到所有的数据
      • 缺点
        • 第一次请求响应时间长
        • 数据如果进行修改,前端无法感知到
      • 优点:因为一次性获取了所有数据,后续翻页不需要去调用后端,后续响应快
    • 后端完成:需要多次请求。推荐
      • 第一次请求时,请求第一页的内容,后端只需要返回第一页的信息即可。请求第二页的内容,后端只返回第二页的内容
  2. 如何实现该功能
    • SQL对返回的语句进行限制:select * from book_info limit $(offset), $(limit)
      • offset:从第几条数据开始返回。需要计算,(currentpasge - 1) * pageSize
      • limit:返回几条数据
    • 前端需要告诉我们的内容
      • 当前页:currentPage
      • 每页显示的条数:因为可能会改需求,所以此处我们并不写死
    • 后端返回的结果
      • 当前页的内容
      • 总条数:如果一共只有24条数据,且每页展示10条,此时最多翻3页,多了数据不够查不到
    • 使用翻页插件:翻页插件有很多,此处我们使用 jqPaginator
  3. controller层与model层的设计
    • 用对象封装:controller层因为涉及到了接口文档的编写,有沟通成本,一旦增加新需求,就需要改文档了(其他service层,dao层无所谓是否用对象)
      • 所以,为了方便后续进行扩展,返回值和参数建议设置一个类
    • 潜在问题:当我们查看完一页内容(默认显示5条),又去设置显示10条,此时就会出现的数据是
@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getBookListByPage")
    public PageResult getBookListByPage(PageRequest pageRequest){
        return null;
    }
}
@Data
public class PageRequest {
    /**
     * 当前是那一页,如果没传默认取第一页
     */
    private int currentPage = 1;

    /**
     * 展示几条记录,如果没传默认展示5条
     */
    private int pageSize = 5;
}
@Data
public class PageResult {
    /**
     * 当前页的记录
     */
    private List<BookInfo> records;

    /**
     * 总记录数
     */
    private Integer total;
}
  1. service的封装问题
    在这里插入图片描述
  2. 后端代码
    • try-catch:处理异常问题
    • 封装操作
    • 使用@slif4j多打印日志:在开发阶段可以多多打印日志,这样我们可以根据日志来判断错误,这样就不用debug了

Controller层

@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    BookService bookService;

    @RequestMapping("/getBookListByPage")
    public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest){
        log.info("查询翻页信息,pageRequest:{}", pageRequest);
        if (pageRequest.getPageSize() < 0 || pageRequest.getCurrentPage() < 1){
            return null;
        }

        PageResult<BookInfo> res = null;
        try {
            res = bookService.selectBookInfoByPage(pageRequest);
        }catch (Exception e){
            log.error("查询翻页信息错误");
        }

        return res;
    }
}

Service层
(1)为什么要返回pageRequest:前端翻页要用

(2)使用线程池优化:可以搞两个线程,一个去获取当前页的内容,一个去获取总记录数

@Slf4j
@Service
public class BookService {
    @Autowired
    BookInfoMapper bookInfoMapper;

    public PageResult selectBookInfoByPage(PageRequest pageRequest){
        if (pageRequest == null){
            return null;
        }
        log.info(pageRequest.getCurrentPage() + "");
        List<BookInfo> bookInfos = bookInfoMapper.selectBookInfoByPage(pageRequest.getOffset(), pageRequest.getPageSize());
        for (BookInfo bookInfo: bookInfos){
            if (bookInfo.getStatus() == 1){
                bookInfo.setStatusCN("可借阅");
            }else if (bookInfo.getStatus() == 2){
                bookInfo.setStatusCN("不可借阅");
            }
            if(bookInfo.getPublish() == null){
                bookInfo.setPublish("不干人事出版社");
            }
        }
        Integer count = bookInfoMapper.count();

        return new PageResult(bookInfos, count, pageRequest);
    }
}

(3)使用枚举类进行优化
在这里插入图片描述

//枚举类
public enum BookStatusEnum {
    DELETED(0, "删除"),
    NORMAL(1, "可借阅"),
    FORBINNEN(2, "不可借阅");
    /**
     * 状态,对应了BookInfo里的status
     */
    private int code;
    /**
     * 状态对应的中文,对应了BookInfo里的statusCN
     */
    private String name;

    BookStatusEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * 根据 code获取对应的 BookStatusEnum
     * 使用static,这样就不需要调用时专门去new一个对象了
     * @return
     */
    public static BookStatusEnum getNameByCode(int code){
        switch (code){
            case 0: return BookStatusEnum.DELETED;
            case 1: return BookStatusEnum.NORMAL;
            case 2: return BookStatusEnum.FORBINNEN;
            default:
                return BookStatusEnum.FORBINNEN;
        }
    }
}
@Slf4j
@Service
public class BookService {
    @Autowired
    BookInfoMapper bookInfoMapper;

    public PageResult selectBookInfoByPage(PageRequest pageRequest){
        if (pageRequest == null){
            return null;
        }
        log.info(pageRequest.getCurrentPage() + "");
        List<BookInfo> bookInfos = bookInfoMapper.selectBookInfoByPage(pageRequest.getOffset(), pageRequest.getPageSize());
        if (bookInfos != null && bookInfos.size() > 0){
            for (BookInfo bookInfo: bookInfos){
                //根据status获取状态的定义
                bookInfo.setStatusCN(BookStatusEnum.getNameByCode(bookInfo.getStatus()).getName());
            }
        }

        Integer count = bookInfoMapper.count();

        return new PageResult(bookInfos, count, pageRequest);
    }
}

model层
(1) 接口参数的定义:实际开发中,只设置接收参数offset、limit即可,但这里因为后端不处理,就要由前端处理,当前情况下,用前端处理比较麻烦,故而后端来处理。

@Data
public class PageRequest {
    /**
     * 首先进入时是在哪里,如果没传默认取第一页
     */
    private int currentPage = 1;

    /**
     * 展示几条记录,如果没传默认展示5条
     */
    private int pageSize = 5;

    private Integer offset;

    public Integer getOffset() {
        return (currentPage - 1) * pageSize;
    }
}
@Data
public class PageResult<T> {
    /**
     * 当前页的记录
     */
    private List<BookInfo> records;

    /**
     * 总记录数
     */
    private Integer total;
    private PageRequest request;

    public PageResult(List<BookInfo> records, Integer total, PageRequest request) {
        this.records = records;
        this.total = total;
        this.request = request;
    }
}

Mapper层

@Mapper
public interface BookInfoMapper {
    /**
     * 获取当前页的信息
     * @param offset 从第几条数据开始算
     * @param pageSize  一次性展示多少数据
     * @return
     */
    @Select("select * from book_info where status != 0 limit #{offset}, #{pageSize}")
    List<BookInfo> selectBookInfoByPage(Integer offset, Integer pageSize);

    /**
     * 获取总记录数
     * @return
     */
    @Select("select count(1) from book_info where status != 0")
    Integer count();
}

  1. 前端代码
    • location.href = “book_list.html?currentPage=” + page:如果有翻页操作,就会执行跳转,跳转的url是【book_list.html?currentPage=xxx】。同时【book_list.html】这个页面会在进入时,执行前端的 getBookList() 方法,以获得当前页的数据
    • url: “/book/getBookListByPage” + location.search
      • 这是【getBookList()】的ajax的url,【location.search】获取的是查询字符串的值,即【?currentPage=xxx】
      • 当执行了翻页操作后,就会有【查询字符串+来到book_list页面+再次执行了getBookList方法】
<script>

    getBookList();
    function getBookList() {
        $.ajax({
            type: "get",
            url: "/book/getBookListByPage" + location.search,
            success: function (result) {
                var finalHtml = "";

                //加载列表
                //var pageResult = result.data;
                for (var book of result.records) {
                    //根据每一条记录去拼接html, 也就是一个tr
                    finalHtml += '<tr>';
                    finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" id="selectBook" class="book-select"></td>';
                    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)" οnclick="deleteBook(' + book.id + ')">删除</a>';
                    finalHtml += '</div></td></tr>';
                }

                $("tbody").html(finalHtml);

                //翻页信息
                $("#pageContainer").jqPaginator({
                    totalCounts: result.total, //总记录数
                    pageSize: 5,    //每页的个数
                    visiblePages: 5, //可视页数
                    currentPage: result.request.currentPage,  //当前页码
                    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);
                        if (type == "change") {
                            location.href = "book_list.html?currentPage=" + page;
                        }

                    }
                });

            },
            error: function (error) {
                console.log(error);
                if (error.status == 401) {
                    console.log("401");
                    location.href = "login.html";
                }
            }
        });
    }
</script>
  1. 处理BigDecimal的精度问题
    • 问题的描述
      • 因为price的类型设置为BigDecimal,如果价钱是22.18,前端不会忽略,如果是22.00,前端展示时会被忽略,只展示22
      • 注意,后端的返回是没有问题的,是可以正确显示出22.00的,这就是前端的显示问题
    • 解决方法
      • 前端解决:这本来就是前端的显示问题,前端专门去修改一下即可
      • 后端解决:后端把返回的格式改为一个字符串,这样就不会被忽略了
    • 如何使用后端来解决—>如何把返回格式设置为字符串
      @JsonFormat(shape = JsonFormat.Shape.STRING),将展示的形态格式化处理,此处设置成了字符串类型
@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer count;
    
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private BigDecimal price;
    
    private String publish;
    private Integer status;
    private String statusCN;
    private Date createTime;
    private Date updateTime;
}

六、添加图书

  1. 后端返回情况选择:告诉前端是否添加成功。第三种更为合理,但为了方便此处我们采用第二种。
    • boolean:true表示添加成功,false表示添加失败
      • 缺点:没法告诉前端,失败的原因是什么
    • String:“”表示添加成功,不为空则是添加失败
    • 对象:boolean类型的result用来表示【是否添加成功】。String类型的errorMsg用来表示【错误原因】
  2. 关于日志的打印:没有异常出错的代码,日志打不打以及在哪里打看个人意愿,但是异常/出错的情况,是需要打日志的,比如密码输入错误,账号不存在等
    • 好处:有了日志,我们就可以减少debug的次数,可以直接根据debug判断错误的点
  3. 关于异常的捕获:service层、controller层选一个 try-catch处理 就好了
    • 如果service层处理了,controller层没必要再处理一次
    • 如果service层没处理,异常会抛给controller层,到时候由controller层处理即可
  4. 关于controller层的参数校验
    在这里插入图片描述
  5. 后端代码
    Controller层
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    BookService bookService;

    @RequestMapping("/addBook")
    public String addBook(BookInfo bookInfo){
        log.info("接收到添加图书请求,bookinfo:{}", bookInfo);
        if (bookInfo == null
                ||!StringUtils.hasLength(bookInfo.getBookName())
                || !StringUtils.hasLength(bookInfo.getAuthor())
                || bookInfo.getCount() < 0
                || bookInfo.getPrice() == null
                || !StringUtils.hasLength(bookInfo.getPublish())){
            log.error("传参错误,bookInfo:{}", bookInfo);
            return "参数校验失败,请检查入参";
        }

        Integer res = bookService.addBook(bookInfo);
        if (res <= 0){
            log.error("添加图书出错:bookInfo:{}", bookInfo);
            return "添加图书出错,请联系管理员";
        }
        return "";
    }
}

Service层

@Slf4j
@Service
public class BookService {
    @Autowired
    BookInfoMapper bookInfoMapper;

    public Integer addBook(BookInfo bookInfo){
        Integer result = 0;
        try{
            result = bookInfoMapper.addBook(bookInfo);
            if (result <= 0){
                log.error("service 层添加图书失败:bookInfo:{}",bookInfo);
            }
        }catch (Exception e){
            log.error("添加图书失败,e:{}",e);
        }

        return result;
    }
}

Model层

@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer count;
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private BigDecimal price;
    private String publish;
    private Integer status;
    private String statusCN;
    private Date createTime;
    private Date updateTime;
}

Mapper层

@Mapper
public interface BookInfoMapper {
    @Insert("insert into book_info(book_name, author, count, price, publish, status)" +
            "values(#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status})")
    Integer addBook(BookInfo bookInfo);
}
  1. 前端代码
    • $(addBook).serialize():提交整个form表单,form标签包裹的input、select、textarea等输入信息都会被提交
      • 传过去的格式在这里插入图片描述
    • 优点:没必要像下面这样,把所有要传过去的数据都写一遍,可以一行代码搞定
data:{
	bookName: $("#bookName").val(),
	xxxxx
}
<script>
    function add() {
        $.ajax({
            url: "/book/addBook",
            type: "post",
            data: $(addBook).serialize(),
            success: function (result){
                if (result == ""){
                    location.href = "book_list.html";
                }else{
                    alert(result);
                }
            }
        })
    }
</script>

七、修改图书

  1. 需求
    • 点击修改按钮时,能把当前图书的信息显示出来
      • 我们可以根据url上的bookId查到对应的BookInfo
    • 点击确定时,能把修改的结果进行保存
  2. 后端代码
    Controller层
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    BookService bookService;

    @RequestMapping("/queryBookInfoById")
    public BookInfo queryBookInfoById(Integer bookId){
        log.info("根据id查询图书,bookId:{}", bookId);

        try{
            BookInfo bookInfo = bookService.queryBookById(bookId);
            return bookInfo;
        }catch (Exception e){
            log.error("查询图书失败,e:{}", e);
        }

        return null;
    }

    @RequestMapping("/updateBook")
    public String updateBook(BookInfo bookInfo){
        log.info("接收到的bookInfo:{}", bookInfo);

        Integer result = bookService.updateBook(bookInfo);
        if (result == 0){
            log.error("更新图书失败,请联系管理员");
            return "失败";
        }

        return "";
    }
}

Service层

@Slf4j
@Service
public class BookService {
    @Autowired
    BookInfoMapper bookInfoMapper;

    public BookInfo queryBookById(Integer id){
        return bookInfoMapper.queryBookById(id);
    }

    public Integer updateBook(BookInfo bookInfo){
        Integer res = 0;
        try{
            res = bookInfoMapper.updateBook(bookInfo);
        }catch (Exception e){
            log.error("更新图书失败,e:{}", e);
        }

        return res;
    }
}

Mapper层

  • 配置yml + 准备文件
    在这里插入图片描述
@Mapper
public interface BookInfoMapper {
    @Select("select * from book_info where id = #{id}")
    BookInfo queryBookById(Integer id);
	
	//涉及到动态sql,使用xml的方式去实现
    Integer updateBook(BookInfo bookInfo);
}

<?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">  
    	<!-- 建议先写简单的SQL,再在其基础上将其改为动态SQL-->
        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};
        <!--此处不用where标签,因为此处where和id是一定要存在的-->
        <!--如果使用了where标签,当没有传id时,where会被删掉,不会显示地报错-->
    </update>
</mapper>
  1. 前端代码
    • $(“#bookName”),val():val方法里,如果里面没有值,会根据选择器的设置去拿值,如此处就是去找id为bookName的选择器,获取里面的值
    • $(“#bookName”),val(book.bookName):val方法里,如果里面有值,就是把括号里的值赋值给选中的对象
    • < input type=“hidden” class=“form-control” id=“bookId” name=“id”>:hidden表示这个文本框是被隐藏的,浏览器上看不到,但是里面可以有值
      • 为什么要加上这个代码:使用location.search获取到的值是【?bookId = xxx】,但有时我们只想要获得这个数字。下面提供了两种解决方法,由于方法一比较困难,我们采用方法二
        (1)解决方法一:通过【去掉问号 + 用等号分割】获得了这个数字

        (2)解决方法二:使用一个隐藏的文本框,这样前端把整个表单传给后端时,后端也能收到bookid了

 <input type="hidden" class="form-control" id="bookId" name="id">
<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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/728442.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SQL每日一练】HackerRan-Basic Join-Challenges练习

文章目录 题目题析题解1.sqlserver 题目 编写一个查询来打印 hacker _ id、 name 和每个学生创建的挑战的总数。按照挑战的总数按降序对结果进行排序。如果不止一个学生创建了相同数量的挑战&#xff0c;那么按 hacker _ id 对结果进行排序。如果不止一个学生创建了相同数量的…

Node.js单点登录SSO详解:Session、JWT、CORS让登录更简单

文章目录 一、SSO介绍1、使用SSO的好处 二、中间件介绍1、Express安装导入使用 2、cors安装导入配置 3、express-session安装导入配置使用 4、jsonwebtoken安装导入使用 5、jwt和session对比 三、SSO实现方案1、安装依赖2、结构3、实现原理 三、示例代码1、nodejs端 server/ind…

求最小生成树的新算法

不管 prim 算法还是 kruskal 算法都基于 “当前可见最短边” 作贪心策略&#xff0c;但这并不适合分布式并行操作&#xff0c;比方说所有节点一起构建最小生成树&#xff0c;这些算法都显得同步开销过大&#xff0c;甚至导出错误的结果。 最近研究并构建最大流多路径传输协议的…

【配置】Notion自动化备份到github方案

步骤 打开notion网页&#xff0c;获取到需要的值 token_v2 找到请求getSpaces的 Cookie 值 token_v2 space_id 找到请求getSpaces的响应结果space,如下图&#xff1a; file_token 找个页面点击导出&#xff0c;之后拿到这个配置项 注意&#xff1a;配置项会过期&#xff0c…

内容安全复习 9 - 身份认证系统攻击与防御

文章目录 基于生物特征的身份认证系统概述基于生物特征的身份认证 人脸活体检测检测方法未解决问题 基于生物特征的身份认证系统概述 作用&#xff1a;判别用户的身份、保障信息系统安全。 是识别操作者身份的过程&#xff0c;要保证其**物理身份&#xff08;现实&#xff0…

『 Linux 』 进程间通信概述

文章目录 什么是进程间通信为什么要有进程间通信如何进行进程间通信 什么是进程间通信 进程间通信(IPC)指的是在操作系统重,允许两个或者多个进程之间传递信息或者数据的机制; 进程是操作系统重独立运行的实体,即进程间具有独立性,存在自己的地址空间; 因此进程间默认无法直接访…

mysql8.0找不到my.ini

报错问题解释&#xff1a; MySQL 8.0 在Windows系统中通常不需要 my.ini 文件&#xff0c;因为安装程序会在 %PROGRAMDATA%\MySQL\MySQL Server 8.0\ &#xff08;通常是 C:\ProgramData\MySQL\MySQL Server 8.0\&#xff09;创建默认的配置文件。如果你的系统中找不到 my.ini…

oracle 数据库导入dmp文件

荆轲刺秦王 从线上正式环境导出的 dmp 文件&#xff0c;导入到本地 oracle 数据库。 1. 创建用户: CREATE USER hf_chip IDENTIFIED BY hf_chip; 2. 授予 CONNECT 和 RESOURCE 基本权限给新用户。 GRANT CONNECT, RESOURCE TO hf_chip; 3. 创建表空间 CREATE TABLESPACE…

qt界面开发-01我的第一个qt程序

Qt 是一个跨平台的 C开发库。主要用来开发图形用户界面&#xff08;Graphical User Interface&#xff0c;简 称 GUI&#xff09;程序。 Qt 虽然经常被当做一个 GUI 库&#xff0c;用来开发图形界面应用程序&#xff0c;但这并不是 Qt 的全部&#xff1b; Qt 除了可以绘制漂亮的…

Mac磁盘满了去哪里删 Mac清理了回收站还是空间不足怎么办

在使用Mac电脑的过程中&#xff0c;我们经常会遇到磁盘空间不足的问题。尤其是当我们清理了回收站之后&#xff0c;发现剩余空间依然不足。如何有效解决Mac磁盘空间不足的问题&#xff0c;以及当清理回收站后空间仍然不足时应怎么办呢&#xff1f;本文将为大家介绍Mac磁盘满了去…

搭建预约咨询小程序,高效便捷新选择

一、预约咨询小程序是什么&#xff1f; 预约咨询小程序是一款适用于各种生活场景包括医疗、保洁、宠物护理、法律等方面的预约咨询类小程序。 二、这款小程序有什么亮点优势&#xff1f; 预约咨询小程序适用场景广泛&#xff0c;无论是心理咨询、法律咨询&#xff0c;还是宠物…

INFINI Labs 助力开源与教育:免费许可证计划全面升级

在数字化浪潮席卷全球的今天&#xff0c;INFINI Labs 深刻认识到开源项目和教育机构在技术创新与人才培养中的核心作用。因此&#xff0c;我们郑重推出全新升级的免费许可证计划&#xff0c;旨在全球范围内为开源社区和教育界提供有力支持&#xff0c;共同推动软件生态的繁荣与…

.NET C# 操作Neo4j图数据库

.NET C# 操作Neo4j图数据库 目录 .NET C# 操作Neo4j图数据库环境Code 环境 VisualStudio2022 .NET 6 Neo4j.Driver 5.21 Code // 连接设置 var uri "bolt://localhost:7687"; var user "neo4j"; var password "password"; // 请替换为你的…

React+TS前台项目实战(十三)-- 全局常用响应式加载动画Loading组件封装

文章目录 前言Loading组件1. 功能分析2. 代码详细注释3. 使用方式4. 不同尺寸loading动画效果展示 总结 前言 高阶组件有几大优点&#xff0c;其中一个就是渲染劫持&#xff0c;如懒加载&#xff0c;是否显示该元素loading&#xff0c;这在项目中我们经常用到。毫无疑问&#…

无人机校企合作

有没有想过&#xff0c;无人机和校企合作能碰撞出怎样的火花&#xff1f;&#x1f525;今天就来给大家揭秘一下这个神秘组合&#xff01; 无人机&#xff0c;作为现代科技的代表&#xff0c;已经渗透到我们生活的方方面面。而校企合作&#xff0c;更是推动科技创新、培养人才的…

武汉工程大学24计算机考研数据,有学硕招收调剂,而专硕不招收调剂!

武汉工程大学是一所以工为主&#xff0c;覆盖工、理、管、经、文、法、艺术、医学、教育学等九大学科门类的多科性教学研究型大学&#xff0c;是湖北省重点建设高校、湖北省国内一流学科建设高校&#xff0c;入选卓越工程师教育培养计划、中西部高校基础能力建设工程、“新工科…

从零开始:使用ChatGPT快速创作引人入胜的博客内容

随着科技的飞速发展&#xff0c;人工智能逐渐渗透到我们生活的各个领域。无论是商业、教育还是娱乐&#xff0c;AI技术都在以惊人的速度改变着我们。特别是在内容创作领域&#xff0c;人工智能正发挥着越来越重要的作用。今天&#xff0c;我将和大家分享如何从零开始&#xff0…

微软Azure AI更新视频翻译和语音翻译 API 功能!企业适用TTS文本转语音

很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新&#xff01; 分别是视频翻译和增强的实时语音翻译 API。 视频翻译&#xff08;批量&#xff09; 微软宣布推出视频翻译预览版&#xff0c;这是一项突破性的服务&#xff0c;旨在改变企业本地化视频内容的方式。 随着…

天润融通CEO吴强:把简单留给客户,复杂留给自己丨数据猿专访

大数据产业创新服务媒体 ——聚焦数据 改变商业 “这是一种‘无奈’&#xff0c;但更是一种责任”。 这句话听起来似乎有点悲情&#xff0c;但用来形容一家技术服务提供商&#xff0c;似乎也在情理之中。细想&#xff0c;实则充满商业智慧。 这句感慨&#xff0c;缘于数据猿专…

养车小程序系统源码,汽修源码,仿途虎养车系统源码,车辆保养小程序系统

用户端&#xff0b;商家端&#xff0b;师傅端 功能介绍: 支持下单上门服务、到店核销&#xff0c;支持单独选择项目、 也支持选择服务人员、和选择门店多种下单方式&#xff0c; 支持上门服务和到店核销两种服务方式&#xff0c;支持自营和多商家联营两种运营模式&#xff…