制作分析

效果演示

image.png

流程解析

  1. 1. 案例实现方案分析
  2. 实体类开发————使用Lombok快速制作实体类
  3. Dao开发————整合MyBatisPlus,制作数据层测试类
  4. Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
  5. Controller开发————基于Restful开发,使用PostMan测试接口功能
  6. Controller开发————前后端开发协议制作
  7. 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
  8. 列表、新增、修改、删除、分页、查询
  9. 项目异常处理
  10. 按条件查询————页面功能调整、Controller修正功能、Service修正功能
  11. 2. SSMP案例制作流程解析
  12. 先开发基础CRUD功能,做一层测一层
  13. 调通页面,确认异步提交成功后,制作所有功能
  14. 添加分页功能与查询功能

模块创建

  • SpringBoot Initializr创建模块
  • 勾选web、mysql dirver,手动添加mybatis-plus和druid的依赖坐标(需要添加version,因为SpringBoot官方没有收录他们的version坐标)

实体类开发

  • 使用Lombok进行实体类的快速开发,Lombok提供了一组注解来简化POJO实体类的开发
  • 在pom.xml文件中添加对应的依赖坐标,version坐标由SpringBoot提供

    1. <!--lombok-->
    2. <dependency>
    3. <groupId>org.projectlombok</groupId>
    4. <artifactId>lombok</artifactId>
    5. </dependency>
  • 常用注解@Data,该注解可以为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等。

  • 注意:@Data不会给当前实体类添加constructor方法,需要自己添加@NoArgsConstructor或者@AllArgsConstructor到实体类上方。
    1. @Data
    2. @NoArgsConstructor
    3. @AllArgsConstructor
    4. public class Book {
    5. private Integer id;
    6. private String type;
    7. private String name;
    8. private String description;
    9. }

�#

数据层标准开发(基础CRUD)

  • 导入MyBatis-plus和Druid对应的starter依赖坐标 ```xml com.baomidou mybatis-plus-boot-starter 3.4.3

com.alibaba druid-spring-boot-starter 1.2.6

  1. - 配置Druid数据源和MyBatis-Plus对应的基础配置(表单中id的自增策略使用数据库自增而不是MyBatis-Plus的雪花算法的自增策略)
  2. ```yaml
  3. # 设置Druid相关配置
  4. spring:
  5. datasource:
  6. druid:
  7. driver-class-name: com.mysql.cj.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
  9. username: root
  10. password: 865330
  11. # 设置MyBatisPlus相关配置
  12. mybatis-plus:
  13. global-config:
  14. db-config:
  15. table-prefix: tbl_
  16. # 使用数据库的id自增而不是MP自带的雪花算法自增
  17. id-type: auto
  18. # 开启MP日志模式
  19. configuration:
  20. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • Dao类(Mapper类)继承MP中提供的BaseMapper接口并指定泛型

    1. @Mapper
    2. public interface BookDao extends BaseMapper<Book> {}
  • 制作测试类

    1. @SpringBootTest
    2. public class BookDaoTestCase {
    3. @Autowired
    4. private BookDao bookDao;
    5. @Test
    6. void testGetById(){
    7. Book book = bookDao.selectById(1);
    8. // 开启MP日志之后,就不用打印出来看了,直接看日志
    9. System.out.println(book);
    10. }
    11. @Test
    12. void testSave(){
    13. Book book = new Book();
    14. book.setType("测试数据123");
    15. book.setName("测试数据123");
    16. book.setDescription("测试数据123");
    17. bookDao.insert(book);
    18. }
    19. @Test
    20. void testUpdate(){
    21. Book book = new Book();
    22. book.setId(14);
    23. book.setType("测试数据abc");
    24. book.setName("测试数据123");
    25. book.setDescription("测试数据123");
    26. bookDao.updateById(book);
    27. }
    28. @Test
    29. void testDelete(){
    30. bookDao.deleteById(15);
    31. }
    32. @Test
    33. void testGetAll(){
    34. // 开启MP日志之后,就不用打印出来看了,直接看日志
    35. bookDao.selectList(null);
    36. }
    37. @Test
    38. void testGetPage(){
    39. // 得到第1页的5条数据
    40. IPage page = new Page(2,5);
    41. bookDao.selectPage(page, null);
    42. System.out.println(page.getPages());
    43. System.out.println(page.getTotal());
    44. System.out.println(page.getCurrent());
    45. System.out.println(page.getSize());
    46. System.out.println(page.getRecords());
    47. }
    48. @Test
    49. void testGetBy(){
    50. QueryWrapper<Book> qw = new QueryWrapper<>();
    51. // select * from tbl_book where name like %Spring%
    52. qw.like("name","Spring");
    53. bookDao.selectList(qw);
    54. }
    55. @Test
    56. void testGetBy2(){
    57. String name = "Spring";
    58. LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
    59. // select * from tbl_book where name like %Spring%
    60. lqw.like(name!=null, Book::getName, "Spring");
    61. bookDao.selectList(lqw);
    62. }
    63. }

    开启MP运行日志

    1. # 设置MyBatisPlus相关配置
    2. mybatis-plus:
    3. global-config:
    4. db-config:
    5. table-prefix: tbl_
    6. # 使用数据库的id自增而不是MP自带的雪花算法自增
    7. id-type: auto
    8. # 开启MP日志模式
    9. configuration:
    10. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

分页

  • 分页需要设定分页对象IPage

    1. @Test
    2. void testGetPage(){
    3. // 得到第1页的5条数据
    4. IPage page = new Page(1,5);
    5. bookDao.selectPage(page, null);
    6. System.out.println(page.getPages());
    7. System.out.println(page.getTotal());
    8. System.out.println(page.getCurrent());
    9. System.out.println(page.getSize());
    10. System.out.println(page.getRecords());
    11. }
  • bookDao.selectPage(page, null)返回的是IPage对象,该对象中封装了分页操作中的所有数据:

    • 数据
    • 当前页码值
    • 每页数据总量
    • 最大页码值
    • 数据总量
  • 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句(在这里,分页操作,也就是给SQL语句尾部动态添加LIMIT关键词),因此需要增强对应的功能,使用MyBatisPlus拦截器实现

    1. @Configuration
    2. public class MPConfig {
    3. @Bean
    4. public MybatisPlusInterceptor mybatisPlusInterceptor(){
    5. // 1.定义拦截器
    6. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    7. // 2.添加具体的拦截器
    8. interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    9. return interceptor;
    10. }
    11. }
  • 测试

    1. @Test
    2. void testGetPage() {
    3. IPage page = new Page(1, 5);
    4. bookDao.selectPage(page, null);
    5. System.out.println(page.getCurrent());
    6. System.out.println(page.getSize());
    7. System.out.println(page.getPages());
    8. System.out.println(page.getTotal());
    9. System.out.println(page.getRecords());
    10. }

数据层标准开发(条件查询)

  • 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用 ```java @Test void testGetBy() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.like(“name”, “Spring”); bookDao.selectList(queryWrapper); }

@Test void testGetBy2() { LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.like(Book::getName, “Spring”); bookDao.selectList(lambdaQueryWrapper); }

  1. - 条件查询支持动态拼接查询条件
  2. ```java
  3. @Test
  4. void testGetBy2() {
  5. String name = "1";
  6. LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
  7. //if (name != null) lambdaQueryWrapper.like(Book::getName,name);
  8. lambdaQueryWrapper.like(Strings.isNotEmpty(name), Book::getName, name);
  9. bookDao.selectList(lambdaQueryWrapper);
  10. }

小结:

  1. 使用QueryWrapper对象封装查询条件
  2. 推荐使用LambdaQueryWrapper对象
  3. 所有查询操作封装成方法调用
  4. 查询条件支持动态条件拼装

业务层标准开发(CRUD)

  • 业务层(service)接口定义与数据层(Dao)接口定义有较大区别,注意区分不要混用

    • example
      1. selectByUserNameAndPassword(String username,String password); //数据层接口
      2. login(String username,String password); //Service层接口
  • 定义接口 ```java public interface BookService {

    Boolean save(Book book);

    Boolean update(Book book);

    Boolean delete(Integer id);

    Book getById(Integer id);

    List getAll();

    IPage getPage(int currentPage,int pageSize); }

  1. - 接口实现类
  2. ```java
  3. // 定义成业务层的bean
  4. @Service
  5. public class BookServiceImpl2 implements BookService {
  6. @Autowired
  7. private BookDao bookDao;
  8. @Override
  9. public Boolean save(Book book) {
  10. // bookDao.insert(book)返回的是操作的行数,如果行数大于0说明操作成功
  11. return bookDao.insert(book) > 0;
  12. }
  13. @Override
  14. public Boolean update(Book book) {
  15. return bookDao.updateById(book) > 0;
  16. }
  17. @Override
  18. public Boolean delete(Integer id) {
  19. return bookDao.deleteById(id) > 0;
  20. }
  21. @Override
  22. public Book getById(Integer id) {
  23. return bookDao.selectById(id);
  24. }
  25. @Override
  26. public List<Book> getAll() {
  27. return bookDao.selectList(null);
  28. }
  29. @Override
  30. public IPage<Book> getPage(int current, int pageSize) {
  31. IPage page = new Page(current, pageSize);
  32. bookDao.selectPage(page, null);
  33. // selectPage()之后page对象会被修改,最后还是返回page对象
  34. return page;
  35. }
  36. }
  • 测试类

    1. @SpringBootTest
    2. public class BookServiceTestCase {
    3. @Autowired
    4. private BookService bookService;
    5. @Test
    6. void testGetById(){
    7. System.out.println(bookService.getById(4));
    8. }
    9. @Test
    10. void testSave(){
    11. Book book = new Book();
    12. book.setType("测试数据123");
    13. book.setName("测试数据123");
    14. book.setDescription("测试数据123");
    15. bookService.save(book);
    16. }
    17. @Test
    18. void testUpdate(){
    19. Book book = new Book();
    20. book.setId(14);
    21. book.setType("测试数据abc");
    22. book.setName("测试数据123");
    23. book.setDescription("测试数据123");
    24. bookService.update(book);
    25. }
    26. @Test
    27. void testDelete(){
    28. System.out.println("delete true or false: "+bookService.delete(15));;
    29. }
    30. @Test
    31. void testGetAll(){
    32. System.out.println("====testGetAll starts=====");
    33. System.out.println(bookService.getAll());;
    34. System.out.println("====testGetAll ends=====");
    35. }
    36. @Test
    37. void testGetPage(){
    38. // 得到第2页的5条数据
    39. IPage<Book> page = bookService.getPage(2, 5);
    40. System.out.println(page.getPages());
    41. System.out.println(page.getTotal());
    42. System.out.println(page.getCurrent());
    43. System.out.println(page.getSize());
    44. System.out.println(page.getRecords());
    45. }
    46. }

    小结:

  1. Service接口名称定义成业务名称,并与Dao接口名称进行区分
  2. 制作测试类测试Service功能是否有效

业务层快速开发(基于MyBatisPlus构建)

  • 快速开发方案
    • 使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现(ServiceImpl
    • 在通用类基础上做功能重载或功能追加
    • 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
  • 定义接口,继承IService

    1. public interface IBookService extends IService<Book> {}
  • 追加接口功能 ```java public interface IBookService extends IService {

    // 追加的操作与原始操作通过名称区分,功能类似 Boolean delete(Integer id);

    Boolean insert(Book book);

    Boolean modify(Book book);

    Book get(Integer id); }

  1. - 实现类
  2. ```java
  3. @Service
  4. public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {}
  • 实现类追加功能 ```java @Service public class BookServiceImpl extends ServiceImpl implements IBookService {

    @Autowired private BookDao bookDao;

    public Boolean insert(Book book) {

    1. return bookDao.insert(book) > 0;

    }

    public Boolean modify(Book book) {

    1. return bookDao.updateById(book) > 0;

    }

    public Boolean delete(Integer id) {

    1. return bookDao.deleteById(id) > 0;

    }

    public Book get(Integer id) {

    1. return bookDao.selectById(id);

    } }

  1. - 测试类
  2. ```java
  3. @SpringBootTest
  4. public class IBookServiceTestCase {
  5. @Autowired
  6. private IBookService bookService;
  7. @Test
  8. void testGetById(){
  9. System.out.println(bookService.getById(4));
  10. }
  11. @Test
  12. void testSave(){
  13. Book book = new Book();
  14. book.setType("测试数据123");
  15. book.setName("测试数据123");
  16. book.setDescription("测试数据123");
  17. bookService.save(book);
  18. }
  19. @Test
  20. void testUpdate(){
  21. Book book = new Book();
  22. book.setId(14);
  23. book.setType("测试数据abc");
  24. book.setName("测试数据123");
  25. book.setDescription("测试数据123");
  26. bookService.updateById(book);
  27. }
  28. @Test
  29. void testDelete(){
  30. System.out.println("delete true or false: "+bookService.removeById(15));;
  31. }
  32. @Test
  33. void testGetAll(){
  34. System.out.println("====testGetAll starts=====");
  35. System.out.println(bookService.list());;
  36. System.out.println("====testGetAll ends=====");
  37. }
  38. @Test
  39. void testGetPage(){
  40. // 得到第2页的5条数据
  41. IPage<Book> page = new Page<>(2, 5);
  42. bookService.page(page);
  43. System.out.println(page.getPages());
  44. System.out.println(page.getTotal());
  45. System.out.println(page.getCurrent());
  46. System.out.println(page.getSize());
  47. System.out.println(page.getRecords());
  48. }
  49. }

小结:

  1. 使用通用接口(ISerivce)快速开发Service
  2. 使用通用实现类(ServiceImpl)快速开发ServiceImpl
  3. 可以在通用接口基础上做功能重载或功能追加
  4. 注意重载时不要覆盖原始操作,避免原始提供的功能丢失

表现层标准开发

  • 基于RESTFul风格进行表现层开发
  • 使用Postman测试表现层接口功能
  • 表现类 ```java @RestController @RequestMapping(“/books”) public class BookController {

    @Autowired private IBookService bookService;

    @GetMapping public List getAll() {

    1. return bookService.list();

    }

    @PostMapping public Boolean save(@RequestBody Book book) {

    1. return bookService.save(book);

    }

    @PutMapping public Boolean update(@RequestBody Book book) {

    1. return bookService.modify(book);

    }

    @DeleteMapping(“{id}”) public Boolean delete(@PathVariable Integer id) {

    1. return bookService.delete(id);

    }

    @GetMapping(“{id}”) public Book getById(@PathVariable Integer id) {

    1. return bookService.getById(id);

    }

    @GetMapping(“{currentPage}/{pageSize}”) public IPage getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {

    1. return bookService.getPage(currentPage, pageSize);

    }

}

  1. - 添加分页的业务层方法
  2. ```java
  3. IPage<Book> getPage(int currentPage,int pageSize);
  1. @Override
  2. public IPage<Book> getPage(int currentPage, int pageSize) {
  3. IPage page = new Page(currentPage, pageSize);
  4. bookDao.selectPage(page, null);
  5. return page;
  6. }

小结:

  1. 基于Restful制作表现层接口
    新增:POST
    删除:DELETE
    修改:PUT
    查询:GET
  2. 接收参数
    实体数据:@RequestBody
    路径变量:@PathVariable

表现层数据一致性处理(R对象)

  • 增加response的状态属性(flag)

image.png

  • 当数据为 null 可能出现的问题
    • 查询id不存在的数据,返回 null
    • 查询过程中抛出异常,catch 中返回 null
  • 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议

    1. @Data
    2. public class R {
    3. private Boolean flag;
    4. private Object data;
    5. public R() {
    6. }
    7. /**
    8. * 不返回数据的构造方法
    9. *
    10. * @param flag
    11. */
    12. public R(Boolean flag) {
    13. this.flag = flag;
    14. }
    15. /**
    16. * 返回数据的构造方法
    17. *
    18. * @param flag
    19. * @param data
    20. */
    21. public R(Boolean flag, Object data) {
    22. this.flag = flag;
    23. this.data = data;
    24. }
    25. }
  • 表现层接口统一返回值类型结果 ```java @RestController @RequestMapping(“/books”) public class BookController {

    @Autowired private IBookService bookService;

    @GetMapping public R getAll() {

    1. return new R(true, bookService.list());

    }

    @PostMapping public R save(@RequestBody Book book) {

    1. return new R(bookService.save(book));

    }

    @PutMapping public R update(@RequestBody Book book) {

    1. return new R(bookService.modify(book));

    }

    @DeleteMapping(“{id}”) public R delete(@PathVariable Integer id) {

    1. return new R(bookService.delete(id));

    }

    @GetMapping(“{id}”) public R getById(@PathVariable Integer id) {

    1. return new R(true, bookService.getById(id));

    }

    @GetMapping(“{currentPage}/{pageSize}”) public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {

    1. return new R(true, bookService.getPage(currentPage, pageSize));

    }

}

  1. 小结:
  2. 1. 设计统一的返回值结果类型便于前端开发读取数据
  3. 1. 返回值结果类型可以根据需求自行设定,没有固定格式
  4. 1. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前<br />后端数据协议
  5. <a name="QbHq2"></a>
  6. # 前后端调用(axios发送异步请求)
  7. - 前后端分离结构设计中页面归属前端服务器
  8. - 单体工程中页面放置在resources目录下的static目录中(建议执行clean
  9. - 前端发送异步请求,调用后端接口
  10. ```javascript
  11. //钩子函数,VUE对象初始化完成后自动执行
  12. created() {
  13. //调用查询全部数据的操作
  14. this.getAll();
  15. }
  1. //列表
  2. getAll() {
  3. //发送异步请求
  4. axios.get("/books").then((res)=>{
  5. console.log(res.data);
  6. })
  7. }

小结:

  1. 单体项目中页面放置在resources/static目录下
  2. created钩子函数用于初始化页面时发起调用
  3. 页面使用axios发送异步请求获取数据后确认前后端是否联通

列表功能

  • 列表页
    1. //列表
    2. getAll() {
    3. //发送异步请求
    4. axios.get("/books").then((res) => {
    5. //console.log(res.data);
    6. this.dataList = res.data.data;
    7. })
    8. }
    小结:
  1. 将查询数据返回到页面,利用前端v-bind数据双向绑定进行数据展示

添加功能

  • 弹出添加窗口

    1. // 弹出添加窗口
    2. handleCreate() {
    3. this.dialogFormVisible = true;
    4. }
  • 清除数据

    1. //重置表单
    2. resetForm() {
    3. this.formData = {};
    4. }
  • 在弹出添加窗口时 清除数据

    1. //弹出添加窗口
    2. handleCreate() {
    3. this.dialogFormVisible = true;
    4. this.resetForm();
    5. }
  • 发送请求

    1. //添加
    2. handleAdd() {
    3. axios.post("/books", this.formData).then((res) => {
    4. //判断当前操作是否成功
    5. if (res.data.flag) {
    6. //1.关闭弹层
    7. this.dialogFormVisible = false;
    8. this.$message.success("添加成功");
    9. } else {
    10. this.$message.error("添加失败");
    11. }
    12. }).finally(() => {
    13. //2.重新加载数据
    14. this.getAll();
    15. })
    16. }
  • 取消添加

    1. //取消
    2. cancel() {
    3. //1.关闭弹层
    4. this.dialogFormVisible = false;
    5. //2.提示用户
    6. this.$message.info("当前操作取消");
    7. }

    小结:

  1. 请求方式使用POST调用后台对应操作
  2. 添加操作结束后动态刷新页面加载数据
  3. 根据操作结果不同,显示对应的提示信息
  4. 弹出添加Div时清除表单数据

删除功能

  • 删除

    1. // 删除
    2. handleDelete(row) {
    3. axios.delete("/books/" + row.id).then((res) => {
    4. if (res.data.flag) {
    5. this.$message.success("删除成功");
    6. } else {
    7. this.$message.error("删除失败");
    8. }
    9. }).finally(() => {
    10. this.getAll();
    11. });
    12. }
  • 加入确认删除对话框 ```javascript // 删除 handleDelete(row) { //1. 弹出提示框 this.$confirm(“些操作永久删除当前信息,是否继续?”, “提示”, {type: “info”}).then(() => {

    1. //2. 做删除业务
    2. axios.delete("/books/" + row.id).then((res) => {
    3. //判断当前操作是否成功
    4. if (res.data.flag) {
    5. this.$message.success("删除成功");
    6. } else {
    7. this.$message.error("删除失败");
    8. }
    9. }).finally(() => {
    10. //2.重新加载数据
    11. this.getAll();
    12. })

    }).catch(() => {

    1. //3. 取消删除
    2. this.$message.info("取消操作");

    });

}

  1. 小结:
  2. 1. 请求方式使用Delete调用后台对应操作
  3. 1. 删除操作需要传递当前行数据对应的id值到后台
  4. 1. 删除操作结束后动态刷新页面加载数据
  5. 1. 根据操作结果不同,显示对应的提示信息
  6. 1. 删除操作前弹出提示框避免误操作
  7. <a name="qlPID"></a>
  8. # 修改功能(加载数据)
  9. - 弹出修改窗口
  10. ```javascript
  11. //弹出编辑窗口
  12. handleUpdate(row) {
  13. axios.get("/books/" + row.id).then((res) => {
  14. if (res.data.flag && res.data.data != null) {
  15. // 展示弹层,加载数据
  16. this.dialogFormVisible4Edit = true;
  17. this.formData = res.data.data;
  18. } else {
  19. this.$message.error("数据同步失败,自动刷新");
  20. }
  21. }).finally(() => {
  22. //重新加载数据
  23. this.getAll();
  24. });
  25. }
  • 删除消息维护 ```javascript // 删除 handleDelete(row) { //1. 弹出提示框 this.$confirm(“些操作永久删除当前信息,是否继续?”, “提示”, {type: “info”}).then(() => {
    1. //2. 做删除业务
    2. axios.delete("/books/" + row.id).then((res) => {
    3. //判断当前操作是否成功
    4. if (res.data.flag) {
    5. this.$message.success("删除成功");
    6. } else {
    7. this.$message.error("数据同步失败,自动刷新");
    8. }
    9. }).finally(() => {
    10. //2.重新加载数据
    11. this.getAll();
    12. });
    }).catch(() => {
    1. //3. 取消删除
    2. this.$message.info("取消操作");
    });

}

  1. <a name="ECGec"></a>
  2. # 修改功能
  3. - 修改
  4. ```javascript
  5. //修改
  6. handleEdit() {
  7. axios.put("/books", this.formData).then((res) => {
  8. //判断当前操作是否成功
  9. if (res.data.flag) {
  10. //1.关闭弹层
  11. this.dialogFormVisible4Edit = false;
  12. this.$message.success("修改成功");
  13. } else {
  14. this.$message.error("修改失败");
  15. }
  16. }).finally(() => {
  17. //2.重新加载数据
  18. this.getAll();
  19. });
  20. }
  • 取消添加和修改
    1. //取消
    2. cancel() {
    3. //1.关闭弹层
    4. this.dialogFormVisible = false;
    5. this.dialogFormVisible4Edit = false;
    6. //2.提示用户
    7. this.$message.info("当前操作取消");
    8. }
    小结:
  1. 请求方式使用PUT调用后台对应操作
  2. 修改操作结束后动态刷新页面加载数据(同新增)
  3. 根据操作结果不同,显示对应的提示信息(同新增)

异常消息处理

  • 业务操作成功或失败返回数据格式 ```json { “flag”: true, “data”: null }

{ “flag”: false, “data”: null }

  1. - 后台代码BUG导致数据格式不统一性
  2. ```json
  3. {
  4. "timestamp": "2021-11-07T12:44:29.343+00:00",
  5. "status": 500,
  6. "error": "Internal Server Error",
  7. "path": "/books"
  8. }
  • 对异常进行统一处理,出现异常后,返回指定信息

    1. @RestControllerAdvice
    2. public class ProjectExceptionAdvice {
    3. //拦截所有的异常信息
    4. @ExceptionHandler(Exception.class)
    5. public R doException(Exception ex) {
    6. // 记录日志
    7. // 发送消息给运维
    8. // 发送邮件给开发人员 ,ex 对象发送给开发人员
    9. ex.printStackTrace();
    10. return new R(false, null, "系统错误,请稍后再试!");
    11. }
    12. }
  • 修改表现层返回结果的模型类,封装出现异常后对应的信息
    flag:false
    Data: null
    消息(msg): 要显示信息

    1. @Data
    2. public class R {
    3. private Boolean flag;
    4. private Object data;
    5. private String msg;
    6. public R() {
    7. }
    8. public R(Boolean flag) {
    9. this.flag = flag;
    10. }
    11. public R(Boolean flag, Object data) {
    12. this.flag = flag;
    13. this.data = data;
    14. }
    15. public R(Boolean flag, String msg) {
    16. this.flag = flag;
    17. this.msg = msg;
    18. }
    19. public R(String msg) {
    20. this.flag = false;
    21. this.msg = msg;
    22. }
    23. }
  • 页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息

    1. //添加
    2. handleAdd() {
    3. axios.post("/books", this.formData).then((res) => {
    4. //判断当前操作是否成功
    5. if (res.data.flag) {
    6. //1.关闭弹层
    7. this.dialogFormVisible = false;
    8. this.$message.success("添加成功");
    9. } else {
    10. this.$message.error(res.data.msg);
    11. }
    12. }).finally(() => {
    13. //2.重新加载数据
    14. this.getAll();
    15. })
    16. }
  • 在表现层Controller中进行消息统一处理

    1. @PostMapping
    2. public R save(@RequestBody Book book) throws IOException {
    3. //if (book.getName().equals("123")) throw new IOException();
    4. boolean flag = bookService.save(book);
    5. return new R(flag, flag ? "添加成功^_^" : "添加失败-_-!");
    6. }
  • 页面消息处理

    1. //添加
    2. handleAdd() {
    3. axios.post("/books", this.formData).then((res) => {
    4. //判断当前操作是否成功
    5. if (res.data.flag) {
    6. //1.关闭弹层
    7. this.dialogFormVisible = false;
    8. this.$message.success(res.data.msg);
    9. } else {
    10. this.$message.error(res.data.msg);
    11. }
    12. }).finally(() => {
    13. //2.重新加载数据
    14. this.getAll();
    15. })
    16. }

    小结:

  1. 使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
  2. 异常处理器必须被扫描加载,否则无法生效
  3. 表现层返回结果的模型类中添加消息属性用来传递消息到页面

分页

  • 页面使用 el 分页组件添加分页功能 ```html

  1. - 定义分页组件需要使用的数据并将数据绑定到分页组件
  2. ```vue
  3. data: {
  4. pagination: { // 分页相关模型数据
  5. currentPage: 1, // 当前页码
  6. pageSize: 10, // 每页显示的记录数
  7. total: 0, // 总记录数
  8. }
  9. }
  • 替换查询全部功能为分页功能

    1. getAll() {
    2. axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {});
    3. }
  • 分页查询
    使用路径参数传递分页数据或封装对象传递数据

    1. @GetMapping("{currentPage}/{pageSize}")
    2. public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
    3. return new R(true, bookService.getPage(currentPage, pageSize));
    4. }
  • 加载分页数据

    1. //分页查询
    2. getAll() {
    3. //发送异步请求
    4. axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {
    5. //console.log(res.data);
    6. this.pagination.currentPage = res.data.data.current;
    7. this.pagination.pageSize = res.data.data.size;
    8. this.pagination.total = res.data.data.total;
    9. this.dataList = res.data.data.records;
    10. })
    11. }
  • 分页页码值切换

    1. //切换页码
    2. handleCurrentChange(currentPage) {
    3. //修改页码值为当前选中的页码值
    4. this.pagination.currentPage = currentPage;
    5. //执行查询
    6. this.getAll();
    7. }

    小结:

  1. 使用el分页组件
  2. 定义分页组件绑定的数据模型
  3. 异步调用获取分页数据
  4. 分页数据页面回显

分页功能维护(删除BUG)

  • 对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询
    1. @GetMapping("{currentPage}/{pageSize}")
    2. public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
    3. IPage<Book> page = bookService.getPage(currentPage, pageSize);
    4. // 如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
    5. if (currentPage > page.getPages()) {
    6. page = bookService.getPage((int) page.getPages(), pageSize);
    7. }
    8. return new R(true, page);
    9. }
    小结:
  1. 基于业务需求维护删除功能

条件查询

  • 查询条件数据封装
    单独封装
    与分页操作混合封装

    1. pagination: {//分页相关模型数据
    2. currentPage: 1,//当前页码
    3. pageSize: 10,//每页显示的记录数
    4. total: 0,//总记录数
    5. type: "",
    6. name: "",
    7. description: ""
    8. }
  • 使用v-model实现页面数据模型绑定

    1. <div class="filter-container">
    2. <el-input placeholder="图书类别" v-model="pagination.type" class="filter-item" />
    3. <el-input placeholder="图书名称" v-model="pagination.name" class="filter-item" />
    4. <el-input placeholder="图书描述" v-model="pagination.description" class="filter-item" />
    5. <el-button @click="getAll()" class="dalfBut">查询</el-button>
    6. <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
    7. </div>
  • 组织数据成为get请求发送的数据

    1. //分页查询
    2. getAll() {
    3. console.log(this.pagination.type);
    4. // /books/1/10?type=???&name=???&decription=?? ;
    5. //1. 获取查询条件 , 拼接查询条件
    6. param = "?name=" + this.pagination.name;
    7. param += "&type=" + this.pagination.type;
    8. param += "&description=" + this.pagination.description;
    9. //console.log("-----------------" + param);
    10. //发送异步请求
    11. axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res) => {
    12. //console.log(res.data);
    13. this.pagination.currentPage = res.data.data.current;
    14. this.pagination.pageSize = res.data.data.size;
    15. this.pagination.total = res.data.data.total;
    16. this.dataList = res.data.data.records;
    17. })
    18. }
  • 条件参数组织可以通过条件判定书写的更简洁

  • Controller接收参数

    1. @GetMapping("/{current}/{pageSize}")
    2. // 传递的参数名为name,type,description,参数名与实体类Book中的属性名一直,SpringMVC自动为实体类注入属性
    3. public R getPage(@PathVariable int current, @PathVariable int pageSize, Book book){
    4. IPage<Book> page = bookService.getPage(current, pageSize, book);
    5. // 如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
    6. if (current > page.getPages()){
    7. page = bookService.getPage((int) page.getPages(), pageSize, book);
    8. }
    9. return new R(true, page);
    10. }
  • 业务层接口功能开发

    1. /**
    2. * 分页的条件查询
    3. *
    4. * @param currentPage
    5. * @param pageSize
    6. * @param book
    7. * @return
    8. */
    9. IPage<Book> getPage(Integer currentPage, int pageSize, Book book);
  • 业务层接口实现类功能开发

    1. @Override
    2. public IPage<Book> getPage(int current, int pageSize, Book book) {
    3. LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
    4. // like的参数:第一个是条件,第二个是对应的属性,第三个是对应的值
    5. lqw.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());
    6. lqw.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());
    7. lqw.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());
    8. IPage page = new Page(current, pageSize);
    9. bookDao.selectPage(page, lqw);
    10. // selectPage()之后page对象会被修改,最后还是返回page对象
    11. return page;
    12. }
  • Controller调用业务层分页条件查询接口

    1. @GetMapping("/{current}/{pageSize}")
    2. // 传递的参数名为name,type,description,参数名与实体类Book中的属性名一直,SpringMVC自动为实体类注入属性
    3. public R getPage(@PathVariable int current, @PathVariable int pageSize, Book book){
    4. IPage<Book> page = bookService.getPage(current, pageSize, book);
    5. // 如果当前页码值大于总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
    6. if (current > page.getPages()){
    7. page = bookService.getPage((int) page.getPages(), pageSize, book);
    8. }
    9. return new R(true, page);
    10. }
  • 页面回显数据

    1. //分页查询
    2. getAll() {
    3. console.log(this.pagination.type);
    4. // /books/1/10?type=???&name=???&decription=?? ;
    5. //1. 获取查询条件 , 拼接查询条件
    6. param = "?name=" + this.pagination.name;
    7. param += "&type=" + this.pagination.type;
    8. param += "&description=" + this.pagination.description;
    9. //console.log("-----------------" + param);
    10. //发送异步请求
    11. axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res) => {
    12. //console.log(res.data);
    13. this.pagination.currentPage = res.data.data.current;
    14. this.pagination.pageSize = res.data.data.size;
    15. this.pagination.total = res.data.data.total;
    16. this.dataList = res.data.data.records;
    17. })
    18. }

    小结:

  1. 定义查询条件数据模型(当前封装到分页数据模型中)
  2. 异步调用分页功能并通过请求参数传递数据到后台

流程总结

  1. pom.xml
    1. 配置起步依赖
  2. application.yml
    1. 设置数据源、端口、框架技术相关配置等
  3. dao
    1. 继承BaseMapper、设置@Mapper
  4. dao测试类
  5. service
    1. 调用数据层接口或MyBatis-Plus提供的接口快速开发
  6. service测试类
  7. controller
    1. 基于Restful开发,使用Postman测试跑通功能
  8. 页面
    1. 放置在resources目录下的static目录中