文章目录
- 一、应用分层
- 二、数据库的设计
- 三、登录功能
- 四、展示列表(使用虚构的数据)
- 五、翻页 + 展示功能
- 六、添加图书
- 七、修改图书
一、应用分层
- 为什么我们需要应用分层:当代码量很多时,将其全部放在一起查找起来就会很麻烦,而且不利于开发。
- 分层思想:两种的侧重点不同,三层架构侧重于对数据的处理
- MVC:Controller层接收返回数据、Model层进行具体的业务处理、View层返回视图。但随着前后端分离,MVC分层思想已经不适用了。
- 三层架构:是目前比较主流的分层方法。如果项目十分复杂,也可以在该架构的基础上分得再细一点
- 表现层:接收并返回请求
- 业务逻辑层:处理业务逻辑
- 数据层:处理数据,包括数据的存储与获取
- 如何分层:Spring支持了三层架构,我们只需要用文件夹分层即可
- 表现层 <------> Controller ---------》接收请求 + 数据的初步校验 + 结果响应
- 业务逻辑层 <------> Service ---------》真正干活的部分
- 数据层 <------> Dao ---------》用来进行DB处理
- 关于实体类:一般把实体类都放在【model】文件夹里
二、数据库的设计
- 表通常分为实体类和关系表:
- 实体类:根据需求中会出现的的名词进行设计,比如用户表、博客表、图书表
- 注意,除了需求的字段,建议加上id、update_time、create_time、deleteFlaf着几个字段
- 关系表:表示了实体类之间的关联关系
- 如果关系比较简单,也是可以放在实体表中的
- 实体类:根据需求中会出现的的名词进行设计,比如用户表、博客表、图书表
- 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;
三、登录功能
- 前端代码:
<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>
- 后端代码:
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);
}
四、展示列表(使用虚构的数据)
- 实体类设置:
- 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;
}
- 后端代码:
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;
}
}
- 前端代码:
- 代码的执行: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);
}
});
五、翻页 + 展示功能
- 功能的实现:可以由后端完成,也可以由前端完成
- 前端完成:可以一次性拿到所有的数据
- 缺点:
- 第一次请求响应时间长
- 数据如果进行修改,前端无法感知到
- 优点:因为一次性获取了所有数据,后续翻页不需要去调用后端,后续响应快
- 缺点:
- 后端完成:需要多次请求。推荐
- 第一次请求时,请求第一页的内容,后端只需要返回第一页的信息即可。请求第二页的内容,后端只返回第二页的内容
- 前端完成:可以一次性拿到所有的数据
- 如何实现该功能:
- SQL对返回的语句进行限制:select * from book_info limit $(offset), $(limit)
- offset:从第几条数据开始返回。需要计算,(currentpasge - 1) * pageSize
- limit:返回几条数据
- 前端需要告诉我们的内容:
- 当前页:currentPage
- 每页显示的条数:因为可能会改需求,所以此处我们并不写死
- 后端返回的结果:
- 当前页的内容:
- 总条数:如果一共只有24条数据,且每页展示10条,此时最多翻3页,多了数据不够查不到
- 使用翻页插件:翻页插件有很多,此处我们使用 jqPaginator
- SQL对返回的语句进行限制:select * from book_info limit $(offset), $(limit)
- controller层与model层的设计:
- 用对象封装:controller层因为涉及到了接口文档的编写,有沟通成本,一旦增加新需求,就需要改文档了(其他service层,dao层无所谓是否用对象)
- 所以,为了方便后续进行扩展,返回值和参数建议设置一个类
- 潜在问题:当我们查看完一页内容(默认显示5条),又去设置显示10条,此时就会出现的数据是
- 用对象封装:controller层因为涉及到了接口文档的编写,有沟通成本,一旦增加新需求,就需要改文档了(其他service层,dao层无所谓是否用对象)
@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;
}
- service的封装问题:
- 后端代码:
- 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();
}
- 前端代码:
- 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>
- 处理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;
}
六、添加图书
- 后端返回情况选择:告诉前端是否添加成功。第三种更为合理,但为了方便此处我们采用第二种。
- boolean:true表示添加成功,false表示添加失败
- 缺点:没法告诉前端,失败的原因是什么
- String:“”表示添加成功,不为空则是添加失败
- 对象:boolean类型的result用来表示【是否添加成功】。String类型的errorMsg用来表示【错误原因】
- boolean:true表示添加成功,false表示添加失败
- 关于日志的打印:没有异常出错的代码,日志打不打以及在哪里打看个人意愿,但是异常/出错的情况,是需要打日志的,比如密码输入错误,账号不存在等
- 好处:有了日志,我们就可以减少debug的次数,可以直接根据debug判断错误的点
- 关于异常的捕获:service层、controller层选一个 try-catch处理 就好了
- 如果service层处理了,controller层没必要再处理一次
- 如果service层没处理,异常会抛给controller层,到时候由controller层处理即可
- 关于controller层的参数校验:
- 后端代码:
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);
}
- 前端代码:
- $(addBook).serialize():提交整个form表单,form标签包裹的input、select、textarea等输入信息都会被提交
- 传过去的格式:
- 优点:没必要像下面这样,把所有要传过去的数据都写一遍,可以一行代码搞定
- $(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>
七、修改图书
- 需求:
- 点击修改按钮时,能把当前图书的信息显示出来
- 我们可以根据url上的bookId查到对应的BookInfo
- 点击确定时,能把修改的结果进行保存
- 点击修改按钮时,能把当前图书的信息显示出来
- 后端代码:
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>
- 前端代码:
- $(“#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>