黑马SpringBoot --基础篇

导读

课程学习目标

基础篇

  • 能够创建SpringBoot工程
  • 基于SpringBoot实现ssm整合

实用篇

  • 运维实用篇
    • 能够掌握SpringBoot程序多环境开发
    • 能够基于Linux系统发布SpringBoot工程
    • 能够解决线上灵活配置SpringBoot工程的需求开发实用篇
  • 开发实用篇
    • 能够基于SpringBoot整合任意第三方技术

原理篇

  • 掌握SpringBoot内部工作流程
  • 理解SpringBoot整合第三方技术的原理
  • 实现自定义开发整合第三方技术的组件

课程学习前置知识

  • 基础篇
    • Java基础语法
    • Spring与SpringMvC
      • 知道Spring是用来管理bean,能够基于Restful实现页面请求交互功能
    • Mybatis与Mybatis-Plus
      • 基于Mybatis和MybatisPlus能够开发出包含基础CRUD功能的标准Dao模块数据库MysQL
      • 能够读懂基础CRUD功能的SQL语句
    • 服务器
      • 知道服务器与web工程的关系,熟悉web服务器的基础配置
    • maven
      • 知道maven的依赖关系,知道什么是依赖范围,依赖传递,排除依赖,可选依赖,继承web技术(含vue,ElementUI)
      • 知道vue如何发送aiax请求.如何获取响应数据.如何进行数据樭型双向绑定
  • 实用篇
    • Linux (Centeros7)
    • 熟悉常用的Linux基础指令,熟悉Linux系统目录结构
  • 实用开发技术
    • 缓存: Redis、MongoDB、…
    • 消息中间件:RocketMq. RabbitMq………
    • .…
  • 原理篇
    • Spring
    • 了解Spring加载bean的各种方式
    • 知道Spring容器底层工作原理,能够阅读简单的Spring底层源码

Spring程序与SpringBoot程序对比
在这里插入图片描述

基于idea开发SpringBoot程序需要确保联网且能够加载到程序框架结构

1.快速上手

1.1SpringBoot入门程序开发

SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程

1.1.1基础创建方式——常规

笔记小结

  1. 开发SpringBoot程序可以根据向导进行联网快速制作
  2. SpringBoot程序需要基于JDK8进行制作
  3. SpringBoot程序中需要使用何种功能通过勾选选择技术
  4. 运行SpringBoot程序通过运行Application程序入口进行

步骤一:创建新模块,选择Spring Initializr,并配置模块相关基础信息

在这里插入图片描述

步骤二:选择当前模块需要使用的技术集

在这里插入图片描述

步骤三:开发控制器类

//Rest模式
@RestController
@RequestMapping("/books")
public class BookController {
    @GetMapping
    public String getById(){
        System.out.println("springboot is running...");
        return "springboot is running...";
    }
}

步骤四:运行自动生成的Application类
在这里插入图片描述

1.1.2手动创建方式——补充

最简SpringBoot程序所包含的基础文件

  • 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.7</version>
        </parent>
        <groupId>com.example</groupId>
        <artifactId>springboot_01_01_quickstart</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  • Application类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@SpringBootApplication启动类

在这里插入图片描述

参考链接:IDEA2022.01 创建Spring Boot项目_刘代码的博客-CSDN博客_idea2022创建springboot

1.1.3基础创建方式——下载

笔记小结

  1. 开发SpringBoot程序可以根据向导进行联网快速制作
  2. SpringBoot程序需要基于JDK8进行制作
  3. SpringBoot程序中需要使用何种功能通过勾选选择技术
  4. 运行SpringBoot程序通过运行Application程序入口进行

步骤一:基于SpringBoot官网创建项目

​ 地址:https://start.spring.io/

步骤二:基于配置进行选择

在这里插入图片描述

当得到压缩包,导入即可

后续具体实现参考基础步骤(一)

1.1.4基础创建方式——阿里云

笔记小结

  1. 选择start来源为自定义URL
  2. 输入阿里云start地址
  3. 创建项目

基于阿里云创建项目,地址:https://start.aliyun.com

在这里插入图片描述

后续具体实现参考基础步骤(一)

注意:

  1. 阿里云提供的坐标版本较低,如果需要使用高版本,进入工程后手工切换SpringBoot版本
  2. 阿里云提供的工程模板与Spring官网提供的工程模板略有不同

在这里插入图片描述

1.1.5基础创建方式——导入

笔记小结

  1. 创建普通Maven工程
  2. 继承spring-boot-starter-parent
  3. 添加依赖spring-boot-starter-web
  4. 制作引导类Application

步骤一:创建maven模块

在这里插入图片描述

步骤二: 继承spring-boot-starter-parent 和添加依赖spring-boot-starter-web

在这里插入图片描述

步骤三:制作引导类Application

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

在这里插入图片描述

若出现导入注解爆红,在导入maven后记得刷新maven
在这里插入图片描述

1.2SpringBoot概述

SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程

  1. Spring程序缺点
    • 依赖设置繁琐
    • 配置繁琐
  2. SpringBoot程序优点
    • 起步依赖(简化依赖配置)
    • 自动配置(简化常用工程相关配置)
    • 辅助功能(内置服务器,……)

1.2.1parent

笔记小结

  1. 开发SpringBoot程序要继承spring-boot-starter-parent
  2. spring-boot-starter-parent中定义了若干个依赖管理
  3. 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
  4. 继承parent的形式也可以采用引入依赖的形式实现效果

当我们两个项目需要公用一套依赖时会导致依赖重复
在这里插入图片描述
SpringBoot为我们统一了版本的管理

在这里插入图片描述

SpringBoot会根据不同的SpringBoot版本定义一些兼容性强的版本号列表,它的执行流程如下

在这里插入图片描述

补充:这些依赖管理,继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突

继承parent的形式也可以采用引入依赖的形式实现效果
在这里插入图片描述

1.2.2starter

笔记小结

减少依赖冲突

spring-boot-starter-web.pom里面又有什么内容呢

在这里插入图片描述

starter和parent的区别

  1. starter

    • SpringBoot中常见项目名称,定义了当前项目使用的所有依赖坐标,以达到减少依赖配置的目的
  2. parent

    • 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的

    • spring-boot-starter-parent各版本间存在着诸多坐标版本不同

注意:

实际开发中

  • 使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本V
  • 如发生坐标错误,再指定Version(要小心版本冲突)

1.2.3引导类

笔记小结

  1. SpringBoot工程提供引导类用来启动程序
  2. SpringBoot工程启动后创建并初始化Spring容器
  • 启动方式

    @SpringBootApplication
    public class Springboot01QuickstartApplication {
        public static void main(String[] args) {
            SpringApplication.run(Springboot01QuickstartApplication.class, args);
        }
    }
    
  • SpringBoot的引导类是Boot工程的执行入口,运行main方法就可以启动项目

  • SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean

补充:

此时可通过获取ConfigurableApplicationContext对象获得bean对象

@SpringBootApplication
public class Springboot0101QuickstartApplication {
 public static void main(String[] args) {
     ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args);
     BookController bean = ctx.getBean(BookController.class);
     System.out.println("bean======>" + bean);
 }
}

引导类只会扫描当前包与其子包下的bean,若存放位置错误,会报错
在这里插入图片描述
在这里插入图片描述

1.2.4内嵌tomcat

笔记小结

  1. 内嵌Tomcat服务器是SpringBoot辅助功能之一
  2. 内嵌Tomcat工作原理是将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理
  3. 变更内嵌服务器思想是去除现有服务器,添加全新的服务器

在spring-boot-starter-web中存有tomcat的核心对象

在这里插入图片描述

可使用maven依赖管理变更起步依赖项

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--web起步依赖环境中,排除Tomcat起步依赖-->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--添加Jetty起步依赖,版本由SpringBoot的starter控制-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

补充:

  1. Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty

  2. 内置服务器:

    • tomcat(默认) apache出品,粉丝多,应用面广,负载了若干较重的组件

    • jetty 更轻量级,负载性能远不及tomcat

    • undertow undertow,负载性能勉强跑赢tomcat

2.基础配置

2.1属性配置

笔记小结

  1. SpringBoot默认配置文件application.properties
  2. SpringBoot中导入对应starter后,提供对应配置属性
  3. 书写SpringBoot配置采用关键字+提示形式书写
  • 修改服务器端口
    在这里插入图片描述

  • SpringBoot默认配置文件application.properties,通过键值对配置对应属性

  • 修改配置

    修改服务器端口

    server.port=80
    

    关闭运行日志图标(banner)

    spring.main.banner-mode=off
    

    设置日志相关

    logging.level.root=debug
    

    注意:若SpringBoot中未导入对应starter,那么则无法提供对应配置属性

  • SpringBoot内置属性查询

    Common Application Properties (spring.io)

    官方文档中参考文档第一项:Application Properties

2.2配置文件分类

笔记小结

  1. SpringBoot提供了3种配置文件的格式
    • properties(传统格式/默认格式)
    • yml(主流格式)
    • yaml
  2. 配置文件间的加载优先级
    • properties(最高)
    • yml
    • yaml(最低)
  3. 不同配置文件中相同配置按照加载优先级相互覆盖不同配置文件中不同配置全部保留

1.SpringBoot提供了多种属性配置方式

  • application.properties

    server.port=80
    
  • application.yml

    server:
     port: 81
    
  • application.yaml

    server:
     port: 82
    

示例
在这里插入图片描述

2.SpringBoot配置文件加载顺序

  • application.properties > application.yml > application.yaml

补充:若加载优先级越低,则不会覆盖

3.常用配置文件种类

  • application.yml

2.3yaml文件

笔记小结

  1. yaml语法规则
    • 大小写敏感
    • 属性层级关系使用多行描述,每行结尾使用冒号结束
    • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许 使用Tab键)
    • 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔 )
    • # 表示注释
  2. 注意属性名冒号后面与数据之间有一个空格
  3. 字面值、对象数据格式、数组数据格式(略)

YAML(YAML Ain’t Markup Language),一种数据序列化格式

1.优点:

  • 容易阅读
  • 容易与脚本语言交互
  • 以数据为核心,重数据轻格式

2.YAML文件扩展名

  • .yml(主流)
  • .yaml

XML方式

<enterprise>
    <name>itcast</name>
    <age>16</age>
    <tel>4006184000</tel>
</enterprise>

Properties方式

enterprise.name=itcast
enterprise.age=16
enterprise.tel=4006184000

Yaml方式

enterprise:
name: itcast
age: 16
tel: 4006184000

Yml方式

enterprise:
name: itcast
age: 16
tel: 4006184000

3.yaml语法规则

笔记小结

  1. 使用**@Value**配合SpEL读取单个数据
  2. 如果数据存在多层级,依次书写层级名称即可
  • 大小写敏感

  • 属性层级关系使用多行描述,每行结尾使用冒号结束

  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(注意:不允许使用Tab键)

  • 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)

  • # 表示注释

  • 核心规则:数据前面要加空格与冒号隔开

  • 字面值表示方式

    boolean: TRUE #TRUE,true,True,FALSE,false,False均可
    float: 3.14 #6.8523015e+5 #支持科学计数法
    int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制
    null: ~ #使用~表示null
    string: HelloWorld #字符串可以直接书写
    string2: "Hello World" #可以使用双引号包裹特殊字符
    date: 2018-02-17 #日期必须使用yyyy-MM-dd格式
    datetime: 2018-02-17T15:02:31+08:00 #时间和日期之间使用T连接,最后使用+代表时区
    

    注意:

    1. yaml文件中对于数字的定义支持进制书写格式,如需使用字符串请使用引号明确标注
  • 数组表示方式:在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔

    subject:
    - Java
    - 前端
    - 大数据
    enterprise:
    name: itcast
    age: 16
    subject:
    - Java
    - 前端
    - 大数据
    likes: [王者荣耀,刺激战场] #数组书写缩略格式
    
  • 对象数组格式

    users: #对象数组格式
    - name: Tom
      age: 4
    - name: Jerry
      age: 5
    users: #对象数组格式二
    -
    name: Tom
    age: 4
    -
    name: Jerry
    age: 5 #对象数组缩略格式
    users2: [ { name: Tom , age: 4 } , { name: Jerry , age: 5 } ]
    

2.4yaml数据读取

笔记小结

简单读取

  1. 使用**@Value**配合SpEL读取单个数据
  2. 如果数据存在多层级,依次书写层级名称即可

复杂读取

  1. 封装Environment对象
  2. 使用Environment对象的getProperty方法根据yml文件中的key获取对象的value

简化配置

  1. 在配置文件中可以使用**${属性名}**方式引用属性值
  2. 如果属性中出现特殊字符,可以使用双引号包裹起来作为字符解析

封装Environment对象

  1. 使用Environment对象封装全部配置信息
  2. 使用@Autowired自动装配数据到Environment对象中

封装自定义对象✳

  1. 使用**@ConfigurationProperties**注解绑定配置信息到封装类中
  2. 封装类需要定义为Spring管理的bean,否则无法进行属性注入

2.4.1读取,使用@Value读取单个数据

属性名引用方式:${一级属性名.二级属性名……}
在这里插入图片描述

示例:

  @Value("${name}")
    public String name;

    @Value("${subject[0]}")
    public String subject;

    @Value("${customer[0].name}")
    public String customer;

    @GetMapping
    public String get() {
        System.out.println("springboot is running...");
        System.out.println(name);
        System.out.println(subject);
        System.out.println(customer);
        return "springboot is running...";
    }

2.4.2配置,在配置文件中可以使用属性名引用方式引用属性

baseDir: /usr/local/fire
center:
    dataDir: ${baseDir}/data
    tmpDir: ${baseDir}/tmp
    logDir: ${baseDir}/log
    msgDir: ${baseDir}/msgDir

2.4.3配置,属性值中如果出现转移字符,需要使用双引号包裹

lesson: "Spring\tboot\nlesson"

2.4.4封装,封装全部数据到Environment对象

在这里插入图片描述

@Autowired自动装配,

自定装配后,可将数据显示出来

示例

//Environment可获取application.yml文件中的所有key:value
@Autowired
private Environment env;

…………
    
//通过Environment对象的getProperty方法根据yml文件中的key获取对象的value
System.out.println(env.getProperty("users[0].age"));

2.4.5封装,自定义对象封装指定数据(重点)

在这里插入图片描述

示例:

创建entity包,创建Database类

package com.example.entity;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

//1.定义数据模型封装yaml文件中对应的数据
//2.定义为spring管控的bean
@Component
//3.指定加载的数据---需要指定前缀
@ConfigurationProperties(prefix = "datasource")
public class Datasource {
    String driveClassName;
    String url;
    String userName;
    String password;

    @Override
    public String toString() {
        return "Datasource{" +
                "driveClassName='" + driveClassName + '\'' +
                ", url='" + url + '\'' +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public String getDriveClassName() {
        return driveClassName;
    }

    public void setDriveClassName(String driveClassName) {
        this.driveClassName = driveClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

注意:

  1. 使用**@ConfigurationProperties**注解绑定配置信息到封装类中
  2. 封装类需要定义为Spring管理的bean,否则无法进行属性注入(当自定义对象和application.yml文件都为springboot所管理时即可完成属性注入)

可验证查看:

…………
    @Autowired
    private Datasource datasource;
…………
    //通过自定义对象装配指定数据
    System.out.println(datasource);    

3.整合第三方技术

笔记小结

整合第三方技术通用方式

  • 导入对应的starter
  • 根据提供的配置格式,配置非默认值对应的配置项

3.1整合JUnit

笔记小结

步骤:

  1. 导入测试对应的starter (SpringBoot默认自动导入)
  2. 测试类使用@SpringBootTest修饰(SpringBoot默认自动导入)
  3. 使用自动装配的形式添加要测试的对象
  4. 测试类如果存在于引导类所在包或子包中无需指定引导类
  5. 测试类如果不存在于引导类所在的包或子包中需要通过classes 属性指定引导类

SpringBoot整合JUnit

  • 名称:@SpringBootTest

  • 类型:测试类注解

  • 位置:测试类定义上方

  • 作用:设置JUnit加载的SpringBoot启动类

  • 范例: @SpringBootTest class Springboot04JUnitApplicationTests {}

  • 相关属性

    • classes:设置SpringBoot启动类

    注意:如果测试类在SpringBoot启动类的包子包中,可以省略启动类的设置,也就是省略classes的设定

    补充:通过配置classes属性可以精准的指定配置类或者 引导类加载的位置,否则会自动在测试类所在的包或父包中寻找

示例:

创建dao/BookDao接口

package com.example.dao;

public interface BookDao {
    public void save();
}

创建dao/impl/BookDaoImpl

package com.example.dao.impl;

import com.example.dao.BookDao;
import org.springframework.stereotype.Repository;


@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao is running~");
    }
}

补充:@Repository注解也可换为@Component注解,但此时@Repository注解一般写在数据层上

@Repository用在持久层的接口上,这个注解是将接口的一个实现类交给spring管理。

​ 这是因为@Repository注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。

创建test/java/com/example/Springboot04JunitApplicationTests

@SpringBootTest
class JunitApplicationTests {

    @Autowired
    private BookDaoImpl bookDao;

    @Test
    void contextLoads() {
        bookDao.save();
    }
}

@SpringBootTest:测试类注解 ,设置JUnit加载的SpringBoot启动类

注意:

​ 如果测试类在不SpringBoot启动类的包子包中,则会报错

在这里插入图片描述

​ 因为,此时取得被测试的对象是在容器中的,而我们现在没有按要求防止测试类的位置,所以不能拿到springboot容器。换句话说,因为测试要拿容器里面的bean,容器由配置类里面run方法创建,所以需要去找配置类。

​ 所以,可通过添加注解的classes方式进行指定配置类的位置来进行 解决

  • @SpringBootTest(classes = Springboot04JunitApplication.class) --------此方法常用
  • @ContextConfiguration(classes = Springboot04JunitApplication.class)

3.2整合MyBatis

笔记小结

步骤:

  1. 勾选MyBatis技术,也就是导入MyBatis对应的starter
  2. 数据库连接相关信息转换成配置 (datasource)
  3. 数据库SQL映射需要添加**@Mapper**被容器识别到
  • 核心配置:数据库连接相关信息(连什么?连谁?什么权限)
  • 映射配置:SQL映射(XML/注解)

步骤一:创建新模块,选择Spring初始化,并配置模块相关基础信息,并选择当前模块需要使用的技术集(MyBatis、MySQL)

在这里插入图片描述

步骤二:设置数据源参数

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

注意:

SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区

jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC

或在MySQL数据库端配置时区解决此问题

步骤三:定义数据层接口与映射配置

@Mapper
public interface UserDao {

    @Select("SELECT * FROM tb_user WHERE id=#{id}")
    public User getById(Integer id);

    @Select("SELECT * FROM tb_user")
    public List<User> getAll();
}

@Mapper注解

1:为了把mapper这个DAO交給Spring管理

2:为了不再写mapper映射文件

3:为了给mapper接口 自动根据一个添加@Mapper注解的接口生成一个实现类

换种方式也可以在启动类上面加MapperScan(“mapper层所在包的全名”),让springboot认识你的mapper层

步骤四:测试类中注入dao接口,测试功

@SpringBootTest
class Springboot05MybatisApplicationTests {

    @Autowired
    public UserDao userDao;
    @Test
    void contextLoads() {
        System.out.println(userDao.getById(1));
        System.out.println(userDao.getAll());
    }
}

示例:

创建domain下的User类

package com.example.domain;

public class User {
    Integer id;
    String username;
    String password;
    String gender;
    String addr;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }
}

domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean

创建dao包下的UserDao接口

package com.example.dao;

import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserDao {

    @Select("SELECT * FROM tb_user WHERE id=#{id}")
    public User getById(Integer id);

    @Select("SELECT * FROM tb_user")
    public List<User> getAll();
}

在测试类中进行测试

package com.example;

import com.example.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot05MybatisApplicationTests {

    @Autowired
    public UserDao userDao;

    @Test
    void contextLoads() {
        System.out.println(userDao.getById(1));
        System.out.println(userDao.getAll());
    }

}

设置application.yml

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

补充:常见MyBatis问题

  1. MySQL 8.X驱动强制要求设置时区
    • 修改url,添加serverTimezone设定
    • 修改MySQL数据库配置(略)
  2. 驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver

3.3整合MyBatis-Plus

笔记小结

  1. 手工添加MyBatis-Plus对应的starter
  2. 数据层接口使用BaseMapper简化开发
  3. 需要使用的第三方技术无法通过勾选确定时,需要手工添加坐标

MyBatis-Plus与MyBatis区别

  • 导入坐标不同
  • 数据层实现简化

1.手动添加SpringBoot整合MyBatis-Plus的坐标,可以通过mvnrepository获取

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

补充:由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version

可前往网站获取最新坐标:Maven Repository: com.baomidou » mybatis-plus-boot-starter (mvnrepository.com)

2.定义数据层接口与映射配置,继承BaseMapper

@Mapper
public interface UserDao extends BaseMapper<User> {
}

3.其他同SpringBoot整合MyBatis

示例:

创建domain下的User类

package com.example.domain;

public class User {
    Integer id;
    String username;
    String password;
    String gender;
    String addr;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }
}

domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean

创建dao包下的UserDao接口

package com.example.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDao extends BaseMapper<User> {


}

设置application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    username: root
    password: qweasdzxc
    
#mybatis-plus
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tb_

注意:在mybatis-plus中需要定义table-prefix,因为MyBatisPlus的映射缘故。例如mysql表名为tb_user,会被MP变为user

在测试类中进行测试

package com.example;

import com.example.dao.UserDao;
import com.example.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class Springboot05MybatisPlusApplicationTests {

    @Autowired
    public UserDao userDao;

    @Test
    void contextLoads() {
        User user = userDao.selectById(1);
        System.out.println(user);
        List<User> list = userDao.selectList(null);
        System.out.println(list);
    }

}

3.3整合Druid

笔记小结

  1. 整合Druid需要手动导入Druid对应的starter
  2. 根据Druid提供的配置方式进行配置
  • 导入对应的starter
  • 配置对应的设置或采用默认配置

1.导入Druid对应的starter

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

补充:由于SpringBoot中未收录Druid的坐标版本,需要指定对应的Version

可前往网站获取最新坐标:Maven Repository: com.alibaba » druid-spring-boot-starter (mvnrepository.com)

2.配置application.yml

  • 方式一:指定数据源类型
spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

  • 方式二:变更Druid的配置方式(标准版)✳
spring:
	datasource:
       druid:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
        username: root
        password: root

示例:

创建domain下的User类

package com.example.domain;

public class User {
    Integer id;
    String username;
    String password;
    String gender;
    String addr;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }
}

domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean

创建dao包下的UserDao接口

package com.example.dao;

import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserDao {

    @Select("SELECT * FROM tb_user WHERE id=#{id}")
    public User getById(Integer id);

    @Select("SELECT * FROM tb_user")
    public List<User> getAll();
}

在测试类中进行测试

package com.example;

import com.example.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot07DruidApplicationTests {


    @Autowired
    public UserDao userDao;

    @Test
    void contextLoads() {
        System.out.println(userDao.getById(1));
        System.out.println(userDao.getAll());
    }

}

设置application.yml

#方式一
#spring:
#  datasource:
#    driver-class-name: com.mysql.cj.jdbc.Driver
#    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
#    username: root
#    password: qweasdzxc
#    type: com.alibaba.druid.pool.DruidDataSource

#方式二(标准写法)

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



编写测试类:

package com.example;

import com.example.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot07DruidApplicationTests {


    @Autowired
    public UserDao userDao;

    @Test
    void contextLoads() {
        System.out.println(userDao.getById(1));
        System.out.println(userDao.getAll());
    }

}

在这里插入图片描述

补充:当出现 Init DruidDataSource时代表Druid加载成功

4.SSMP整合案例(重点)

笔记小结

  1. pom.xml

    配置起步依赖

  2. application.yml

    设置数据源、端口、框架技术相关配置等

  3. dao 继承BaseMappe

    设置@Mapper

  4. dao测试类

  5. service

    调用数据层接口或MyBatis-Plus提供的接口快速开发

  6. service测试类

  7. controller

    基于Restful开发,使用Postman测试跑通功能

  8. 页面

    放置在resources目录下的static目录中

案例实现方案分析

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

SSMP案例制作流程解析

  1. 先开发基础CRUD功能,做一层测一层
  2. 调通页面,确认异步提交成功后,制作所有功能
  3. 添加分页功能与查询功能

4.1模块创建

在这里插入图片描述

  1. 勾选SpringMVC与MySQL坐标

在这里插入图片描述

  1. 添加依赖

    <!--MyBatisPlus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.15</version>
    </dependency>
    
  2. 修改配置文件为yml格式

  3. 设置端口为80方便访问

4.2实体类开发

笔记小结

使用Lombok依赖

Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发

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

补充:lombok版本由SpringBoot提供,无需指定版本

Lombok:常用注解:@Data

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

在这里插入图片描述

补充:为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等

4.3数据层开发

4.3.1基础功能

笔记小结

  1. 手工导入starter坐标(2个)
  2. 配置数据源与MyBatisPlus对应的配置
  3. 开发Dao接口(继承BaseMapper)
  4. 制作测试类测试Dao功能是否有效

技术实现方案

  • MyBatisPlus
  • Druid

1.导入MyBatisPlus与Druid对应的starter

<!--MyBatisPlus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.15</version>
</dependency>

2.配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)

#tomcat服务器
server:
  port: 80

#Jdbc,druid
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      username: root
      password: qweasdzxc

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

3.制作测试类测试结果

package com.example.dao;

import com.example.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class BookDaoTest {
    @Autowired
    private BookDao bookDao;

    @Test
    void testSave() {
        Book book = new Book();
        book.setName("玥玥");
        book.setType("美女");
        book.setDescription("大美女只为你着迷");
        System.out.println(bookDao.insert(book));
    }

    @Test
    void testDelete() {
        System.out.println(bookDao.deleteById(1));
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(3);
        book.setName("杰哥");
        book.setType("帅哥");
        book.setDescription("大帅哥只为你着迷");
        System.out.println(bookDao.updateById(book));
    }

    @Test
    void testSelect() {
        System.out.println(bookDao.selectById(2));
        System.out.println(bookDao.selectList(null));
    }

    @Test
    void testGetPage() {

    }
    @Test
    void testGetByCondition(){

    }
}

补充:

1.MyBatisPlus默认:采用驼峰映射规则,例如 Users 对应的数据库表为 users,MyUserTable 对应的数据库表为 my_user_table

2.bookDao.insert(book)插入时,记得给application.yml文件中配置 id-type: auto 的属性,防止mysql数据库自增长失效,从而插入雪花值

4.3.2日志功能

笔记小结

使用配置方式开启日志,设置日志输出方式为标准输出

1.为方便调试可以开启MyBatisPlus的日志

mybatis-plus:	
	configuration:
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

测试结果:
在这里插入图片描述

4.3.3分页功能

笔记小结

  1. 使用IPage封装分页数据
  2. 分页操作依赖MyBatisPlus分页拦截器实现功能
  3. 借助MyBatisPlus日志查阅执行SQL语句

1.分页操作需要设定分页对象IPage

@Test
void testGetPage() {
    IPage<Book> iPage = new Page<>(1, 5);
    bookDao.selectPage(iPage, null);
    System.out.println(iPage.getRecords());
    System.out.println(iPage.getCurrent());
    System.out.println(iPage.getPages());
    System.out.println(iPage.getSize());
    System.out.println(iPage.getTotal());
}

2.IPage对象中封装了分页操作中的所有数据

  • 数据 getRecords
  • 当前页码值 getCurrent
  • 每页数据总量 getTotal
  • 最大页码 getPages
  • 数据总量 getTotal

3.分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能, 使用MyBatisPlus拦截器实现

@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加具体的拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

我们所做的所有东西都得受spring进行管理,而spring是用来管理bena的,因此我们需要使用spring管理第三方bean的方式始化加载出来

@Configuration注解作用

1.告诉spring这是一个配置类,相当于spring的xml配置文件

2.被@Configuration 注解的类,会被cglib代理进行增强

3.@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean之间的依赖关系,保证@Bean的对象作用域受到控制,避免多例

@Bean注解作用

用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理

4.3.4条件查询功能

笔记小结

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

使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用

@Test
void testGetByCondition(){
    IPage page = new Page(1,10);
    LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
    lqw.like(Book::getName,"Spring");
    bookDao.selectPage(page,lqw);
}
@Test
void testGetByCondition(){
    QueryWrapper<Book> qw = new QueryWrapper<Book>();
    qw.like("name","Spring");
    bookDao.selectList(qw);
}

支持动态拼写查询条件✳

@Test
void testGetByCondition(){
    String name = "Spring";
    IPage page = new Page(1,10);
    LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
    lqw.like(Strings.isNotEmpty(name),Book::getName,"Spring");
    bookDao.selectPage(page,lqw);
}

补充:

Java中的双冒号写法::

1.表达式:person -> person.getName();可以替换成:Person::getName

示例:

@Test
void testGetByCondition() {
    String name = "spring";
    //        String name = null;
    QueryWrapper<Book> qw = new QueryWrapper<>();
    qw.like(name != null, "name", name);
    bookDao.selectList(qw);
}

@Test
void testGetByCondition2() {
    String name = "spring";
    //        String name = null;
    LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
    lqw.like(name != null, Book::getName, name);
    bookDao.selectList(lqw);
}

4.4业务层开发

Service层接口定义与数据层接口定义具有较大区别,不要混用

  • selectByUserNameAndPassword(String username,String password);
  • login(String username,String password)

4.4.1通用方式

笔记小结

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

接口定义

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

实现类定义

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

补充:@Service注解用于类上,标记当前类是一个service类,加上该注解会将当前类自动注入到spring容器中

测试类定义

@SpringBootTest
public class BookServiceTest {
    @Autowired
    private BookService bookService;
    @Test
    void testGetById(){
        bookService.getById(9);
    }
    @Test
    void testGetAll(){
        bookService.getAll();
    }
    @Test
    void testGetByPage(){
        bookService.getByPage(1,5);
    }
    … …
}

4.4.2快速开发✳

笔记小结

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

快速开发方案

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

1.接口定义
在这里插入图片描述

2.实现类定义
在这里插入图片描述

3.实现类追加功能

在这里插入图片描述

  1. 测试类定义(略)

示例:

创建service/IBookService接口

package com.example.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.domain.Book;

/**
 * 需要继承IService<Book>
 **/
public interface IBookService extends IService<Book> {

    //自定义方式添加接口
    Boolean savaBook(Book book);
}

创建service/impl/BookServiceImpl实现类

package com.example.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * 需要继承 ServiceImpl<BookDao, Book>类
 * 需要好实现  IBookService接口
 **/
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
    private final BookDao bookDao;

    @Autowired
    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    //实现自定义方式的接口
    @Override
    public Boolean savaBook(Book book) {
        return bookDao.insert(book) > 0;
    }
}

创建test/java包下创建dao/service下的测试类

package com.example.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.domain.Book;
import com.example.service.impl.BookServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class BookServiceTest {

    @Autowired
    BookServiceImpl bookService;

    @Test
    void testSave() {
        Book book = new Book();
        book.setName("玥玥");
        book.setType("美女");
        book.setDescription("大美女只为你着迷");
        //System.out.println(bookService.save(book));
        System.out.println(bookService.savaBook(book));
    }

    @Test
    void testDelete() {
        System.out.println(bookService.removeById(1));
    }

    @Test
    void testUpdate() {
        Book book = new Book();
        book.setId(3);
        book.setName("杰哥");
        book.setType("帅哥");
        book.setDescription("大帅哥只为你着迷");
        System.out.println(bookService.updateById(book));
    }

    @Test
    void testSelect() {
        System.out.println(bookService.list(null));
    }

    @Test
    void testGetPage() {
        IPage<Book> iPage = new Page<>(1, 5);
        bookService.page(iPage);
        System.out.println(iPage.getRecords());
        System.out.println(iPage.getCurrent());
        System.out.println(iPage.getPages());
        System.out.println(iPage.getSize());
        System.out.println(iPage.getTotal());
    }
}

4.5表现层开发

  • 基于Restful进行表现层接口开发
  • 使用Postman测试表现层接口功能

4.5.4基础开发

笔记小结

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

功能测试

@RestController
@RequestMapping("/books")
public class BookController {
    @Autowired
    private IBookService bookService;
    @GetMapping
    public List<Book> getAll(){
        return bookService.list();
    }
}

在这里插入图片描述

表现层接口开发

@RestController
@RequestMapping("/books")
public class BookController {
    //此处是IBookService接口
    @Autowired
    private IBookService bookService;

    //增加
    @PutMapping
    public Boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }

    //删除
    @DeleteMapping("/{id}")
    public Boolean delete(@PathVariable Integer id) {
        return bookService.removeById(id);
    }

    //修改
    @PostMapping
    public Boolean update(@RequestBody Book book) {
        return bookService.updateById(book);
    }

    //查询--单个
    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id) {
        return bookService.getById(id);
    }

    //查询--所有
    @GetMapping
    public List<Book> getAll() {
        return bookService.list();
    }

    //查询--分页
    @GetMapping("/{currentPage}/{pageSize}")
    public IPage<Book> getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
        return bookService.getPage(currentPage, pageSize);
    }

}

postman测试

  • 分页

在这里插入图片描述

4.5.2消息一致性处理

笔记小结

  1. 设计统一的返回值结果类型便于前端开发读取数据
  2. 返回值结果类型可以根据需求自行设定,没有固定格式
  3. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前 后端数据协议

在这里插入图片描述

1.设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议

创建controller/utils下的R类

@Data
public class R{
    private Boolean flag;
    private Object data;
}
@Data
public class R{
    private Boolean flag;
    private Object data;
    public R(){
    }
    public R(Boolean flag){
        this.flag = flag;
    }
    public R(Boolean flag,Object data){
        this.flag = flag;
        this.data = data;
    }
}

2.表现层接口统一返回值类型结果

@RestController
@RequestMapping("/books")
public class BookController {
    @Autowired
    private IBookService bookService;
    @PostMapping
    public R save(@RequestBody Book book){
        Boolean flag = bookService.insert(book);
        return new R(flag);
    }
    @PutMapping
    public R update(@RequestBody Book book){
        Boolean flag = bookService.modify(book);
        return new R(flag);
    }
    @DeleteMapping("/{id}")
    public R delete(@PathVariable Integer id){
        Boolean flag = bookService.delete(id);
        return new R(flag);
    }
    @GetMapping("/{id}")
    public R getById(@PathVariable Integer id){
        Book book = bookService.getById(id);
        return new R(true,book);
    }
    @GetMapping
    public R getAll(){
        List<Book> bookList = bookService.list();
        return new R(true ,bookList);
    }
    @GetMapping("/{currentPage}/{pageSize}")
    public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
        IPage<Book> page = bookService.getPage(currentPage, pageSize);
        return new R(true,page);
    }
}

4.5.3前后端协议联调

  1. 单体项目中页面放置在resources/static目录下
  2. created钩子函数用于初始化页面时发起调用
  3. 页面使用axios发送异步请求获取数据后确认前后端是否联通
  • 前后端分离结构设计中页面归属前端服务器
  • 单体工程中页面放置在resources目录下的static目录中(建议执行clean)

1.前端发送异步请求,调用后端接口

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

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

2.列表页

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

补充:将查询数据返回到页面,利用前端数据双向绑定进行数据展示

3.弹出添加窗口

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

4.清除数据

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

补充:弹出添加Div时清除表单数据

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

5.添加

//添加
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();
    });
}

补充:请求方式使用POST调用后台对应操作,添加操作结束后动态刷新页面加载数据,根据操作结果不同,显示对应的提示信息

6.取消添加

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

7.删除

handleDelete(row) {
    // console.log(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(() => {
            //2.重新加载数据
            this.getAll();
        });
    }).catch(() => {
        //3.取消删除
        this.$message.info("取消操作");
    });
}

注意:请求方式使用Delete调用后台对应操作

补充:删除操作需要传递当前行数据对应的id值到后,删除操作结束后动态刷新页面加载数据,根据操作结果不同,显示对应的提示信息,删除操作前弹出提示框避免误操作(防手抖)

8.弹出修改窗口

//弹出编辑窗口
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(() => {
        //2.重新加载数据
        this.getAll();
    });
}

注意:res.data.flag && res.data.data != null 逻辑判断

补充:加载要修改数据通过传递当前行数据对应的id值到后台查询数据,利用前端数据双向绑定将查询到的数据进行回显

9.修改

//修改
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();
    });
} 

注意:请求方式使用PUT调用后台对应操作

补充:修改操作结束后动态刷新页面加载数据(同新增). 根据操作结果不同,显示对应的提示信息(同新增 )

10.取消添加和修改

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

4.5.4业务消息一致性处理

笔记小结

  1. 使用注解**@RestControllerAdvice**定义SpringMVC异常处理 器用来处理异常的
  2. 异常处理器必须被扫描加载,否则无法生效
  3. 表现层返回结果的模型类中添加消息属性用来传递消息到页面
  • 业务操作成功或失败返回数据格式

在这里插入图片描述

  • 后台代码BUG导致数据格式不统一性

在这里插入图片描述

11.对异常进行统一处理,出现异常后,返回指定信息

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

注意:ex.printStackTrace();异常打印

补充:

​ 一旦项目中发生了异常,就会进入使用了RestControllerAdvice注解类中使用了ExceptionHandler注解的方法,我们可以在这里处理全局异常,将异常信息输出到指定的位置。并对所有的错误信息进行归置。

@RestControllerAdvice是什么

@RestControllerAdvice是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。

@RestControllerAdvice的特点:

  1. 通过@ControllerAdvice注解可以将对于控制器的全局配置放在同一个位置
  2. 注解了@RestControllerAdvice的类的方法可以使用**@ExceptionHandler、@InitBinder、@ModelAttribute**注解到方法上。
  3. @RestControllerAdvice注解将作用在所有注解了**@RequestMapping**的控制器的方法上。
  4. @ExceptionHandler:用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。
  5. @InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model中
  6. @ModelAttribute:本来作用是绑定键值对到Model中,当与@ControllerAdvice配合使用时,可以让全局的@RequestMapping都能获得在此处设置的键值对

参考链接:RestControllerAdvice注解与全局异常处理 - 掘金 (juejin.cn)

修改表现层返回结果的模型类,封装出现异常后对应的信息

  • flag:false
  • Data: null
  • 消息(msg): 要显示信息
@Data
public class R{
    private Boolean flag;
    private Object data;
    private String msg;
    public R(Boolean flag,Object data,String msg){
        this.flag = flag;
        this.data = data;
        this.msg = msg;
    }
}

可以在表现层Controller中进行消息统一处理

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

目的:国际化

页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息

//添加
handleAdd () {
    //发送ajax请求
    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();
    });
}

4.5.5分页功能

笔记小结

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

1.页面使用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>

2.定义分页组件需要使用的数据并将数据绑定到分页组件

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

3.替换查询全部功能为分页功能

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

4.分页查询

//查询--分页
@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
    return new R(true, bookService.getPage(currentPage, pageSize), "查询成功~");
}

注意:使用路径参数传递分页数据或封装对象传递数据

5.加载分页数据

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

6.分页页码值切换

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

7.删除功能维护

  • 基于业务需求维护删除功能
@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);
}

补充:对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询。此方法不能有效解决。项目开发中,当删除一项时,可直接跳转到第一页。

4.5.6条件查询功能

笔记小结

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

1.查询条件数据封装

  • 单独封装
  • 与分页操作混合封装
pagination: { //分页相关模型数据
    currentPage: 1, //当前页码
    pageSize:10, //每页显示的记录数
    total:0, //总记录数
    name: "",
    type: "",
    description: ""
}

2.页面数据模型绑定

<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>

3.组织数据成为get请求发送的数据

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

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

4.Controller接收参数

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

补充:请求参数和形式参数同名,自动注入,或者直接模型类中的属性和请求参数相同,也会直接注入到模型类中。

SpringMVC会按请求参数名(name)和POJO属性名进行自动匹配,自动为该对象填充属性值

这就是,POJO获取请求参数。

5.业务层接口功能开发

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

6.Controller调用业务层分页条件查询接口

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

7.页面回显数据

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;
    });
}

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

4.6完整步骤(重点)

步骤零:数据表

DROP TABLE IF EXISTS `tb_book`;
CREATE TABLE `tb_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 = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

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

SET FOREIGN_KEY_CHECKS = 1;

步骤一:基础模块

1.创建模块时勾选功能

在这里插入图片描述

2.在pom.xml文件中手动添加依赖

<!--MyBatisPlus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.15</version>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

步骤二:功能目录模块

3.在项目模块中创建如下目录

  • java
    • config
    • controller
    • utils
    • dao
    • domain
    • service
      • impl
  • resource
    • static
      • css
      • js
      • pages
        在这里插入图片描述

步骤三:实体类

4.创建domain/Book类

package com.example.domain;

import lombok.Data;

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

步骤四:dao层

5.创建dao/BookDao接口

package com.example.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.domain.Book;
import org.apache.ibatis.annotations.Mapper;

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

步骤五:Service层

6.创建service/IBookService接口

package com.example.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.domain.Book;

/**
 * 需要继承IService<Book>
 **/
public interface IBookService extends IService<Book> {

    //自定义方式添加接口

    Boolean savaBook(Book book);

    Book getById(Integer id);


    IPage<Book> getPage(int currentPage, int pageSize, LambdaQueryWrapper<Book> lqw);

}

7.创建service/impl/BookServiceImpl实现类

package com.example.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * 需要继承 ServiceImpl<BookDao, Book>类
 * 需要好实现  IBookService接口
 **/
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
    @Autowired
    private BookDao bookDao;

    //实现自定义方式的接口
    @Override
    public Boolean savaBook(Book book) {
        return bookDao.insert(book) > 0;
    }

    @Override
    public Book getById(Integer id) {
        return bookDao.selectById(id);
    }

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


}

步骤六:表现层

8.创建controller/utils/R类

package com.example.controller.utils;

import lombok.Data;

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


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

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

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

9.创建controller/utils/ProjectExceptionAdvice类

package com.example.controller.utils;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//作为springMVC的异常处理
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
    //拦截所有的异常处理
    @ExceptionHandler
    public R doException(Exception e) {
        //记录日志
        //通知运维
        //通知开发
        e.printStackTrace();//注意,打印日志,此处实际开发时容易忽略
        return new R(false, null, "服务器故障,请稍后再试");
    }

}

注意:ex.printStackTrace();异常打印

10.创建controller/BookController类

package com.example.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.controller.utils.R;
import com.example.domain.Book;
import com.example.service.IBookService;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/books")
public class BookController {
    //此处是IBookService接口
    @Autowired
    private IBookService bookService;

    //增加
    @PostMapping
    public R save(@RequestBody Book book) throws IOException {
        boolean flag;
        //健壮性判断
        if (book.getType().trim().length() == 0) {
            flag = false;
            return new R(flag, null, flag ? "添加成功~" : "图书类别不能为空!");
        } else if (book.getName().trim().length() == 0) {
            flag = false;
            return new R(flag, null, flag ? "添加成功~" : "图书名称不能为空!");
        } else if (book.getDescription().trim().length() == 0) {
            flag = false;
            return new R(flag, null, flag ? "添加成功~" : "描述不能为空!");
        }
        flag = bookService.save(book);
        return new R(flag, null, flag ? "添加成功~" : "添加失败");
    }

    //删除
    @DeleteMapping("/{id}")
    public R delete(@PathVariable Integer id) {
        boolean flag = bookService.removeById(id);
        return new R(flag, null, flag ? "删除成功~" : "数据同步失败,自动刷新");
    }

    //修改
    @PutMapping
    public R update(@RequestBody Book book) {
        boolean flag = bookService.updateById(book);
        return new R(flag, null, flag ? "修改成功~" : "数据同步失败,自动刷新");
    }

    //查询--单个
    @GetMapping("/{id}")
    public R getById(@PathVariable Integer id) {
        Book book = bookService.getById(id);
        boolean flag;
        if (book != null) {
            flag = true;
        } else {
            flag = false;
        }
        return new R(true, bookService.getById(id), flag ? "查询成功~" : "数据同步失败,自动刷新");
    }

    //查询--所有
    @GetMapping
    public R getAll() {
        return new R(true, bookService.list(), "查询成功~");
    }

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

    //查询--分页
    @GetMapping("/{currentPage}/{pageSize}")
    public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book queryBook) {
        LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
        lqw.like(Strings.isNotEmpty(queryBook.getType()), Book::getType, queryBook.getType());
        lqw.like(Strings.isNotEmpty(queryBook.getName()), Book::getName, queryBook.getName());
        lqw.like(Strings.isNotEmpty(queryBook.getDescription()), Book::getDescription, queryBook.getDescription());
        IPage<Book> page = bookService.getPage(currentPage, pageSize, lqw);
        //如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
        if (currentPage > page.getPages()) {
            currentPage = (int) page.getPages();
            page = bookService.getPage(currentPage, pageSize, lqw);
        }
        return new R(true, page, "查询成功~");
    }

}

步骤七:前端

11.创建resources/static/pages/books.html

<!DOCTYPE html>

<html>

    <head>

        <!-- 页面meta -->

        <meta charset="utf-8">

        <meta content="IE=edge" http-equiv="X-UA-Compatible">

        <title>基于SpringBoot整合SSM案例</title>

        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">

        <!-- 引入样式 -->


        <link href="../plugins/elementui/index.css" rel="stylesheet">

        <link href="../plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet">

        <link href="../css/style.css" rel="stylesheet">

    </head>

    <body class="hold-transition">

        <div id="app">

            <!--标题部分-->
            <div class="content-header">

                <h1>图书管理</h1>

            </div>

            <!--主体部分-->
            <div class="app-container">

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

                    <!--数据展示列表-->
                    <el-table :data="dataList" current-row-key="id" highlight-current-row size="small" stripe>

                        <el-table-column align="center" label="序号" type="index"></el-table-column>

                        <el-table-column align="center" label="图书类别" prop="type"></el-table-column>

                        <el-table-column align="center" label="图书名称" prop="name"></el-table-column>

                        <el-table-column align="center" label="描述" prop="description"></el-table-column>

                        <el-table-column align="center" label="操作">

                            <!--操作-->
                            <template slot-scope="scope">

                                <el-button @click="handleUpdate(scope.row)" size="mini" type="primary">编辑</el-button>

                                <el-button @click="handleDelete(scope.row)" size="mini" type="danger">删除</el-button>

                            </template>

                        </el-table-column>

                    </el-table>

                    <!--分页组件-->
                    <div class="pagination-container">

                        <el-pagination
                                :current-page="pagination.currentPage"

                                :page-size="pagination.pageSize"

                                :total="pagination.total"

                                @current-change="handleCurrentChange"

                                class="pagiantion"

                                layout="total, prev, pager, next, jumper">

                        </el-pagination>

                    </div>

                    <!-- 新增标签弹层 -->

                    <div class="add-form">

                        <el-dialog :visible.sync="dialogFormVisible" title="新增图书">

                            <el-form :model="formData" :rules="rules" label-position="right" label-width="100px"
                                     ref="dataAddForm">

                                <el-row>

                                    <el-col :span="12">

                                        <el-form-item label="图书类别" prop="type">

                                            <el-input v-model="formData.type"/>

                                        </el-form-item>

                                    </el-col>

                                    <el-col :span="12">

                                        <el-form-item label="图书名称" prop="name">

                                            <el-input v-model="formData.name"/>

                                        </el-form-item>

                                    </el-col>

                                </el-row>


                                <el-row>

                                    <el-col :span="24">

                                        <el-form-item label="描述">

                                            <el-input type="textarea" v-model="formData.description"></el-input>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                            </el-form>

                            <div class="dialog-footer" slot="footer">

                                <el-button @click="cancel()">取消</el-button>

                                <el-button @click="handleAdd()" type="primary">确定</el-button>

                            </div>

                        </el-dialog>

                    </div>

                    <!-- 编辑标签弹层 -->

                    <div class="add-form">

                        <el-dialog :visible.sync="dialogFormVisible4Edit" title="编辑检查项">

                            <el-form :model="formData" :rules="rules" label-position="right" label-width="100px"
                                     ref="dataEditForm">

                                <el-row>

                                    <el-col :span="12">

                                        <el-form-item label="图书类别" prop="type">

                                            <el-input v-model="formData.type"/>

                                        </el-form-item>

                                    </el-col>

                                    <el-col :span="12">

                                        <el-form-item label="图书名称" prop="name">

                                            <el-input v-model="formData.name"/>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                                <el-row>

                                    <el-col :span="24">

                                        <el-form-item label="描述">

                                            <el-input type="textarea" v-model="formData.description"></el-input>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                            </el-form>

                            <div class="dialog-footer" slot="footer">

                                <el-button @click="cancel()">取消</el-button>

                                <el-button @click="handleEdit()" type="primary">确定</el-button>

                            </div>

                        </el-dialog>

                    </div>

                </div>

            </div>

        </div>

    </body>

    <!-- 引入组件库 -->

    <script src="../js/vue.js"></script>

    <script src="../plugins/elementui/index.js"></script>

    <script src="../js/jquery.min.js" type="text/javascript"></script>

    <script src="../js/axios-0.18.0.js"></script>

    <script>
        var vue = new Vue({
            el: '#app',
            data: {
                dataList: [],//当前页要展示的列表数据
                dialogFormVisible: false,//添加表单是否可见
                dialogFormVisible4Edit: false,//编辑表单是否可见
                formData: {
                    type: "",
                    name: "",
                    description: ""
                },//表单数据
                rules: {//校验规则
                    type: [{required: true, message: '图书类别为必填项', trigger: 'blur'}],
                    name: [{required: true, message: '图书名称为必填项', trigger: 'blur'}]
                },
                pagination: {//分页相关模型数据
                    currentPage: 1,//当前页码
                    pageSize: 10,//每页显示的记录数
                    total: 0,//总记录数
                    type: "",
                    name: "",
                    description: ""
                }
            },

            //钩子函数,VUE对象初始化完成后自动执行
            created() {
                //调用查询全部数据的操作
                this.getAll();
            },

            methods: {
                //列表
                // getAll() {
                //     //发送异步请求
                //     axios.get("/books").then((res)=>{
                //         // console.log(res.data);
                //         this.dataList = res.data.data;
                //     });
                // },

                //分页查询
                getAll() {
                    //组织参数,拼接url请求地址
                    // console.log(this.pagination.type);
                    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;
                    });
                },

                //切换页码
                handleCurrentChange(currentPage) {
                    //修改页码值为当前选中的页码值
                    this.pagination.currentPage = currentPage;
                    //执行查询
                    this.getAll();
                },

                //弹出添加窗口
                handleCreate() {
                    this.dialogFormVisible = true;
                    //每次打开窗口重置数据
                    this.resetForm();
                },

                //重置表单
                resetForm() {
                    this.formData = {
                        type: "",
                        name: "",
                        description: ""
                    }
                },

                //添加
                handleAdd() {
                    // axios({
                    //     method: "put",
                    //     url: "/books",
                    //     data: this.formData
                    // }).then(response => {
                    //     console.log(response.data)
                    // }).finally(() => {
                    //     //2.重新加载数据
                    //     this.getAll();
                    // })
                    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();
                    });
                },

                //取消
                cancel() {
                    //关闭添加表单
                    this.dialogFormVisible = false;
                    //关闭编辑表单
                    this.dialogFormVisible4Edit = false;
                    this.$message.info("当前操作取消");
                },

                // 删除
                handleDelete(row) {
                    // console.log(row);
                    this.$confirm("此操作永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
                        axios.delete("/books/" + row.id).then((res) => {
                            if (res.data.flag) {
                                this.$message.success(res.data.msg);
                            } else {
                                this.$message.error(res.data.msg);
                            }
                        }).finally(() => {
                            //2.重新加载数据
                            this.getAll();
                        });
                    }).catch(() => {
                        this.$message.info("取消操作");
                    });
                },

                //弹出编辑窗口
                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(res.data.msg);
                        }
                    }).finally(() => {
                        //2.重新加载数据
                        this.getAll();
                    });
                },

                //修改
                handleEdit() {
                    axios.put("/books", this.formData).then((res) => {
                        //判断当前操作是否成功
                        if (res.data.flag) {
                            //1.关闭弹层
                            this.dialogFormVisible4Edit = false;
                            this.$message.success(res.data.msg);
                        } else {
                            this.$message.error(res.data.msg);
                        }
                    }).finally(() => {
                        //2.重新加载数据
                        this.getAll();
                    });
                },

                //条件查询
            }
        })

    </script>

</html>

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值