暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot-8-SpringBoot结合Mybatis-plus和前端实现简单综合案例

384

SpringBoot结合Mybatis-plus和前端实现简单综合案例

今日目标

完成综合案例

SpringBoot整合技术实在是太多了,对于我们来说一点点掌握就可以了,现在我们通过一个综合案例,将知识进行串联,同时做一个小功能,体验一下真是开发。不过有言在先,这个案例制作的时候,你可能会有这种感觉,说好的SpringBoot整合其他技术的案例,为什么感觉SpringBoot整合其他技术的身影不多呢?因为这东西书写太简单了,简单到瞬间写完,大量的时间做的不是这些整合工作。

效果展示:

  • 主页面
  • 添加
  • 删除
  • 修改
  • 分页
  • 条件查询

整体案例中需要采用的技术如下,先了解一下,做到哪一个说哪一个

  1. 实体类开发————使用Lombok快速制作实体类
  2. Dao开发————整合MyBatisPlus,制作数据层测试
  3. Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
  4. Controller开发————基于Restful开发,使用PostMan测试接口功能
  5. Controller开发————前后端开发协议制作
  6. 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
    • 列表
    • 新增
    • 修改
    • 删除
    • 分页
    • 查询
  7. 项目异常处理
  8. 按条件查询————页面功能调整、Controller修正功能、Service修正功能

可以看的出来,东西还是很多的,希望通过这个案例,各位小伙伴能够完成基础开发的技能训练。整体开发过程采用做一层测一层的形式进行,过程完整,战线较长,希望各位能跟紧进度,完成这个小案例的制作。

1.模块创建

对于这个案例如果按照企业开发的形式进行应该制作后台微服务,前后端分离的开发。

对初学的小伙伴要求太高了,咱们简化一下。后台做单体服务器,前端不使用前后端分离的制作了

一个服务器即充当后台服务调用,又负责前端页面展示,降低学习的门槛。 下面我们创建一个新的模块,加载要使用的技术对应的starter,修改配置文件格式为yml格式,并把web访问端口先设置成80。

【步骤一】:创建SpringBoot项目

创建SpringBoot项目名称为day21-springboot-ssmp
基础差的小伙伴可以去查看如何创建SPringBoot项目Spring Boot实战:快速搭建你的第一个应用

【步骤二】:在Pom.xml中添加依赖

<?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/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zbbmeta</groupId>
    <artifactId>day21-springboot-ssmp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <name>day21-springboot-ssmp</name>


    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
      <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


复制

【步骤三】:application.yml修改端口

server:
  port: 80

复制

2.实体类开发

【步骤一】:创建案例对应表

本案六对应的学生表结构如下

-- 创建测试库
create database  if not exists springboot_mp character set utf8;
use springboot_mp;
-- 创建测试表
CREATE TABLE tb_student (
                              id bigint(20NOT NULL AUTO_INCREMENT COMMENT '主键ID',
                              stuid varchar(40unique NOT NULL comment '学号',
                              name varchar(30DEFAULT NULL COMMENT '姓名',
                              age tinyint   COMMENT '年龄',
                              sex tinyint(1comment '性别 0 男 1 女',
                              dept varchar(2000DEFAULT NULL COMMENT '院系',
                              address varchar(400comment '家庭地址',
                              PRIMARY KEY (`id`)
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 插入测试数据
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528247996417'6840300''张三'200'计算机学院''广东省广州市天河街100号');
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528352854017'6840000''李四'240'计算机学院''广东省珠海市香洲路100号');
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528352854018'6840500''王五'250'物理学院''广东省广州市番禺100号');
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528352854019'6840600''邹六'260'艺术学院''河南省郑州市100号');
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528352854020'6840700''韩梅梅'211'英语学院''广东省广州市天河街150号');
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528352854021'6840800''小芳'201'英语学院''河南省郑州市140号');
INSERT INTO tb_student (id, stuid, name, age, sex, dept, address) VALUES (1692936528352854022'6840900''小花'201'计算机学院''广东省深圳市100号');


复制

【步骤二】:根据表结构创建实体类

实体类:

@Data
@ToString
public class Student implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */

    private String id;

    /**
     * 学号
     */

    private String stuid;

    /**
     * 姓名
     */

    private String name;

    /**
     * 年龄
     */

    private Integer age;

    /**
     * 性别 0 男 1 女
     */

    private Integer sex;

    /**
     * 院系
     */

    private String dept;

    /**
     * 家庭地址
     */

    private String address;
}

复制

实体类的开发可以自动通过工具手工生成get/set方法,然后覆盖toString()方法,方便调试,等等。不过这一套操作书写很繁琐,有对应的工具可以帮助我们简化开发,介绍一个小工具,lombok。

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

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
  </dependency>

复制

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

@Data
@ToString
public class Student implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */

    private String id;

    /**
     * 学号
     */

    private String stuid;

    /**
     * 姓名
     */

    private String name;

    /**
     * 年龄
     */

    private Integer age;

    /**
     * 性别 0 男 1 女
     */

    private Integer sex;

    /**
     * 院系
     */

    private String dept;

    /**
     * 家庭地址
     */

    private String address;
}

复制

在之前的文章中也介绍过SpringBoot-4-MyBatis快速入门指南

到这里实体类就做好了,是不是比不使用lombok简化好多,这种工具在Java开发中还有N多,后面遇到了能用的实用开发技术时,在不增加各位小伙伴大量的学习时间的情况下,尽量多给大家介绍一些。

总结

  1. 实体类制作
  2. 使用lombok简化开发
    • 导入lombok无需指定版本,由SpringBoot提供版本
    • @Data注解

3.定义数据接口——基础CRUD

数据层开发本次使用MyBatisPlus技术,学都学了都用上。MyBatis-Plus:轻松上手快速入门和高级查询

【步骤一】:导入MyBatisPlus对应的starter和mysql的驱动

什么是驱动可以查看之前文章MyBatis快速入门指南

  • 在pom.xml中添加依赖

<!--        mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>


复制

【步骤二】:在application.yml中配置数据库连接相关的数据源配置

server:
  port: 80

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_mp?serverTimezone=UTC
    username: root
    password: root

复制

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

package com.zbbmeta.mapper;

public interface StudentMapper extends BaseMapper<Student{

}


复制

【步骤四】:制作测试类测类

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

package com.zbbmeta;

import com.zbbmeta.entity.Student;
import com.zbbmeta.mapper.StudentMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


/**
 * @author springboot葵花宝典
 * @description: TODO
 */

@SpringBootTest
public class StudentMapperTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    void testGetById(){
        System.out.println(studentMapper.selectById(1));
    }

    @Test
    void testSave(){

        Student student = new Student();
        student.setStuid("6843000");
        student.setName("张绍港");
        student.setAge(20);
        student.setSex(0);
        student.setDept("新闻学院");
        student.setAddress("广东省广州市天河街100号");
        studentMapper.insert(student);
    }

    @Test
    void testUpdate(){
        Student student = new Student();
        student.setId("1692936528352854022");

        student.setName("小花2");
        student.setAge(25);
        student.setSex(1);
        student.setDept("新闻学院");
        student.setAddress("广东省广州市天河街100号");
        studentMapper.updateById(student);
    }

    @Test
    void testDelete(){
        studentMapper.deleteById("1692936528352854022");
    }

    @Test
    void testGetAll(){
        studentMapper.selectList(null);
    }
}

复制

运行测试方法发现抱错

报错说库中studnet表不存在 **思考:**为什么会出现这样的情况? 答:因为实体类名称为student
表名为tb_student

类名和表明不一致解决方法

  • 1.实体类上添加注解TableName (局部方案)
  • 2.添加配置(全局方案)
解决方法一

在实体类Student上添加注解TableName

@TableName("tb_student")
@Data
@ToString
public class Student implements Serializable {

}

复制
解决方法二

在application.yml中配置全局配置

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tb_  #设置表名通用前缀
      id-type: assign_id # id生成策略

复制

查看MyBatisPlus运行日志

SpringBoot整合MyBatisPlus的时候充分考虑到了这点,通过配置的形式就可以查阅执行期SQL语句,配置如下:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启日志输出到控制台

复制

再来看运行结果,此时就显示了运行期执行SQL的情况。

其中清晰的标注了当前执行的SQL语句是什么,携带了什么参数,对应的执行结果是什么,所有信息应有尽有。

注:此处设置的是日志的显示形式,当前配置的是控制台输出,当然还可以由更多的选择,根据需求切换即可

4.分页功能制作

前面仅仅是使用了MyBatisPlus提供的基础CRUD功能,实际上MyBatisPlus给我们提供了几乎所有的基础操作,这一节说一下如何实现数据库端的分页操作。

  • MyBatisPlus提供的分页操作API如下:
@Test
void testGetPage(){
    IPage page = new Page(2,5);
    studentMapper.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());
}

复制

到这里就知道这些数据如何获取了,但是当你去执行这个操作时,你会发现并不像我们分析的这样,实际上这个分页功能当前是无效的。为什么这样呢?这个要源于MyBatisPlus的内部机制。

对于MySQL的分页操作使用limit关键字进行,而并不是所有的数据库都使用limit关键字实现的,这个时候MyBatisPlus为了制作的兼容性强,将分页操作设置为基础查询操作的升级版,你可以理解为IPhone6与IPhone6S-PLUS的关系。

基础操作中有查询全部的功能,而在这个基础上只需要升级一下(PLUS)就可以得到分页操作。所以MyBatisPlus将分页操作做成了一个开关,你用分页功能就把开关开启,不用就不需要开启这个开关。而我们现在没有开启这个开关,所以分页操作是没有的。这个开关是通过MyBatisPlus的拦截器的形式存在的,其中的原理这里不分析了,有兴趣的小伙伴可以学习MyBatisPlus这门课程进行详细解读。具体设置方式如下:

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

package com.zbbmeta.config;

/**
 * @author springboot葵花宝典
 * @description: TODO
 */

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }

}

复制

上述代码第一行是创建MyBatisPlus的拦截器栈,这个时候拦截器栈中没有具体的拦截器,第二行是初始化了分页拦截器,并添加到拦截器栈中。如果后期开发其他功能,需要添加全新的拦截器,按照第二行的格式继续add进去新的拦截器就可以了。

5.条件查询功能制作

除了分页功能,MyBatisPlus还提供有强大的条件查询功能。以往我们写条件查询要自己动态拼写复杂的SQL语句,现在简单了,MyBatisPlus将这些操作都制作成API接口,调用一个又一个的方法就可以实现各种条件的拼装。这里给大家普及一下基本格式,详细的操作还是到MyBatisPlus的课程中查阅吧。

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

@Test
void testGetBy(){
    //2 条件查询
    LambdaQueryWrapper<Student> lqw = new LambdaQueryWrapper<>();
    lqw.like("name","张三");
    List<Student> studentList = studentMapper.selectList(lqw);
    System.out.println("studentList = " + studentList);
}

复制

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

这组API使用还是比较简单的,但是关于属性字段名的书写存在着安全隐患,比如查询字段name,当前是以字符串的形态书写的,万一写错,编译器还没有办法发现,只能将问题抛到运行器通过异常堆栈告诉开发者,不太友好。

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

@Test
void testGetBy2(){
    LambdaQueryWrapper<Student> lqw = new LambdaQueryWrapper<>();
    lqw.like(Student::getName,"张三");
    List<Student> studentList = studentMapper.selectList(lqw);
    System.out.println("studentList = " + studentList);
}

复制

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

@Test
void testGetBy(){
    String name ="张三";
    LambdaQueryWrapper<Student> lqw = new LambdaQueryWrapper<>();
    lqw.like(name!=null,Student::getName,name);
    List<Student> studentList = studentMapper.selectList(lqw);
    System.out.println("studentList = " + studentList);
}

复制

6.业务层(Service)开发

【步骤一】: 业务层接口定义如下:

package com.zbbmeta.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zbbmeta.entity.R;
import com.zbbmeta.entity.Student;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author springboot葵花宝典
 */

public interface IStudentService extends IService<Student{
    public IPage<Student> getPageint currentPage, int pageSize, Student student);
}

复制

【步骤二】: 业务层实现类定义如下:

package com.zbbmeta.service.impl;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author springboot葵花宝典
 * @since 2023-08-20
 */

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapperStudentimplements IStudentService {

    /**
     * 条件+分页查询
     * @param currentPage
     * @param pageSize
     * @param student
     * @return
     */

   @Override
    public IPage<Student> getPageint currentPage,  int pageSize, Student student){
        //1 创建IPage分页对象,设置分页参数
        IPage<Student> page=new Page<>(currentPage,pageSize);
        //2 执行分页查询
        LambdaQueryWrapper<Student> lqw = new LambdaQueryWrapper<>();
       if(student!=null){
           lqw.eq(StrUtil.isNotEmpty(student.getStuid()),Student::getStuid,student.getStuid());
           lqw.like(StrUtil.isNotEmpty(student.getName()),Student::getName,student.getName());
           lqw.like(StrUtil.isNotEmpty(student.getDept()),Student::getDept,student.getDept());
           lqw.like(StrUtil.isNotEmpty(student.getAddress()),Student::getAddress,student.getAddress());
       }
       IPage<Student> studentList = this.page(page, lqw);
       //如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
        return studentList;
    }
}

复制

【步骤三】: 测试类如下:

package com.zbbmeta;


/**
 * @author springboot葵花宝典
 * @description: TODO
 */

@SpringBootTest
public class StudentServiceTest {

    @Autowired
    private IStudentService studentService;

    @Test
    void testGetById(){
        System.out.println(studentService.getById("1692936528352854018"));
    }
    @Test
    void testSave(){

        Student student = new Student();
        student.setStuid("6843000");
        student.setName("张绍港");
        student.setAge(20);
        student.setSex(0);
        student.setDept("新闻学院");
        student.setAddress("广东省广州市天河街100号");
        studentService.save(student);
    }
    @Test
    void testUpdate(){
        Student student = new Student();
        student.setId("1692936528352854022");

        student.setName("小花2");
        student.setAge(25);
        student.setSex(1);
        student.setDept("新闻学院");
        student.setAddress("广东省广州市天河街100号");
        studentService.updateById(student);
    }
    @Test
    void testDelete(){
        studentService.removeById(18);
    }

    @Test
    void testGetAll(){
        studentService.list();
    }

    @Test
    void testGetPage(){

        IPage<Student> page = studentService.getPage(25new Student());
        System.out.println(page.getRecords());
    }
}

复制

7.表现层(Controller)开发

终于做到表现层了,做了这么多都是基础工作。其实你现在回头看看,哪里还有什么SpringBoot的影子?前面1,2步就搞完了。继续完成表现层制作吧,咱们表现层的开发使用基于Restful的表现层接口开发,功能测试通过Postman工具进行。

表现层接口如下:

package com.zbbmeta.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zbbmeta.entity.Student;
import com.zbbmeta.service.IStudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author springboot葵花宝典
 */

@RestController
@RequestMapping("/students")
public class Student1Controller {
    @Autowired
    private IStudentService studentService;

    @GetMapping
    public List<Student> getAll(){
        return   studentService.list();
    }

    @PostMapping
    public Boolean save(@RequestBody Student student) throws IOException {
        return  studentService.save(student);
    }

    @PutMapping
    public Boolean update(@RequestBody Student student) throws IOException {
        return  studentService.updateById(student);
    }

    @DeleteMapping("{id}")
    public Boolean delete(@PathVariable String id){
        return studentService.removeById(id);
    }

    @GetMapping("{id}")
    public Student getById(@PathVariable String  id){
        return studentService.getById(id);
    }

    @PostMapping("{currentPage}/{pageSize}")
    public   IPage<Student> getPage(@PathVariable int currentPage, @PathVariable int pageSize, @RequestBody Student student){

       return studentService.getPage(currentPage, pageSize, student);
    }
}


复制

在使用Postman测试时关注提交类型,对应上即可,不然就会报405的错误码了。

普通GET请求

普通POST/PUT请求

传递json数据,后台实用@RequestBody接收数据

总结

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

8.表现层消息一致性处理

目前我们通过Postman测试后业务层接口功能是通的,但是这样的结果给到前端开发者会出现一个小问题。不同的操作结果所展示的数据格式差异化严重。增删改操作结果

true

复制

查询单个数据操作结果

{
    "id""1692936528247996417",
    "stuid""6840300",
    "name""张三三",
    "age"20,
    "sex"0,
    "dept""计算机学院",
    "address""广东省广州市天河街100号"
}

复制

查询全部数据操作结果

[
    {
        "id""1692936528247996417",
        "stuid""6840300",
        "name""张三三",
        "age"20,
        "sex"0,
        "dept""计算机学院",
        "address""广东省广州市天河街100号"
    },
    {
        "id""1692936528352854017",
        "stuid""6840000",
        "name""李四",
        "age"24,
        "sex"0,
        "dept""计算机学院",
        "address""广东省珠海市香洲路100号"
    }
]

复制

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

package com.zbbmeta.entity;

import lombok.Data;

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

    public R(){}

    public R(Boolean flag){
        this.flag = flag;
    }

    public R(Boolean flag,Object data){
        this.flag = flag;
        this.data = data;
    }

    public R(Boolean flag,String msg){
        this.flag = flag;
        this.msg = msg;
    }

    public R(String msg){
        this.flag = false;
        this.msg = msg;
    }
}

复制

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

{
    "flag"true,
    "data": {
        "id""1692936528247996417",
        "stuid""6840300",
        "name""张三三",
        "age"20,
        "sex"0,
        "dept""计算机学院",
        "address""广东省广州市天河街100号"
    },
    "msg"null
}

复制

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

总结

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

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

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

9.页面基础功能开发

列表功能(非分页版)

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

页面数据模型定义

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

复制

异步请求获取数据

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

复制

添加功能

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

复制

默认状态

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

复制

切换为显示状态

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

复制

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

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

复制

切换弹窗状态时清理数据

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

复制

至此准备工作完成,下面就要调用后台完成添加操作了。添加操作在methodes中添加handleAdd方法

//添加
handleAdd () {
    axios.post("/students",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();
    });
},

复制
  1. 将要保存的数据传递到后台,通过post请求的第二个参数传递json数据到后台
  2. 根据返回的操作结果决定下一步操作
    • 如何是true就关闭添加窗口,显示添加成功的消息
    • 如果是false保留添加窗口,显示添加失败的消息
  3. 无论添加是否成功,页面均进行刷新,动态加载数据(对getAll操作发起调用)

取消添加操作在methodes中添加cancel方法

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

复制

总结

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

删除功能

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

删除操作在methodes中添加handleDelete方法

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

复制

删除操作要有确认信息,避免因为误操作删除数据(想想为什么要这样)

删除操作提示信息

// 删除
handleDelete(row) {
    //1.弹出提示框
    this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
        type:'info'
    }).then(()=>{
        //2.做删除业务
        axios.delete("/students/"+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. 删除操作前弹出提示框避免误操作

修改功能

修改功能可以说是列表功能、删除功能与添加功能的合体。几个相似点如下:

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

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

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

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

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

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

步骤:


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

复制
  • 2.修改操作
//修改
handleEdit() {
    axios.put("/students",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. 根据操作结果不同,显示对应的提示信息(同新增)

10.业务消息统一异常处理

目前的功能制作基本上达成了正常使用的情况,什么叫正常使用呢?但是如果我们搞一个BUG出来,你会发现程序马上崩溃掉。比如后台手工抛出一个异常,看看前端接收到的数据什么样子。所以在表现层做统一的异常处理,使用SpringMVC提供的异常处理器做统一的异常处理。

package com.zbbmeta.advice;

import com.zbbmeta.entity.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author springboot葵花宝典
 * @description: TODO
 */

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

复制

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

//添加
handleAdd () {
 //发送ajax请求
    axios.post("/students",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. 表现层返回结果的模型类中添加消息属性用来传递消息到页面

11.页面功能开发

分页功能

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

<!--分页组件-->
<div class="pagination-container">
    <el-pagination
            class="pagiantion"
            @current-change="handleCurrentChange"
            @size-change="handleSizeChange"
            :current-page="pagination.currentPage"
            :page-sizes="[5, 10, 20, 30]"
            :page-size="pagination.pageSize"
            layout="total,sizes, prev, pager, next, jumper"
            :total="pagination.total">

    </el-pagination>
</div>

复制

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

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

复制

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

getAll() {
  axios.post("/students/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {
      this.dataList = res.data.data.records;
  });
},

复制

后台代码测试

@PostMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
    IPage<Student> page = studentService.getPage(currentPage, pageSize,null);
    return new R(null != page ,page);
}

复制

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

getAll() {
 axios.post("/students/"+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. 分页数据页面回显

删除功能维护

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

@PostMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
    IPage<Student> page = studentService.getPage(currentPage, pageSize,null);
    if( currentPage > page.getPages()){
        page = studentService.getPage((int)page.getPages(), pageSize,null);
    }
    return new R(null != page ,page);
}

复制

条件查询功能

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

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

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

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

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

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

前端页面封装查询条件字段

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

复制

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

<div class="filter-container">
    <el-input placeholder="学生姓名" v-model="pagination.name" style="width: 200px;" class="filter-item"></el-input>
    <el-input placeholder="学生院系" v-model="pagination.dept" style="width: 200px;" class="filter-item"></el-input>
    <el-input placeholder="学号" v-model="pagination.stuid" 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>


复制

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

//分页查询
getAll() {
    // console.log(param);
    this.param.name=this.pagination.name;
    this.param.dept=this.pagination.dept;
    this.param.stuid=this.pagination.stuid;
    //发送异步请求
    axios.post("/students/"+this.pagination.currentPage+"/"+this.pagination.pageSize,this.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;
    });
},

复制

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

@PostMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, @RequestBody Student student){

    IPage<Student> page = studentService.getPage(currentPage, pageSize, student);
    if( currentPage > page.getPages()){
        page = studentService.getPage((int)page.getPages(), pageSize,null);
    }
    return new R(true, page);
}

复制

总结

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

如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!

原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!



文章转载自springboot葵花宝典,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论