基于 SpringBoot 的 SSMP 整合案例
一个简单的图书管理demo,基于 SSMP(Spring+SpringMVC+MyBatis-Plus)的CRUD (增删改查) 模块
案例实现方案分析与流程解析
案例实现方案分析
- 实体类开发————使用Lombok快速制作实体类
- Dao开发————整合MyBatisPlus,制作数据层测试类
- Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
- Controller开发————基于Restful开发,使用PostMan测试接口功能
- Controller开发————前后端开发协议制作
- 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
- 列表、新增、修改、删除、分页、查询
- 项目异常处理
- 按条件查询————页面功能调整、Controller修正功能、Service修正功能
SSMP案例制作流程解析
- 先开发基础CRUD功能,做一层测一层
- 调通页面,确认异步提交成功后,制作所有功能
- 添加分页功能与查询功能
模块创建
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--MyBatisPlus对应starter-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--druid对应starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
tbl_book.sql
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 = MyISAM AUTO_INCREMENT = 19 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Java编程思想(第4版)', 'Java学习经典,殿堂级著作!赢得了全球程序员的广泛赞誉。');
INSERT INTO `tbl_book` VALUES (2, '计算机理论', '计算机组成原理', '资深的计算机体系结构教育家Alan Clements博士编写,涵盖计算机系统的组成和体系结构的基本概念、指令系统以及处理器实现等涉及计算机组成原理课程的内容。');
INSERT INTO `tbl_book` VALUES (3, '程序设计', 'C++ Primer Plus 第6版 中文版', 'C++程序设计经典教程,畅销30年的C++大百科全书全新升级,经典C++入门教程十年新版再现');
INSERT INTO `tbl_book` VALUES (4, '程序设计', 'RocketMQ技术内幕:RocketMQ架构设计与实现原理(第2版)', '畅销书升级,RocketMQ创始人高度评价,深入源码分析技术架构和实现原理,打造高性能、高可用、高吞吐量、低延迟RocketMQ');
INSERT INTO `tbl_book` VALUES (5, '程序设计', ' 深入理解Java虚拟机:JVM高级特性与实践(第3版)', '周志明虚拟机新作,第3版新增内容近50%,5个维度全面剖析JVM,大厂面试知识点全覆盖。与 Java编程思想、Effective Java、Java核心技术 堪称:Java四大名著');
INSERT INTO `tbl_book` VALUES (6, '历史', '见识城邦·人类简史:从动物到上帝(新版)', '以色列新锐历史学家尤瓦尔·赫拉利代表作,第十届文津图书奖获奖作品');
INSERT INTO `tbl_book` VALUES (7, '历史', '中国通史', '吕思勉先生写给普通读者的中国通史入门书,用白话文写成的中国通史,把历史从“帝王的家谱”转变为人类的进化史');
INSERT INTO `tbl_book` VALUES (8, '哲学', '理想国(柏拉图代表作)', '奠定西方哲学史的源流之作。2021新译本,以斯灵斯校勘本为底本,遵照“字对字”的原则,从古希腊原文直译,兼顾准确性和语言通顺性,助你读懂理想国。');
INSERT INTO `tbl_book` VALUES (9, '哲学', '苏格拉底的申辩', '《柏拉图注疏集:苏格拉底的申辩》记述的是公元前399年,一个叫莫勒图斯的年轻人在雅典状告哲学家苏格拉底,说他不信城邦诸神,引进新的精灵之事,败坏青年。 于是,苏格拉底被传讯,在500人组成的陪审团面前作了著名的申辩。');
INSERT INTO `tbl_book` VALUES (10, '文学', '鲁迅全集', '大师全集,完整收录,鲁迅毕生之心血尽归于此。\r\n\r\n 鲁迅是中国20世纪的文学家、思想家、革命家,中国近代文学巨匠。他早年留学于日本,后来弃医从文,他用笔耕不辍的文字为新一代青年们指引方向,在国内外思想文化领域有着极高的声誉。');
INSERT INTO `tbl_book` VALUES (11, '文学', '人间清醒', '茅盾文学奖获得者梁晓声2021全新力作');
INSERT INTO `tbl_book` VALUES (12, '经济', '八次危机:中国的真实经验1949-2009', '著名“三农”专家温铁军,用经济的独特视角,重新审视中国的1949-2009,历史给我了我们怎样的真实经验?');
SET FOREIGN_KEY_CHECKS = 1;
application.yml
# 设置端口为80方便访问
server:
port: 80
小结
- 勾选 SpringMVC 与 MySQL 坐标
- 修改配置文件为 yml 格式
- 设置端口为80方便访问
实体类快速开发(lombok)
lombok
,一个Java类库,提供了一组注解,简化POJO实体类开发
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
lombok
版本由 SpringBoot 提供,无需指定版本
- 常用注解:
@Data
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
- 为当前实体类在编译期设置对应的
get/set方法
、toString方法
、hashCode方法
、equals方法
等
小结
- 实体类制作
- 使用lombok简化开发
- 导入lombok无需指定版本,由SpringBoot提供版本
- @Data 注解
数据层标准开发(基础 CRUD
)
- 导入 MyBatisPlus 与 Druid 对应的 starter
<!--MyBatisPlus对应starter-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--druid对应starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
- 配置 druid数据源 与 MyBatisPlus 对应的基础配置(id生成策略使用数据库
自增
策略)
# druid 数据源配置
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: 283619
# mybatis-plus 配置
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto # 主键自增策略
数据层接口
- 继承 BaseMapper 并指定泛型
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
编写测试用例
- 单个查询、全部查询、增删改查
@SpringBootTest
public class BookDaoTestCase {
@Autowired
private BookDao bookDao;
// 根据id查询单个数据
@Test
void testGetById() {
System.out.println(bookDao.selectById(1));
}
// 新增数据
@Test
void testSave() {
Book book = new Book();
book.setType("测试用例-小说");
book.setName("测试用例-狂人日记");
book.setDescription("鲁迅");
bookDao.insert(book);
}
// 更新操作
@Test
void testUpdate() {
Book book = new Book();
book.setId(13);
book.setType("测试用例-小说");
book.setName("测试用例-狂人日记");
book.setDescription("鲁迅-新文化运动");
bookDao.updateById(book);
}
// 删除操作
@Test
void testDelete() {
bookDao.deleteById(19);
}
// 查询全部数据
@Test
void testGetAll() {
List<Book> list = bookDao.selectList(null);
for (Book book : list) {
System.out.println(book);
}
}
}
小结
- 手工导入starter坐标(2个)
- 配置数据源与MyBatisPlus对应的配置
- 开发Dao接口(继承BaseMapper)
- 制作测试类测试Dao功能是否有效
开启 MyBatis-Plus 运行日志
- 为方便调试可以开启
MyBatis-Plus
的日志 - 使用配置方式开启日志,设置日志输出方式为
标准输出
# mybatis-plus 配置
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto # 主键自增策略
configuration:
# 开启MyBatisPlus的日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 标准输出
分页功能实现
- 分页操作需要设定分页对象 IPage
// 分页操作
@Test
void testGetPage() {
IPage page = new Page(1, 5);
bookDao.selectPage(page, null);
}
- IPage对象中封装了分页操作中的所有数据
- 数据
- 当前页码值
- 每页数据总量
- 最大页码值
- 数据总量
- 分页操作是在 MyBatis-Plus 的常规操作基础上增强得到,内部是动态的拼写SQL语句
,因此需要增强对应的功能,使用 MyBatis-Plus 拦截器实现
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1. 定义 Mybatis-Plus 拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 2. 添加具体的拦截器(分页拦截器)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
- 测试分页数据
// 分页操作
@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("获取分页查询数据:");
List<Book> books = page.getRecords();
for (Book book : books) {
System.out.println(book);
}
}
小结
- 使用IPage封装分页数据
- 分页操作依赖MyBatisPlus分页拦截器实现功能
- 借助MyBatisPlus日志查阅执行SQL语句
按条件查询
写法一
// 根据条件进行查询
@Test
void testGetBy() {
QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
// 查询包含"java"名称的书籍有哪些
queryWrapper.like("name", "java");
bookDao.selectList(queryWrapper);
}
写法二(推荐)
- 使用
QueryWrapper
对象封装查询条件,推荐使用LambdaQueryWrapper
对象,所有查询操作封装成方法调用
// 根据条件进行查询
@Test
void testGetBy2() {
LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
// 查询包含"java"名称的书籍有哪些
queryWrapper.like(Book::getName, "java");
bookDao.selectList(queryWrapper);
}
支持动态拼写查询条件(
业务开发一般是这种写法
)
- 当查询条件为空时,相当于查询全部数据
// 根据条件进行查询
@Test
void testGetBy3() {
String name = null;
LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
// 如果name为空,就不进行查询
queryWrapper.like(name != null, Book::getName, name);
bookDao.selectList(queryWrapper);
}
- 查询条件不为空时,进行条件查询
// 根据条件进行查询
@Test
void testGetBy3() {
String name = "中国";
LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
// 查询包含"中国"名称的书籍有哪些
// 如果name为空,就不进行查询
queryWrapper.like(name != null, Book::getName, name);
bookDao.selectList(queryWrapper);
}
小结
- 使用QueryWrapper对象封装查询条件
- 推荐使用LambdaQueryWrapper对象
- 所有查询操作封装成方法调用
- 查询条件支持动态条件拼装
业务层标准开发(基础 CRUD
)
- Service层接口定义与数据层接口定义具有较大区别,不要混用
selectByUserNameAndPassword(String username,String password);
数据层接口login(String username,String password);
Service层接口
接口定义
public interface BookService {
Boolean save(Book book);
Boolean update(Book book);
Boolean delete(Integer id);
Book getById(Integer id);
List<Book> getAll();
// currentPage:当前页码值
// pageSize:每页显示个数
IPage<Book> getPage(int currentPage, int pageSize);
}
实现类定义
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public Boolean save(Book book) {
// 当插入成功,返回的是影响行计数,大于0表示插入成功,否则插入失败
return bookDao.insert(book) > 0;
}
@Override
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
@Override
public List<Book> getAll() {
return bookDao.selectList(null);
}
// currentPage:当前页码值
// pageSize:每页显示个数
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage, pageSize);
bookDao.selectPage(page, null);
return page;
}
}
测试类定义
@SpringBootTest
public class BookServiceTestCase {
@Autowired
private BookService bookService;
@Test
void testGetById() {
System.out.println(bookService.getById(4));
}
@Test
void testSave() {
Book book = new Book();
book.setType("测试用例-小说");
book.setName("测试用例-狂人日记");
book.setDescription("鲁迅");
bookService.save(book);
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(20);
book.setType("测试用例-小说");
book.setName("测试用例-狂人日记");
book.setDescription("鲁迅-新文化运动");
bookService.update(book);
}
@Test
void testDelete() {
bookService.delete(20);
}
@Test
void testGetAll() {
System.out.println(bookService.getAll());
}
@Test
void testGetPage() {
IPage<Book> page = bookService.getPage(2, 5);
System.out.println("获取当前页:" + page.getCurrent());
System.out.println("获取每页显示个数:" + page.getSize());
System.out.println("获取总个数:" + page.getTotal());
System.out.println("获取总页数:" + page.getPages());
// 获取分页查询数据
System.out.println("获取分页查询数据:");
List<Book> books = page.getRecords();
for (Book book : books) {
System.out.println(book);
}
}
}
小结
- Service接口名称定义成业务名称,并与Dao接口名称进行区分
- 制作测试类测试Service功能是否有效
业务层快速开发(基于 MyBatis-Plus 构建)
快速开发方案
- 使用MyBatisPlus提供有业务层通用接口(
ISerivce<T>
)与业务层通用实现类(ServiceImpl<M,T>
) - 在通用类基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
接口定义
public interface IBookService extends IService<Book> {
}
- 接口追加功能
/**
* @author xiexu
* @create 2022-04-06 13:54
*/
public interface IBookService extends IService<Book> {
// 注意:追加的操作与原始操作通过名称区分,功能类似
// 新增
Boolean saveBook(Book book);
// 删除
Boolean delete(Integer id);
// 修改
Boolean modify(Book book);
// 根据id获取数据
Book get(Integer id);
// 分页查询
IPage<Book> getPage(int currentPage, int pageSize);
}
实现类定义
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
}
- 实现类追加功能
/**
* @author xiexu
* @create 2022-04-06 13:58
*/
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
@Autowired
private BookDao bookDao;
@Override
public Boolean saveBook(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
@Override
public Boolean modify(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public Book get(Integer id) {
return bookDao.selectById(id);
}
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage, pageSize);
bookDao.selectPage(page, null);
return page;
}
}
测试类定义
@SpringBootTest
public class BookServiceTest {
@Autowired
private IBookService bookService;
@Test
void testGetById() {
System.out.println(bookService.getById(4));
}
@Test
void testSave() {
Book book = new Book();
book.setType("测试用例-小说");
book.setName("测试用例-狂人日记");
book.setDescription("鲁迅");
bookService.save(book);
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(21);
book.setType("测试用例-小说");
book.setName("测试用例-狂人日记");
book.setDescription("鲁迅-新文化运动");
bookService.updateById(book);
}
@Test
void testDelete() {
bookService.removeById(21);
}
@Test
void testGetAll() {
System.out.println(bookService.list());
}
@Test
void testGetPage() {
IPage<Book> page = new Page<>(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("获取分页查询数据:");
List<Book> books = page.getRecords();
for (Book book : books) {
System.out.println(book);
}
}
}
小结
- 使用通用接口(ISerivce)快速开发Service
- 使用通用实现类(ServiceImpl<M,T>)快速开发ServiceImpl
- 可以在通用接口基础上做功能重载或功能追加
- 注意重载时
不要覆盖原始操作
,避免原始提供的功能丢失
表现层标准开发
- 基于Restful进行表现层接口开发
- 使用Postman测试表现层接口功能
表现层开发
/**
* @author xiexu
* @create 2022-04-07 10:09
*/
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public List<Book> getAll() {
return bookService.list();
}
@PostMapping
public Boolean save(@RequestBody Book book) {
return bookService.save(book);
}
@PutMapping
public Boolean update(@RequestBody Book book) {
return bookService.updateById(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);
}
}
- 使用
Postman
进行功能测试
小结
- 基于Restful制作表现层接口
- 新增:POST
- 删除:DELETE
- 修改:PUT
- 查询:GET
- 接收参数
- 实体数据:@RequestBody
- 路径变量:@PathVariable
表现层数据一致性处理(R对象)
之前的格式
增加一个 data 属性,把数据全部封装到 data 里
- 当数据为 null 可能出现的问题
- 查询id不存在的数据,返回 null
- 查询过程中抛出异常,catch 中返回 null
增加 一个状态属性
- 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
/**
* @author xiexu
* @create 2022-04-07 11:22
*/
@Data
public class R {
private Boolean flag;
private Object data;
public R() {
}
/**
* 不返回数据的构造方法
*
* @param flag
*/
public R(Boolean flag) {
this.flag = flag;
}
/**
* 返回数据的构造方法
*
* @param flag
* @param data
*/
public R(Boolean flag, Object data) {
this.flag = flag;
this.data = data;
}
}
- 表现层接口统一返回值类型结果
/**
* @author xiexu
* @create 2022-04-07 10:09
*/
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public R getAll() {
return new R(true, bookService.list());
}
@PostMapping
public R save(@RequestBody Book book) {
return new R(bookService.save(book));
}
@PutMapping
public R update(@RequestBody Book book) {
return new R(bookService.updateById(book));
}
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id) {
return new R(bookService.delete(id));
}
@GetMapping("/{id}")
public R getById(@PathVariable Integer id) {
return new R(true, bookService.getById(id));
}
@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return new R(true, bookService.getPage(currentPage, pageSize));
}
}
- 使用
Postman
进行功能测试
小结
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议
前后端调用
将提前编辑好的静态资源文件放入 resource/static
目录中,执行maven插件中的clean
脚本清除 target 中的项目缓存。
打开浏览器访问 localhost/pages/books.html
小结
- 前后端分离结构设计中页面归属前端服务器
- 单体工程中页面放置在resources目录下的static目录中(建议执行clean)
- 前端发送异步请求,调用后端接口
axios
发送异步请求
- 在
books.html
中修改对应位置的代码,执行完毕后可以看到浏览器控制台和IDEA控制台有数据信息显示。
//钩子函数,VUE对象初始化完成后自动执行
created() {
// 调用查询全部数据的操作
this.getAll();
},
methods: {
//列表
getAll() {
// 发送异步请求
axios.get("/books").then((res) => {
console.log(res.data);
});
},
小结
- 单体项目中页面放置在
resources/static
目录下 created
钩子函数用于初始化页面时发起调用- 页面使用
axios
发送异步请求获取数据后确认前后端是否联通
列表功能
列表页
//列表
getAll() {
// 发送异步请求
axios.get("/books").then((res) => {
// console.log(res.data);
this.dataList = res.data.data; // res.data是前端返回给后端的数据名
});
},
小结
- 将查询数据返回到页面,利用前端数据双向绑定进行数据展示
添加功能
- 弹出添加窗口
// 弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
- 清除数据
// 重置表单
resetForm() {
this.formData = {};
},
- 在弹出添加窗口时 清除数据
// 弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
- 发送添加请求
// 添加
handleAdd() {
// 发送异步请求
axios.post("/books", this.formData).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) {
// 1.关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功");
} else {
this.$message.error("添加失败");
}
}).finally(() => {
// 2.重新加载数据
this.getAll();
});
},
- 取消添加
// 取消
cancel() {
// 1.关闭弹层
this.dialogFormVisible = false;
// 2.提示用户
this.$message.info("当前操作取消");
},
小结
- 请求方式使用POST调用后台对应操作
- 添加操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 弹出添加Div时清除表单数据
删除功能
- 删除基本操作
// 发送异步请求
axios.delete("/books/" + row.id).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) { // res.data是前端返回给后端的数据名
this.$message.success("删除成功");
} else {
this.$message.error("删除失败");
}
}).finally(() => {
// 删除后需要重新加载数据
this.getAll();
});
- 加入确认删除对话框
// 删除
handleDelete(row) {
// 1.删除弹出框
this.$confirm("此操作将永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
// 2.发送异步请求
axios.delete("/books/" + row.id).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) { // res.data是前端返回给后端的数据名
this.$message.success("删除成功");
} else {
this.$message.error("删除失败");
}
}).finally(() => {
// 删除后需要重新加载数据
this.getAll();
});
}).catch(() => {
// 3.取消删除操作
this.$message.info("取消操作");
});
},
小结
- 请求方式使用Delete调用后台对应操作
- 删除操作需要传递当前行数据对应的id值到后台
- 删除操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 删除操作前弹出提示框避免误操作
修改功能
加载数据
- 弹出修改窗口
//弹出编辑窗口
handleUpdate(row) {
// 发送异步请求
axios.get("/books/" + row.id).then((res) => {
if (res.data.flag && res.data.data != null) {
// 展示弹层,加载数据
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
} else {
this.$message.error("数据同步失败,自动刷新");
}
}).finally(() => {
// 重新加载数据
this.getAll();
});
},
- 删除消息维护
// 删除
handleDelete(row) {
// 1.删除弹出框
this.$confirm("此操作将永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
// 2.发送异步请求
axios.delete("/books/" + row.id).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) { // res.data是前端返回给后端的数据名
this.$message.success("删除成功");
} else {
this.$message.error("数据同步失败,自动刷新");
}
}).finally(() => {
// 删除后需要重新加载数据
this.getAll();
});
}).catch(() => {
// 3.取消删除操作
this.$message.info("取消操作");
});
},
小结
- 加载要修改数据通过传递当前行数据对应的id值到后台查询数据
- 利用前端数据双向绑定将查询到的数据进行回显
修改功能
- 修改
//修改
handleEdit() {
// 发送异步请求
axios.put("/books", this.formData).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) {
// 1.关闭弹层
this.dialogFormVisible4Edit = false;
this.$message.success("修改成功");
} else {
this.$message.error("修改失败");
}
}).finally(() => {
// 2.重新加载数据
this.getAll();
});
},
- 取消添加和修改
//取消
cancel() {
// 1.关闭弹层
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
// 2.提示用户
this.$message.info("当前操作取消");
},
小结
- 请求方式使用PUT调用后台对应操作
- 修改操作结束后动态刷新页面加载数据(同新增)
- 根据操作结果不同,显示对应的提示信息(同新增)
异常消息处理
- 对异常进行统一处理,出现异常后,返回指定信息
// 作为springmvc的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
// 拦截所有的异常信息
@ExceptionHandler
public R doException(Exception ex) {
// 记录日志
// 通知运维
// 通知开发
ex.printStackTrace();
return new R(false, "服务器故障,请稍后再试!");
}
}
- 修改表现层返回结果的模型类,封装出现异常后对应的信息
- flag:false
- 消息(msg): 要显示信息
@Data
public class R {
private Boolean flag;
private Object data;
private String msg;
public R() {
}
/**
* 不返回数据的构造方法
*
* @param flag
*/
public R(Boolean flag) {
this.flag = flag;
}
/**
* 返回数据的构造方法
*
* @param flag
* @param data
*/
public R(Boolean flag, Object data) {
this.flag = flag;
this.data = data;
}
/**
* 返回异常信息的构造方法
*
* @param flag
* @param msg
*/
public R(Boolean flag, String msg) {
this.flag = flag;
this.msg = msg;
}
}
- 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
//添加
handleAdd() {
// 发送异步请求
axios.post("/books", this.formData).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) {
// 1.关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功");
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
// 2.重新加载数据
this.getAll();
});
},
- 可以在表现层Controller中进行消息统一处理
@PostMapping
public R save(@RequestBody Book book) throws Exception {
if (book.getName().equals("123")) {
throw new Exception();
}
boolean flag = bookService.save(book);
return new R(flag, flag ? "添加成功^_^" : "添加失败-_-!");
}
- 页面消息处理
//添加
handleAdd() {
// 发送异步请求
axios.post("/books", this.formData).then((res) => {
// 判断当前操作是否成功
if (res.data.flag) {
// 1.关闭弹层
this.dialogFormVisible = false;
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
// 2.重新加载数据
this.getAll();
});
},
小结
- 使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
- 异常处理器必须被扫描加载,否则无法生效
- 表现层返回结果的模型类中添加消息属性用来传递消息到页面
分页功能
- 页面使用el分页组件添加分页功能
<!--分页组件-->
<div class="pagination-container">
<el-pagination
class="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 getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return new R(true, bookService.getPage(currentPage, pageSize));
}
- 加载分页数据
//分页查询
getAll() {
// 发送异步请求
axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {
this.pagination.pageSize = res.data.data.size;
this.pagination.currentPage = res.data.data.current;
this.pagination.total = res.data.data.total;
this.dataList = res.data.data.records;
});
},
- 分页页码值切换
//切换页码
handleCurrentChange(currentPage) {
//修改页码值为当前选中的页码值
this.pagination.currentPage = currentPage;
//执行查询
this.getAll();
},
小结
- 使用el分页组件
- 定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
分页功能维护(删除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);
}
小结
- 基于业务需求维护删除功能
条件查询
- 查询条件数据封装
- 单独封装
- 与分页操作混合封装
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize: 10,//每页显示的记录数
total: 0,//总记录数
type: "",
name: "",
description: ""
}
- 页面数据模型绑定
<div class="filter-container">
<el-input placeholder="图书类别" v-model="pagination.type" style="width: 200px;"
class="filter-item"></el-input>
<el-input placeholder="图书名称" v-model="pagination.name" style="width: 200px;"
class="filter-item"></el-input>
<el-input placeholder="图书描述" v-model="pagination.description" style="width: 200px;"
class="filter-item"></el-input>
<el-button @click="getAll()" class="dalfBut">查询</el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
</div>
- 组织数据成为get请求发送的数据
//分页查询
getAll() {
// 组织参数,拼接url请求地址
param = "?type=" + this.pagination.type;
param += "&name=" + this.pagination.name;
param += "&description=" + this.pagination.description;
// console.log(param);
// 发送异步请求
axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res) => {
this.pagination.pageSize = res.data.data.size;
this.pagination.currentPage = res.data.data.current;
this.pagination.total = res.data.data.total;
this.dataList = res.data.data.records;
});
},
- 条件参数组织可以通过条件判定书写的更简洁
- Controller接收参数进行测试
@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {
System.out.println("参数====>" + book);
IPage<Book> page = bookService.getPage(currentPage, pageSize);
// 如果当前页码值大于总页码值,那么重新执行查询操作时,使用最大页码值作为当前页码值
if (currentPage > page.getPages()) {
page = bookService.getPage((int) page.getPages(), pageSize);
}
return new R(true, page);
}
- 业务层接口功能开发
// 分页的条件查询
IPage<Book> getPage(int currentPage, int pageSize, Book book);
- 业务层接口实现类功能开发
@Override
public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper();
lqw.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());
lqw.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());
lqw.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());
IPage page = new Page(currentPage, pageSize);
bookDao.selectPage(page, lqw);
return page;
}
- Controller调用业务层分页条件查询接口
@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {
// System.out.println("参数====>" + book);
IPage<Book> page = bookService.getPage(currentPage, pageSize, book);
// 如果当前页码值大于总页码值,那么重新执行查询操作时,使用最大页码值作为当前页码值
if (currentPage > page.getPages()) {
page = bookService.getPage((int) page.getPages(), pageSize, book);
}
return new R(true, page);
}
小结
- 定义查询条件数据模型(当前封装到分页数据模型中)
- 异步调用分页功能并通过请求参数传递数据到后台