SpringBoot【基础篇】---- SSMP整合综合案例

news2024/11/16 2:41:20

SpringBoot【基础篇】---- SSMP整合综合案例

  • 1. 模块创建
  • 2. 实体类开发
  • 3. 数据层开发----基于CRUD查看MP运行日志
  • 查看 MP 的运行日志
  • 4. 数据层开发----分页功能制作
  • 5. 数据层开发----条件查询功能制作
  • 6. 业务层开发
  • 业务层快速开发
  • 7. 表现层开发
  • 8. 表现层消息一致性处理
  • 9. 前后端联通性测试
  • 10. 页面基础功能开发
    • F-1. 列表功能(非分页版)
    • F-2. 添加功能
    • F-3. 删除功能
    • F-4. 修改功能
  • 11. 页面消息一致性处理
  • 12. 页面工程开发
    • 1. 分页功能
    • 2. 删除功能维护
    • 3. 条件查询功能

1. 模块创建

单体服务器,前端不使用前后daunt分离的制作了。
一个服务器即充当后台服务调用,又负责前端页面的展示。
在这里插入图片描述
pom.xml

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

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

application.yml

server:
  port: 80

2. 实体类开发

数据库表

-- ----------------------------
-- 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+');

实体类

package com.example.springboot_08_ssmp.domain;

import lombok.Data;

/**
 * @author dell
 * @version 1.0
 */

@Data
public class Book {

    private Integer id;
    private String type;
    private String name;
    private String description;

}

通过使用 lombok 的 @Data 注解可以快速的完成一个实体类对应的 getter、setter、tostring、equals、hashCode 等方法

3. 数据层开发----基于CRUD查看MP运行日志

导入mybatis-plus对应的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: 80

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

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

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

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

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto

查看 MP 的运行日志

在进行数据层测试的时候,因为基础的CRUD操作均由MP给我们提供了,所以就出现了一个局面,开发者不需要书写SQL语句了,这样程序运行的时候总有一种感觉,一切的一切都是黑盒的,作为开发者我们啥也不知道就完了。如果程序正常运行还好,如果报错了,这个时候就很崩溃,你甚至都不知道从何下手,因为传递参数、封装SQL语句这些操作完全不是你干预开发出来的,所以查看执行期运行的SQL语句就成为当务之急。
SpringBoot整合MP的时候充分考虑到了这点,通过配置的形式就可以查阅执行期SQL语句,配置如下

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: auto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

现在再执行 SQL 语句就可以清楚的看到运行时期 SQL 的执行情况了
在这里插入图片描述

4. 数据层开发----分页功能制作

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

MP提供的分页操作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());
    }

在这里插入图片描述
其中selectPage方法需要传入一个封装分页数据的对象,可以通过new的形式创建这个对象,当然这个对象也是MP提供的,别选错包了。创建此对象时就需要指定分页的两个基本数据

  • 当前显示第几页
  • 每页显示几条数据

可以通过创建Page对象时利用构造方法初始化这两个数据

IPage page = new Page(2, 5);

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

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

原来这个IPage对象中封装了若干个数据,而查询的结果作为IPage对象封装的一个数据存在的,可以理解为查询结果得到后,又塞到了这个IPage对象中,其实还是为了高度的封装,一个IPage描述了分页所有的信息。下面5个操作就是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());		//详细数据
    }

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

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

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

@Configuration
public class MPConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

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

5. 数据层开发----条件查询功能制作

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

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

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

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

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

MP针对字段检查进行了功能升级,全面支持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数据作为条件使用,MP还提供了动态拼装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);
    }

其实就是个格式,没有区别。关于MP的基础操作就说到这里吧,如果这一块知识不太熟悉的小伙伴还是去完整的学习一下MP的知识吧,这里只是蜻蜓点水的用了几个操作而已。

6. 业务层开发

数据层开发告一段落,下面进行业务层开发,其实标准业务层开发很多初学者认为就是调用数据层,怎么说呢?这个理解是没有大问题的,更精准的说法应该是组织业务逻辑功能,并根据业务需求,对数据持久层发起调用。有什么差别呢?目标是为了组织出符合需求的业务逻辑功能,至于调不调用数据层还真不好说,有需求就调用,没有需求就不调用。

一个常识性的知识普及一下,业务层的方法名定义一定要与业务有关,例如登录操作

login(String username,String password);

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

selectByUserNameAndPassword(String username,String password);

我们在开发的时候是可以根据完成的工作不同划分成不同职能的开发团队的。比如一个哥们制作数据层,他就可以不知道业务是什么样子,拿到的需求文档要求可能是这样的

接口:传入用户名与密码字段,查询出对应结果,结果是单条数据
接口:传入ID字段,查询出对应结果,结果是单条数据
接口:传入离职字段,查询出对应结果,结果是多条数据

但是进行业务功能开发的哥们,拿到的需求文档要求差别就很大

接口:传入用户名与密码字段,对用户名字段做长度校验,4-15位,对密码字段做长度校验,824位,对喵喵喵字段做特殊字符校验,不允许存在空格,查询结果为对象。如果为null,返回BusinessException,封装消息码INFO_LOGON_USERNAME_PASSWORD_ERROR

你比较一下,能是一回事吗?差别太大了,所以说业务层方法定义与数据层方法定义差异化很大,只不过有些入门级的开发者手懒或者没有使用过公司相关的ISO标准化文档而已。

多余的话不说了,咱们做案例就简单制作了,业务层接口定义如下:

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 {

    @Autowired
    private BookDao bookDao;

    @Override
    public Boolean save(Book book) {
        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);
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page = new Page(currentPage, pageSize);
        bookDao.selectPage(page, null);
        return page;
    }
}

别忘了对业务层接口进行测试,测试类如下

    @Test
    void testServiceGetPage() {
        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(page.getRecords());		//详细数据
    }

业务层快速开发

其实MP技术不仅提供了数据层快速开发方案,业务层MP也给了一个通用接口,个人观点不推荐使用,凑合能用吧,其实就是一个封装+继承的思想,代码给出,实际开发慎用

业务层接口快速开发

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

业务层接口实现类快速开发,关注继承的类需要传入两个泛型,一个是数据层接口,另一个是实体类

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

如果感觉MP提供的功能不足以支撑你的使用需要,其实是一定不能支撑的,因为需求不可能是通用的,在原始接口基础上接着定义新的API接口就行了,此处不再说太多了,就是自定义自己的操作了,但是不要和已有的API接口名冲突即可。

7. 表现层开发

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

表现层接口如下:

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping
    public List<Book> getAll() {
        return bookService.getAll();
    }

    @PostMapping
    public Boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }

    @PutMapping
    public Boolean update(@RequestBody Book book) {
        return bookService.update(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测试时关注提交类型,对应上即可,不然就会报405的错误码了
在这里插入图片描述

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

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

增删改操作结果

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": "十年沉淀之作"
    }
]

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

@Data
public class Result {

    private Boolean flag;
    private Object data;

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

}

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

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

表现层开发格式也需要转换一下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果这么一折腾,全格式统一,现在后端发送给前端的数据格式就统一了,免去了不少前端解析数据的麻烦。

9. 前后端联通性测试

后端的表现层接口开发完毕,就可以进行前端的开发了。

将前端人员开发的页面保存到lresources目录下的static目录中,建议执行maven的clean生命周期,避免缓存的问题出现。
在这里插入图片描述
在进行具体的功能开发之前,先做联通性的测试,通过页面发送异步提交(axios),这一步调试通过后再进行进一步的功能开发

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

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

10. 页面基础功能开发

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

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

页面数据模型定义

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

异步请求获取数据

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

这样在页面加载时就可以获取到数据,并且由VUE将数据展示到页面上了
在这里插入图片描述

F-2. 添加功能

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

默认状态

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

切换为显示状态

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

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

定义清理数据操作

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

切换弹窗状态时清理数据

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

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

添加操作

//添加
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("操作取消");
},

需要在取消的时候清空表单吗?

F-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();
    });
},

删除操作提示信息

// 删除
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("取消删除操作");
    });
}

F-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();
    });
},

11. 页面消息一致性处理

目前的功能制作基本上达成了正常使用的情况,什么叫正常使用呢?也就是这个程序不出BUG,如果我们搞一个BUG出来,你会发现程序马上崩溃掉。比如后台手工抛出一个异常,看看前端接收到的数据什么样子

{
    "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 Result {

    private Boolean flag;
    private Object data;
    private String msg;

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

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

    public Result(Boolean flag, Object data, String msg) {
        this.flag = flag;
        this.data = data;
        this.msg = 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 () {
                axios.post("/books", this.formData).then((res)=>{
                   //如果操作成功,关闭弹层,显示数据
                   if (res.data.flag) {
                       this.dialogFormVisible = false;
                       this.$message.success(res.data.msg);
                   } else {
                       this.$message.error(res.data.msg);
                   }
                   //最后无论如何都回调一次全部查询函数
                }).finally(()=>{
                   this.getAll();
                });
            },

12. 页面工程开发

1. 分页功能

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

<!--分页组件-->
<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,		//总记录数
	}
},

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

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.date.records;
                });
            },

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

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

2. 删除功能维护

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

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

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

    @GetMapping("{currentPage}/{pageSize}")
    public Result 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(currentPage, pageSize, book);
        }
        return new Result(true, page);
    }

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

    IPage<Book> getPage(int currentPage, int pageSize, Book book);
    @Override
    public IPage<Book> getPage(int currentPage, int pageSize, Book queryBook) {
        System.out.println("测试************");
        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());
        IPage page = new Page(currentPage,pageSize);
        bookDao.selectPage(page,lqw);
        System.out.println("********" + page);
        return page;
    }

最后的条件查询有点问题,没有输入条件时,可以正常查询,但是输入条件后就会报错,暂时未发现原因,搜了搜报错信息,没有完全一样的错误信息,大致相似的是 jdk 的问题,敬请大佬斧正!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STC32G单片机内置ADC及应用编程

一 STC32G单片机内置ADC模块简介 STC32G单片机内部集成了一个12位高速ADC转换器&#xff0c;ADC的最高时钟频率为系统频率的1/2。其输入通道多达15个&#xff08;第15通道为专门测量内部1.19V参考信号源的通道&#xff09;&#xff0c;可分时切换使用。 STC15系列单片机内置AD…

AES加密

来源&#xff1a;链接: b站up主可厉害的土豆 &#xff08;据评论区说图片中有计算错误&#xff0c;但是过程是对的。只是了解过程问题不大&#xff0c;专门研究这一块的话自己可以看视频算一下&#xff09; 准备 首先&#xff0c;明文是固定长度 16字节 128位。 密钥长度可以…

C++语法(18)---- set和map

C语法&#xff08;17&#xff09;---- 二叉搜索树_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130174864 目录 1.set的介绍 1.set使用 1.基本结构 2.insert 3.erase 4.find 5.count 2.multiset 1.count 2.find 2.map的介绍 1.map …

zookeeper + kafka集群搭建详解

目录 1.消息队列介绍 1.为什么需要消息队列 &#xff08;MO&#xff09; 2.使用消息队列的好处 3.消息队列的两种模式 2.Kafka相关介绍 1.Kafka定义 2.Kafka简介 3. Kafka的特性 3.Kafka系统架构 1. Broker&#xff08;服务器&#xff09; 2. Topic&#xff08;一个队…

GaussDB数据库存储过程介绍

文章目录一、前言二、GaussDB中的定义三、存储过程的使用场景四、存储过程的使用优缺点五、存储过程的示例及示例解析1、GaussDB存储过程语法格式2、GaussDB存储过程语法示例3、存储过程的调用方法七、总结一、前言 华为云数据库GaussDB是一款高性能、高安全性的云原生数据库&…

链表基础知识

1.链表必知必会 什么是链表? 链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指针域&#xff08;存放指向下一个节点的指针&#xff09;&#xff0c;最后一个节点的指针域指向null&#xff08;空指针的意思&#…

23北京邮电大学备考经验

目录【写在前面】本科成绩择校历程英语复习数学复习政治复习专业课复习其它建议笔记复盘压力处理恋爱关系【写在最后】【写在前面】 初试成绩&#xff1a; 本科成绩 总体&#xff1a;浙江某双非学校的软件工程专业、综合测评成绩班级前两名、浙江省省级优秀毕业生、发表过论…

【Node】Node.js 资源汇总推荐

【导读】&#xff1a;Node.js 是一个开源、跨平台的&#xff0c;用于编写服务器和命令行的 JavaScript 运行时工具。awesome-nodejs 是sindresorhus发起维护的 Node.js 资源列表&#xff0c;内容包括&#xff1a;命令行工具、日志、调试、HTTP、构建工具、文件系统、模板、Web …

Elasticjob(2.1.4) failover 、misfire及执行线程池分析

Failover 当设置failover为true时候&#xff0c;elasticjob 集群通过zookeeper 的event watcher 监听是否有instance 丢失&#xff0c;然后对丢失instance 对应的分片进行立即执行。重复一下&#xff0c;failover是立即执行&#xff0c;不是按crontab时间来触发&#xff0c;这…

基于RDF本体模型和图数据库实现知识查询与推理

基于RDF本体模型和图数据库实现知识查询与推理 基于RDF本体模型和图数据库实现知识查询与推理一、案例本体模型解释二、数据构建与查询 Here’s the table of contents: 基于RDF本体模型和图数据库实现知识查询与推理 本文主要使用ONgDB图数据库和Neosemantics组件&#xff0c;…

自建个人音乐播放器Navidrome - 内网穿透实现在外随时访问

文章目录 1. 前言2. Navidrome网站搭建2.1 Navidrome下载和安装2.1.1 安装并添加ffmpeg2.1.2下载并配置Navidrome2.1.3 添加Navidrome到系统服务 2.2. Navidrome网页测试 3. 本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4. 公网访问测试5. 结语 转…

【Android实战开发】flutter实现网络请求的方法示例

Flutter网络请求使用的是Dio。Dio是一个强大易用的dart http请求库&#xff0c;支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载……. Flutter json数据解析是使用了json_serializable package包。它是一个自动化源代码生成器&#xff0c;可以为我们…

C++快速幂详解例题

基本概念 什么是快速幂呢&#xff1f;个人理解&#xff0c;就是更快速的计算幂运算。 比如计算a^b 刚学这个算法的时候我也很疑惑&#xff0c;幂运算不是有现成的公式么&#xff0c;直接pow&#xff08;a,b&#xff09;不就好了吗&#xff1f; 后来才明白&#xff0c;pow(a,b)的…

三分钟了解什么是时序数据库

在介绍时序数据库之前&#xff0c;我们先来看看什么是时序数据。时序数据就是基于时间排序的数据&#xff0c;再通过时间坐标将这些数据连接起来&#xff0c;形成一个折线图&#xff0c;直观地展示一个指标在过去一段时间内的走势和规律&#xff0c;帮助定位数据异常点。 时序…

Oracle中Archived redolog的生成

目录 一、问题预览 二、问题解答 一、问题预览 大家都知道 Oracle 中 online redolog切换后会生成 archived redolog&#xff0c;心里默认的就是 online redolog 切换后 archived redolog 已经生成。切换示意图&#xff0c;如下图所示。 但事实真的是这样吗&#xff1f; 二、…

C++ 23 实用工具(一)

C 23 实用工具&#xff08;一&#xff09; 工具函数是非常有价值的工具。它们不仅可以用于特定的领域&#xff0c;还可以应用于任意值和函数&#xff0c;甚至可以创建新的函数并将它们绑定到变量上。 常用函数 你可以使用各种变体的 min、max 和 minmax 函数来对值和初始化列…

【使用ChatGPT自动化】批量转换.xls文件为.xlsx文件

第1次提问&#xff1a; 我&#xff1a;我想使用Python批量转换.xls文件为.xlsx文件&#xff0c;请你提供代码 它&#xff1a; 当涉及到批量处理文件时&#xff0c;我们通常需要使用Python中的os模块和glob模块。os模块用于管理文件和目录&#xff0c;glob模块用于匹配文件路径名…

Visual Studio Code 1.77 发布!

欢迎使用 Visual Studio Code 2023 年3月版。此版本有许多更新&#xff0c;其中一些主要亮点包括&#xff1a; 无障碍改进&#xff1a;新的悬停、通知和 Sticky Scroll 快捷键 复制 GitHub 深度链接&#xff1a;在编辑器内创建永久链接和 HEAD 链接 笔记本保存格式&#xff1…

软件测试流程进阶----四年软件测试总结

工作四年了&#xff0c;我一直希望让自己每年对测试的理解更深入一层。工作一年的时候&#xff0c;我谈轮了自己对各种测试的理解&#xff0c;这一年来&#xff0c;虽然对那些理概念的有所加强&#xff0c;自我感觉没有什么质的变化。前些天听我们公司的一位测试经理讲《敏捷测…

精准抓住核心要点!!!十名面试官总结出这样一份面试通关答案,还不赶紧开始“作弊”通关!!!

金三银四求职季&#xff0c;但最近很多朋友私信说&#xff1a; 熬过了去年的寒冬&#xff0c;却没躲过如今的内卷&#xff1b; 打开Boss直拒&#xff0c;一排已读不回&#xff1b; 大部分回复的都是外包&#xff0c;薪资低于预期&#xff0c;对技术水平要求却远超从前&#x…