SSMP整合综合案例【SpringBoot的基本增删改查】

一、基本页面

主页面

添加

删除

分页

条件查询

  1. 实体类开发————使用Lombok快速制作实体类

  2. Dao开发————整合MyBatisPlus,制作数据层测试

  3. Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类

  4. Controller开发————基于Restful开发,使用PostMan测试接口功能

  5. Controller开发————前后端开发协议制作

  6. 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理

    • 列表

    • 新增

    • 修改

    • 删除

    • 分页

    • 查询

  7. 项目异常处理

  8. 按条件查询————页面功能调整、Controller修正功能、Service修正功能

二、基本框架的搭建

加载要使用的技术对应的starter,修改配置文件格式为yml格式,并把web访问端口先设置成80。

pom.xml

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

application.yml

server:port: 80

三、实体类

-- ----------------------------
-- Table structure for tbl_book
-- ----------------------------
DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book`  (`id` int(11) NOT NULL AUTO_INCREMENT,`type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');

实体类

public class Book {private Integer id;private String type;private String name;private String description;
}

Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发,SpringBoot目前默认集成了lombok技术,并提供了对应的版本控制,所以只需要提供对应的坐标即可,在pom.xml中添加lombok的坐标。

<dependencies><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
</dependencies>

使用lombok可以通过一个注解@Data完成一个实体类对应的getter,setter,toString,equals,hashCode等操作的快速添加

import lombok.Data;
@Data
public class Book {private Integer id;private String type;private String name;private String description;
}

总结

  1. 实体类制作

  2. 使用lombok简化开发

    • 导入lombok无需指定版本,由SpringBoot提供版本

    • @Data注解

四、数据层开发--基础CRUD

步骤①:导入MyBatisPlus与Druid对应的starter,当然mysql的驱动不能少

<dependencies><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.6</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>
</dependencies>

步骤②:配置数据库连接相关的数据源配置

server:port: 80spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTCusername: rootpassword: root

步骤③:使用MP的标准通用接口BaseMapper加速开发,别忘了@Mapper和泛型的指定

@Mapper
public interface BookDao extends BaseMapper<Book> {
}

步骤④:制作测试类测试结果,这个测试类制作是个好习惯,不过在企业开发中往往都为加速开发跳过此步,且行且珍惜吧

package com.itheima.dao;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class BookDaoTestCase {@Autowiredprivate BookDao bookDao;@Testvoid testGetById(){System.out.println(bookDao.selectById(1));}@Testvoid testSave(){Book book = new Book();book.setType("测试数据123");book.setName("测试数据123");book.setDescription("测试数据123");bookDao.insert(book);}@Testvoid testUpdate(){Book book = new Book();book.setId(17);book.setType("测试数据abcdefg");book.setName("测试数据123");book.setDescription("测试数据123");bookDao.updateById(book);}@Testvoid testDelete(){bookDao.deleteById(16);}@Testvoid testGetAll(){bookDao.selectList(null);}
}

步骤⑤:在配置文件中设置自增id

温馨提示

MP技术默认的主键生成策略为雪花算法,生成的主键ID长度较大,和目前的数据库设定规则不相符,需要配置一下使MP使用数据库的主键生成策略,方式嘛还是老一套,做配置。在application.yml中添加对应配置即可,具体如下

server:port: 80spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTCusername: rootpassword: rootmybatis-plus:global-config:db-config:table-prefix: tbl_		#设置表名通用前缀id-type: auto				#设置主键id字段的生成策略为参照数据库设定的策略,当前数据库设置id生成策略为自增

步骤⑥:查看MP运行日志

mybatis-plus:global-config:db-config:table-prefix: tbl_id-type: autoconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

总结

  1. 手工导入starter坐标(2个),mysql驱动(1个)

  2. 配置数据源与MyBatisPlus对应的配置

  3. 开发Dao接口(继承BaseMapper)

  4. 制作测试类测试Dao功能是否有效

  5. 使用配置方式开启日志,设置日志输出方式为标准输出即可查阅SQL执行日志

五、数据层开发--分页功能制作

MyBatisPlus提供的分页操作API如下:

@Test
void testGetPage(){IPage page = new Page(2,5);bookDao.selectPage(page, null);System.out.println(page.getCurrent());System.out.println(page.getSize());System.out.println(page.getTotal());System.out.println(page.getPages());System.out.println(page.getRecords());
}

1. IPage 

selectPage需要导入的是Ipage对象

IPage(当前显示第几页,每页显示几条数据)

IPage page = new Page(2,5);

将该对象传入到查询方法selectPage后,可以得到查询结果,但是我们会发现当前操作查询结果返回值仍然是一个IPage对象,这又是怎么回事?

IPage page = bookDao.selectPage(page, null);

原来这个IPage对象中封装了若干个数据,而查询的结果作为IPage对象封装的一个数据存在的,可以理解为查询结果得到后,又塞到了这个IPage对象中,其实还是为了高度的封装,一个IPage描述了分页所有的信息。

@Test
void testGetPage(){IPage page = new Page(2,5);bookDao.selectPage(page, null);System.out.println(page.getCurrent());		//当前页码值System.out.println(page.getSize());			//每页显示数System.out.println(page.getTotal());		//数据总量System.out.println(page.getPages());		//总页数System.out.println(page.getRecords());		//详细数据
}

2. 定义MyBatisPlus拦截器并将其设置为Spring管控的bean

@Configuration
public class MPConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}
}
    @GetMapping("{currentPage}/{pageSize}")public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){//IPage(当前显示第几页,每页显示几条数据)IPage<Book> page = bookService.getPage(currentPage, pageSize);//最后的封装page = bookService.getPage((int)page.getPages(), pageSize);return new R(true, page);}

总结

  1. 使用IPage封装分页数据

  2. 分页操作依赖MyBatisPlus分页拦截器实现功能

  3. 借助MyBatisPlus日志查阅执行SQL语句

六、数据层开发--条件查询功能制作

1. QueryWrapper:条件查询

执行一个模糊匹配对应的操作,由like条件书写变为了like方法的调用

@Test
void testGetBy(){QueryWrapper<Book> qw = new QueryWrapper<>();qw.like("name","Spring");bookDao.selectList(qw);
}

第一句QueryWrapper对象是一个用于封装查询条件的对象,该对象可以动态使用API调用的方法添加条件,最终转化成对应的SQL语句。第二句就是一个条件了,需要什么条件,使用QueryWapper对象直接调用对应操作即可。比如做大于小于关系,就可以使用lt或gt方法,等于使用eq方法

缺点:

比如查询字段name,当前是以字符串的形态书写的,万一写错,编译器还没有办法发现,只能将问题抛到运行器通过异常堆栈告诉开发者,不太友好。

2. LambdaQueryWrapper

MyBatisPlus针对字段检查进行了功能升级,全面支持Lambda表达式,就有了下面这组API。由QueryWrapper对象升级为LambdaQueryWrapper对象,这下就避免了上述问题的出现。

@Test
void testGetBy2(){String name = "1";LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();lqw.like(Book::getName,name);bookDao.selectList(lqw);
}

为了便于开发者动态拼写SQL,防止将null数据作为条件使用,MyBatisPlus还提供了动态拼装SQL的快捷书写方式。

@Test
void testGetBy2(){String name = "1";LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();//if(name != null) lqw.like(Book::getName,name);		//方式一:JAVA代码控制lqw.like(name != null,Book::getName,name);				//方式二:API接口提供控制开关bookDao.selectList(lqw);
}

总结

  1. 使用QueryWrapper对象封装查询条件

  2. 推荐使用LambdaQueryWrapper对象

  3. 所有查询操作封装成方法调用

  4. 查询条件支持动态条件拼装

七、业务层开发

组织业务逻辑功能,并根据业务需求,对数据持久层发起调用

1.一个常识性的知识普及一下,业务层的方法名定义一定要与业务有关

login(String username,String password);

2. 而数据层的方法名定义一定与业务无关,是一定,不是可能,也不是有可能,例如根据用户名密码查询

selectByUserNameAndPassword(String username,String password);

1.使用普通方法:

业务层接口

public interface BookService {Boolean save(Book book);Boolean update(Book book);Boolean delete(Integer id);Book getById(Integer id);List<Book> getAll();IPage<Book> getPage(int currentPage,int pageSize);
}

业务层

实现类如下,转调数据层即可

@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;@Overridepublic Boolean save(Book book) {return bookDao.insert(book) > 0;}@Overridepublic Boolean update(Book book) {return bookDao.updateById(book) > 0;}@Overridepublic Boolean delete(Integer id) {return bookDao.deleteById(id) > 0;}@Overridepublic Book getById(Integer id) {return bookDao.selectById(id);}@Overridepublic List<Book> getAll() {return bookDao.selectList(null);}@Overridepublic IPage<Book> getPage(int currentPage, int pageSize) {IPage page = new Page(currentPage,pageSize);bookDao.selectPage(page,null);return page;}
}

测试

@SpringBootTest
public class BookServiceTest {@Autowiredprivate IBookService bookService;@Testvoid testGetById(){System.out.println(bookService.getById(4));}@Testvoid testSave(){Book book = new Book();book.setType("测试数据123");book.setName("测试数据123");book.setDescription("测试数据123");bookService.save(book);}@Testvoid testUpdate(){Book book = new Book();book.setId(17);book.setType("-----------------");book.setName("测试数据123");book.setDescription("测试数据123");bookService.updateById(book);}@Testvoid testDelete(){bookService.removeById(18);}@Testvoid testGetAll(){bookService.list();}@Testvoid testGetPage(){IPage<Book> page = new Page<Book>(2,5);bookService.page(page);System.out.println(page.getCurrent());System.out.println(page.getSize());System.out.println(page.getTotal());System.out.println(page.getPages());System.out.println(page.getRecords());}}

总结

  1. Service接口名称定义成业务名称,并与Dao接口名称进行区分

  2. 制作测试类测试Service功能是否有效

2.使用Mybatis-plus进行开发

业务层接口快速开发:IService<Book>

public interface IBookService extends IService<Book> {//添加非通用操作API接口
}

业务层接口实现类:ServiceImpl<BookDao,Book>

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {@Autowiredprivate BookDao bookDao;//添加非通用操作API
}

总结

  1. 使用通用接口(ISerivce<T>)快速开发Service

  2. 使用通用实现类(ServiceImpl<M,T>)快速开发ServiceImpl

  3. 可以在通用接口基础上做功能重载或功能追加

  4. 注意重载时不要覆盖原始操作,避免原始提供的功能丢失

八、表现层开发

@RestController
@RequestMapping("/books")
public class BookController2 {@Autowiredprivate IBookService bookService;@GetMappingpublic List<Book> getAll(){return bookService.list();}@PostMappingpublic Boolean save(@RequestBody Book book){return bookService.save(book);}@PutMappingpublic Boolean update(@RequestBody Book book){return bookService.modify(book);}@DeleteMapping("{id}")public Boolean delete(@PathVariable Integer id){return bookService.delete(id);}@GetMapping("{id}")public Book getById(@PathVariable Integer id){return bookService.getById(id);}@GetMapping("{currentPage}/{pageSize}")public IPage<Book> getPage(@PathVariable int currentPage,@PathVariable int pageSize){return bookService.getPage(currentPage,pageSize, null);}
}

总结

  1. 基于Restful制作表现层接口

    • 新增:POST

    • 删除:DELETE

    • 修改:PUT

    • 查询:GET

  2. 接收参数

    • 实体数据:@RequestBody

    • 路径变量:@PathVariable

九、表现层消息一致性处理(Result)

1.未统一时

增删改操作结果

true

查询单个数据操作结果

{"id": 1,"type": "计算机理论","name": "Spring实战 第5版","description": "Spring入门经典教程"
}

查询全部数据操作结果

[{"id": 1,"type": "计算机理论","name": "Spring实战 第5版","description": "Spring入门经典教程"},{"id": 2,"type": "计算机理论","name": "Spring 5核心原理与30个类手写实战","description": "十年沉淀之作"}
]

每种不同操作返回的数据格式都不一样,而且还不知道以后还会有什么格式,这样的结果让前端人员看了是很容易让人崩溃的,必须将所有操作的操作结果数据格式统一起来,需要设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议

2.前后端数据协议

@Data
public class R {private Boolean flag;private Object data;
}

其中flag用于标识操作是否成功,data用于封装操作数据,现在的数据格式就变了

{"flag": true,"data":{"id": 1,"type": "计算机理论","name": "Spring实战 第5版","description": "Spring入门经典教程"}
}

表现层开发格式也需要转换一下

总结

  1. 设计统一的返回值结果类型便于前端开发读取数据

  2. 返回值结果类型可以根据需求自行设定,没有固定格式

  3. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议

十、前后端连桥测试

将前端人员开发的页面保存到lresources目录下的static目录中,建议执行maven的clean生命周期,避免缓存的问题出现。

在进行具体的功能开发之前,先做联通性的测试,通过页面发送异步提交(axios),这一步调试通过后再进行进一步的功能开发。

//列表
getAll() {axios.get("/books").then((res)=>{console.log(res.data);});
},

只要后台代码能够正常工作,前端能够在日志中接收到数据,就证明前后端是通的,也就可以进行下一步的功能开发了。

总结

  1. 单体项目中页面放置在resources/static目录下

  2. created钩子函数用于初始化页面时发起调用

  3. 页面使用axios发送异步请求获取数据后确认前后端是否联通

十一、页面基础功能开发

1. 列表功能(非分页版)

列表功能主要操作就是加载完数据,将数据展示到页面上,此处要利用VUE的数据模型绑定,发送请求得到数据,然后页面上读取指定数据即可。

页面数据模型定义

data:{dataList: [],		//当前页要展示的列表数据...
},

异步请求获取数据

//列表
getAll() {axios.get("/books").then((res)=>{this.dataList = res.data.data;});
},

总结:

  1. 将查询数据返回到页面,利用前端数据绑定进行数据展示

2.添加功能

添加功能用于收集数据的表单是通过一个弹窗展示的,因此在添加操作前首先要进行弹窗的展示,添加后隐藏弹窗即可。因为这个弹窗一直存在,因此当页面加载时首先设置这个弹窗为不可显示状态,需要展示,切换状态即可。

默认状态

data:{dialogFormVisible: false,	//添加表单是否可见...
},

切换为显示状态

//弹出添加窗口
handleCreate() {this.dialogFormVisible = true;
},

由于每次添加数据都是使用同一个弹窗录入数据,所以每次操作的痕迹将在下一次操作时展示出来,需要在每次操作之前清理掉上次操作的痕迹。

定义清理数据操作

//重置表单
resetForm() {this.formData = {};
},

切换弹窗状态时清理数据

至此准备工作完成,下面就要调用后台完成添加操作了。

添加操作

//添加
handleAdd () {//发送异步请求axios.post("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层,显示数据if(res.data.flag){this.dialogFormVisible = false;this.$message.success("添加成功");}else {this.$message.error("添加失败");}}).finally(()=>{this.getAll();});
},
  1. 将要保存的数据传递到后台,通过post请求的第二个参数传递json数据到后台

  2. 根据返回的操作结果决定下一步操作

    • 如何是true就关闭添加窗口,显示添加成功的消息

    • 如果是false保留添加窗口,显示添加失败的消息

  3. 无论添加是否成功,页面均进行刷新,动态加载数据(对getAll操作发起调用)

取消添加操作

//取消
cancel(){this.dialogFormVisible = false;this.$message.info("操作取消");
},

总结

  1. 请求方式使用POST调用后台对应操作

  2. 添加操作结束后动态刷新页面加载数据

  3. 根据操作结果不同,显示对应的提示信息

  4. 弹出添加Div时清除表单数据

3 .删除功能

模仿添加操作制作删除功能,差别之处在于删除操作仅传递一个待删除的数据id到后台即可。

删除操作

// 删除
handleDelete(row) {axios.delete("/books/"+row.id).then((res)=>{if(res.data.flag){this.$message.success("删除成功");}else{this.$message.error("删除失败");}}).finally(()=>{this.getAll();});
},

删除操作提示信息 :this.$confirm("",{})

// 删除
handleDelete(row) {//1.弹出提示框this.$confirm("此操作永久删除当前数据,是否继续?","提示",{type:'info'}).then(()=>{//2.做删除业务axios.delete("/books/"+row.id).then((res)=>{if(res.data.flag){this.$message.success("删除成功");}else{this.$message.error("删除失败");}}).finally(()=>{this.getAll();});}).catch(()=>{//3.取消删除this.$message.info("取消删除操作");});
},	

总结

  1. 请求方式使用Delete调用后台对应操作

  2. 删除操作需要传递当前行数据对应的id值到后台

  3. 删除操作结束后动态刷新页面加载数据

  4. 根据操作结果不同,显示对应的提示信息

  5. 删除操作前弹出提示框避免误操作

4.修改功能

  1. 页面也需要有一个弹窗用来加载修改的数据,这一点与添加相同,都是要弹窗

  2. 弹出窗口中要加载待修改的数据,而数据需要通过查询得到,这一点与查询全部相同,都是要查数据

  3. 查询操作需要将要修改的数据id发送到后台,这一点与删除相同,都是传递id到后台

  4. 查询得到数据后需要展示到弹窗中,这一点与查询全部相同,都是要通过数据模型绑定展示数据

  5. 修改数据时需要将被修改的数据传递到后台,这一点与添加相同,都是要传递数据

    所以整体上来看,修改功能就是前面几个功能的大合体

查询并展示数据

//弹出编辑窗口
handleUpdate(row) {axios.get("/books/"+row.id).then((res)=>{if(res.data.flag){//展示弹层,加载数据this.formData = res.data.data;this.dialogFormVisible4Edit = true;}else{this.$message.error("数据同步失败,自动刷新");}});
},

修改操作

//修改
handleEdit() {axios.put("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层并刷新页面if(res.data.flag){this.dialogFormVisible4Edit = false;this.$message.success("修改成功");}else {this.$message.error("修改失败,请重试");}}).finally(()=>{this.getAll();});
},

总结

  1. 加载要修改数据通过传递当前行数据对应的id值到后台查询数据(同删除与查询全部)

  2. 利用前端双向数据绑定将查询到的数据进行回显(同查询全部)

  3. 请求方式使用PUT调用后台对应操作(同新增传递数据)

  4. 修改操作结束后动态刷新页面加载数据(同新增)

  5. 根据操作结果不同,显示对应的提示信息(同新增)

十二、业务消息一致性处理

{"timestamp": "2021-09-15T03:27:31.038+00:00","status": 500,"error": "Internal Server Error","path": "/books"
}

面对这种情况,前端的同学又不会了,这又是什么格式?怎么和之前的格式不一样?

{"flag": true,"data":{"id": 1,"type": "计算机理论","name": "Spring实战 第5版","description": "Spring入门经典教程"}
}

看来不仅要对正确的操作数据格式做处理,还要对错误的操作数据格式做同样的格式处理。

首先在当前的数据结果中添加消息字段,用来兼容后台出现的操作消息。

@Data
public class R{private Boolean flag;private Object data;private String msg;		//用于封装消息
}

后台代码也要根据情况做处理,当前是模拟的错误。

@PostMapping
public R save(@RequestBody Book book) throws IOException {Boolean flag = bookService.insert(book);return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
}

然后在表现层做统一的异常处理,使用SpringMVC提供的异常处理器做统一的异常处理。

@RestControllerAdvice
public class ProjectExceptionAdvice {@ExceptionHandler(Exception.class)public R doOtherException(Exception ex){//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员ex.printStackTrace();return new R(false,null,"系统错误,请稍后再试!");}
}

页面上得到数据后,先判定是否有后台传递过来的消息,标志就是当前操作是否成功,如果返回操作结果false,就读取后台传递的消息。

//添加
handleAdd () {//发送ajax请求axios.post("/books",this.formData).then((res)=>{//如果操作成功,关闭弹层,显示数据if(res.data.flag){this.dialogFormVisible = false;this.$message.success("添加成功");}else {this.$message.error(res.data.msg);			//消息来自于后台传递过来,而非固定内容}}).finally(()=>{this.getAll();});
},

总结

  1. 使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的

  2. 异常处理器必须被扫描加载,否则无法生效

  3. 表现层返回结果的模型类中添加消息属性用来传递消息到页面

十三、页面功能开发

1. 分页功能

分页功能的制作用于替换前面的查询全部,其中要使用到elementUI提供的分页组件。

<!--分页组件-->
<div class="pagination-container"><el-paginationclass="pagiantion"@current-change="handleCurrentChange":current-page="pagination.currentPage":page-size="pagination.pageSize"layout="total, prev, pager, next, jumper":total="pagination.total"></el-pagination>
</div>

为了配合分页组件,封装分页对应的数据模型。

data:{pagination: {	//分页相关模型数据currentPage: 1,	//当前页码pageSize:10,	//每页显示的记录数total:0,		//总记录数}
},

修改查询全部功能为分页查询,通过路径变量传递页码信息参数

getAll() {axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {});
},

后台提供对应的分页功能。

@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){IPage<Book> pageBook = bookService.getPage(currentPage, pageSize);return new R(null != pageBook ,pageBook);
}

页面根据分页操作结果读取对应数据,并进行数据模型绑定。

getAll() {axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {this.pagination.total = res.data.data.total;this.pagination.currentPage = res.data.data.current;this.pagination.pagesize = res.data.data.size;this.dataList = res.data.data.records;});
},

对切换页码操作设置调用当前分页操作。

//切换页码
handleCurrentChange(currentPage) {this.pagination.currentPage = currentPage;this.getAll();
},

总结

  1. 使用el分页组件

  2. 定义分页组件绑定的数据模型

  3. 异步调用获取分页数据

  4. 分页数据页面回显

2. 删除功能维护

由于使用了分页功能,当最后一页只有一条数据时,删除操作就会出现BUG,最后一页无数据但是独立展示,对分页查询功能进行后台功能维护,如果当前页码值大于最大页码值,重新执行查询。其实这个问题解决方案很多,这里给出比较简单的一种处理方案。

@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){IPage<Book> page = bookService.getPage(currentPage, pageSize);//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值if( currentPage > page.getPages()){page = bookService.getPage((int)page.getPages(), pageSize);}return new R(true, page);
}

3.条件查询功能

最后一个功能来做条件查询,其实条件查询可以理解为分页查询的时候除了携带分页数据再多带几个数据的查询。这些多带的数据就是查询条件。比较一下不带条件的分页查询与带条件的分页查询差别之处,这个功能就好做了

  • 页面封装的数据:带不带条件影响的仅仅是一次性传递到后台的数据总量,由传递2个分页相关数据转换成2个分页数据加若干个条件

  • 后台查询功能:查询时由不带条件,转换成带条件,反正不带条件的时候查询条件对象使用的是null,现在换成具体条件,差别不大

  • 查询结果:不管带不带条件,出来的数据只是有数量上的差别,其他都差别,这个可以忽略

    经过上述分析,看来需要在页面发送请求的格式方面做一定的修改,后台的调用数据层操作时发送修改,其他没有区别。

    页面发送请求时,两个分页数据仍然使用路径变量,其他条件采用动态拼装url参数的形式传递。

页面封装查询条件字段

pagination: {		
//分页相关模型数据currentPage: 1,		//当前页码pageSize:10,		//每页显示的记录数total:0,			//总记录数name: "",type: "",description: ""
},

页面添加查询条件字段对应的数据模型绑定名称

<div class="filter-container"><el-input placeholder="图书类别" v-model="pagination.type" class="filter-item"/><el-input placeholder="图书名称" v-model="pagination.name" class="filter-item"/><el-input placeholder="图书描述" v-model="pagination.description" class="filter-item"/><el-button @click="getAll()" class="dalfBut">查询</el-button><el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
</div>

将查询条件组织成url参数,添加到请求url地址中,这里可以借助其他类库快速开发,当前使用手工形式拼接,降低学习要求

getAll() {//1.获取查询条件,拼接查询条件param = "?name="+this.pagination.name;param += "&type="+this.pagination.type;param += "&description="+this.pagination.description;console.log("-----------------"+ param);axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res) => {this.dataList = res.data.data.records;});
},

后台代码中定义实体类封查询条件

@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {System.out.println("参数=====>"+book);IPage<Book> pageBook = bookService.getPage(currentPage,pageSize);return new R(null != pageBook ,pageBook);
}

对应业务层接口与实现类进行修正

public interface IBookService extends IService<Book> {IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook);
}
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {public IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook){IPage page = new Page(currentPage,pageSize);LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();lqw.like(Strings.isNotEmpty(queryBook.getName()),Book::getName,queryBook.getName());lqw.like(Strings.isNotEmpty(queryBook.getType()),Book::getType,queryBook.getType());lqw.like(Strings.isNotEmpty(queryBook.getDescription()),Book::getDescription,queryBook.getDescription());return bookDao.selectPage(page,lqw);}
}

页面回显数据

getAll() {//1.获取查询条件,拼接查询条件param = "?name="+this.pagination.name;param += "&type="+this.pagination.type;param += "&description="+this.pagination.description;console.log("-----------------"+ param);axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res) => {this.pagination.total = res.data.data.total;this.pagination.currentPage = res.data.data.current;this.pagination.pagesize = res.data.data.size;this.dataList = res.data.data.records;});
},

总结

  1. 定义查询条件数据模型(当前封装到分页数据模型中)

  2. 异步调用分页功能并通过请求参数传递数据到后台

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Two Stream network (Tsn)】(二) 阅读笔记

贡献 将深度神经网络应用于视频动作识别的难点&#xff0c;是如何同时利用好静止图像上的 appearance information以及物体之间的运动信息motion information。本文主要有三点贡献&#xff1a; 1.提出了一种融合时间流和空间流的双流网络&#xff1b; 2.证明了直接在光流上训…

【Spring面试】五、Bean扩展、JavaConfig、@Import

文章目录 Q1、如何在Spring创建完所有的Bean之后做扩展&#xff1f;Q2、Spring容器启动时&#xff0c;为什么先加载BeanFactoryPostProcess?Q3、Bean的生产顺序是由什么决定的&#xff1f;Q4、Spring有哪几种配置方式Q5、JavaConfig是如何替代spring.xml的&#xff1f;Q6、Com…

SpringMVC基础入门及工作流程---全方面详细介绍

一&#xff0c;SpringMVC概念 Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;通过把Model&#xff0c;View&#xff0c;Controller分离&#xff0c;将web层进行职责解耦&#xff0c;把复杂的web应用分成逻辑清晰的几部分&#xff0c;简…

赋能工业物联网 | 数据驱动,加速智能制造

行业背景 工业物联网场景下&#xff0c;随着智能设备及物联网技术的广泛应用&#xff0c;数据是最重要的资源之一&#xff0c;企业需要对各种机器、设备和传感器产生的时序数据进行采集、存储与分析。 因此&#xff0c;何为行之有效的数据库解决方案&#xff1f;企业普遍更为…

MySQL--数据库基础

数据库分类 数据库大体可以分为 关系型数据库 和 非关系型数据库 常用数据类型 数值类型&#xff1a; 分为整型和浮点型&#xff1a; 字符串类型 日期类型

【PTA】浙江大学计算机与软件学院2019年考研复试上机自测

个人学习记录&#xff0c;代码难免不尽人意。 呃&#xff0c;今天做了做19年的复试上机题&#xff0c;死在hash表上了&#xff0c;后面详细解释。心态要好&#xff0c;心态要好 7-1 Conway’s Conjecture John Horton Conway, a British mathematician active in recreational…

SpringMVCJReble的使用文件的上传下载

目录 前言 一、JReble的使用 1.IDea内安装插件 2.激活 3.离线使用 使用JRebel的优势 二、文件上传与下载 1 .导入pom依赖 2.配置文件上传解析器 3.数据表 4.配置文件 5.前端jsp页面 6.controller层 7.测试结果 前言 当涉及到Web应用程序的开发时&…

AI人工智能Mojo语言:AI的新编程语言

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 Mojo的主要功能包括&#xff1a; 类似Python的语法和动态类型使Python开发人员易于学习Mojo&#xff0c;因为Python是现代AI / ML开发背后的主要编程语言。使用Mojo&#xff0c;您可以导入和使用任何Python库&#xf…

设计模式之外观模式

文章目录 影院管理项目传统方式解决影院管理传统方式解决影院管理问题分析外观模式基本介绍外观模式原理类图外观模式解决影院管理传统方式解决影院管理说明外观模式应用实例 外观模式的注意事项和细节 影院管理项目 组建一个家庭影院&#xff1a; DVD 播放器、投影仪、自动屏…

FPGA通信—千兆网(UDP)软件设计

一、PHY引脚功能描述 引脚功能描述1CLK25 CLK125:内部PLL生成的125MHz参考时钟&#xff0c;如MAC未使用125MHe时钟&#xff0c;则此引脚应保持浮动&#xff0c; 2 4 63 GND 接地3REG OUT开关压器&#xff0c;1.05V输出 5 6 8 9 11 12 14 15 MDI[0] MDI[0]- MDI[1] MDI[1…

Redis Redis介绍、安装 - Redis客户端

目录 redis是什么&#xff0c;他的应用场景是什么&#xff1f; Redis的一些主要特点和应用场景&#xff1a; redis的官方网站&#xff1a;Redis redis是键值型数据库&#xff1a;&#xff08;也就是key-value模式&#xff09;&#xff08;跟python的字典很像&#xff09; …

Web server failed to start. Port 8080 was already in use.之解决方法

问题&#xff1a; Web server failed to start. Port 8080 was already in use&#xff0c;这句错误描述意思是当前程序的端口号8080被占用了&#xff0c;需要将占用该端口的程序停止掉才行&#xff1b;错误如图所示&#xff1a; 解决方法&#xff1a; 按住winr&#xff0c;输入…

【大虾送书第九期】速学Linux:系统应用从入门到精通

目录 &#x1f36d;写在前面 &#x1f36d;为什么学习Linux系统 &#x1f36d;Linux系统的应用领域 &#x1f36c;&#xff11;.Linux在服务器的应用 &#x1f36c;&#xff12;.嵌入式Linux的应用 &#x1f36c;&#xff13;.桌面Linux的应用 &#x1f36d;Linux的版本选择 &a…

深入浅出PyTorch函数torch.rand与torch.randn

torch.rand 和 torch.randn 都是PyTorch中用于生成随机张量的函数&#xff0c;但它们生成随机数的方式有所不同。 一、torch.rand torch.rand 生成在区间 [0, 1) 内均匀分布的随机数。 size 参数是一个表示所需张量形状的元组或整数。可以生成任何形状的随机张量。 二、torch.…

C++的运算符重载介绍

所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。 实际上,我们已经在不知不觉中使用了运算符重载。例如,+号可以对…

javaee springMVC model的使用

项目结构图 pom依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org…

2020年12月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:数组指定部分逆序重放 将一个数组中的前k项按逆序重新存放。例如,将数组8,6,5,4,1前3项逆序重放得到5,6,8,4,1。 时间限制:1000 内存限制:65536 输入 输入为两行: 第一行两个整数,以空格分隔,分别为数组元素的个数n(1 < n…

[EROOR] SpringMVC之500 回调函数报错

首先&#xff0c;检查一下idea里面的报错的原因&#xff0c;我的是jdk的版本的问题。所以更换一下就可以了。

JavaScipt中如何实现函数缓存?函数缓存有哪些场景?

1、函数缓存是什么&#xff1f; 函数缓存就是将函数运行的结果进行缓存。本质上就是用空间&#xff08;缓存存储&#xff09;换时间&#xff08;计算过程&#xff09; 常用于缓存数据计算结果和缓存对象。 缓存只是一个临时的数据存储&#xff0c;它保存数据&#xff0c;以便将…

平衡二叉搜索树(AVL)——【C++实现插入、删除等操作】

本章完整代码gitee地址&#xff1a;平衡二叉搜索树 文章目录 &#x1f333;0. 前言&#x1f332;1. AVL树概念&#x1f334;2. 实现AVL树&#x1f33f;2.1 结构定义&#x1f33f;2.2 插入&#x1f490;左单旋&#x1f490;右单旋&#x1f490;左右双旋&#x1f490;右左双旋 &a…