目录

  • 前言
  • 从今天开始进入微服务阶段
  • 一、 HelloWorld
    • 1.1、什么是SpringBoot
    • 1.3、微服务架构
  • 二、第一个SpringBoot程序
    • 2.1、环境配置
    • 2.2、创建基础项目说明
      • 2.2.1、项目创建方式一
      • 2.2.2、项目创建方式二(推荐)
      • 2.2.3、项目结构分析
    • 2.3、SpringBoot特点
      • 2.3.1、依赖管理
      • 2.3.2、自动配置
  • 三、自动配置原理初探
    • 3.1、pom.xml
    • 3.2、自动配置原理结构图
    • 3.3、@SpringBootApplication
      • 3.3.1、@ComponentScan
      • 3.3.2、@SpringBootConfiguration
      • 3.3.3、@EnableAutoConfiguration
    • 3.4、@EnableAutoConfiguration
      • 3.4.1、@AutoConfigurationPackage
      • 3.4.2、@Import({AutoConfigurationImportSelector.class})
    • 3.5、自动配置失效
    • 3.6、例子
    • 3.7、自动配置结论
    • 3.8、SpringApplication.run分析
  • 四、SpingBoot配置文件
    • 4.1、配置文件
    • 4.2、yaml概述
    • 4.3、yaml注入配置文件
      • 4.3.1、原始得给实体对象赋值
      • 4.3.2、通过yaml文件赋值
    • 4.4、加载指定的配置文件
    • 4.5、配置文件占位符
    • 4.6、回顾properties配置
    • 4.7、Properties和Yaml的对比小结
  • 五、JSP303数据校验及多环境切换
    • 5.1、JSP303
    • 5.2、多环境切换
    • 5.3、yaml实现多环境切换(推荐)
    • 5.4、拓展、运维小技巧
  • 六、※自动配置原理
    • 6.1、分析自动配置原理
    • 6.2、精髓
    • 6.3、了解:@Conditional
  • 七、整合第三方技术
    • 7.1、整合Mybatis
      • 步骤一:创建新模块,
      • 步骤二:设置数据源参数(application.yml)
      • 步骤三:定义数据层接口与映射配置
    • 7.2、整合Mybatis-Plus
      • 步骤一:添加依赖
      • 步骤二:设置数据源参数(application.yml)
      • 步骤三:定义数据层接口与映射配置
    • 7.3、整合Dirud
      • 步骤一:导入对应的starter
      • 步骤二:配置application.yml
      • 步骤三:定义数据层接口与映射配置
  • 八、SSMP整合案例(重点)
    • 8.0 数据库准备
    • 8.1、模块创建
    • 8.2、实体类开发
    • 8.3、数据层开发
      • 8.3.1、基础功能
      • 8.3.2、日志功能
      • 8.3.3、分页功能
      • 8.3.4、条件查询功能
    • 8.4、业务层开发
      • 8.4.1、通用方式
      • 8.4.2、快速开发*
    • 8.5、Controller层开发
      • 8.5.1、基础开发
      • 8.5.2、消息一致性处理
      • 8.5.3、前后端协议联调
      • 8.5.4、业务消息一致性处理
      • 8.5.5、分页功能
      • 8.5.6、条件查询功能
    • 8.6、完整步骤(重要)
      • 步骤零:数据表以及静态资源
      • 步骤一:基础模块
      • 步骤二:功能目录模块
      • 步骤三:实体类
      • 步骤四:dao层
      • 步骤五:Service层
      • 步骤六:表现层
      • 步骤七:前端

前言

本博客 一至六章节笔记 是根据b站狂神SpringBoot视频(p1-p12)整理而来
视频链接如下:https://www.bilibili.com/video/BV1PE411i7CV?p=1&vd_source=f4a032fee75744e378f4ac30c7e8ad39

七至八章节笔记,是根据b站黑马程序员SpringBoot视频(p26-p50)整理而来
视频链接如下:https://www.bilibili.com/video/BV15b4y1a7yG/?spm_id_from=333.337.search-card.all.click&vd_source=f4a032fee75744e378f4ac30c7e8ad39

从今天开始进入微服务阶段

  • javase:OOP
  • MySQL:持久化
  • html+css+js+jquery+框架:视图,框架不熟练,css不好
  • javaweb:独立开发MVC三层架构的网站:原始
  • ssm:框架:简化了我们的开发流程,配置也开始较为复杂;
  • 在此之前项目打包都是war包,程序在Tomcat中运行
  • spring再简化:springBoot-jar包,内嵌Tomcat;微服务架构!
  • 服务越来越多:springCloud

SpringBoot学习内容:

一、 HelloWorld

1.1、什么是SpringBoot

SpringBoot就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

1.3、微服务架构

​ 微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须建成一系列小服务组合,可以通过http方式进行通信。

​ 所谓微服务加购,就是打破之前all in one的架构方式,把每个功能元素独立出来,把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行赋值,而没有对整个应用进行复制,这样做的好处是:

  • 节省了调用资源
  • 每个功能元素的服务都是一个可替换的,可独立升级的软件代码

程序核心:
高内聚(在划分模块时,要把功能关系紧密的放到一个模块中)
低耦合(模块之间的联系越少越好,接口越简单越好)

微服务论文介绍:https://martinfowler.com/articles/microservices.html#CharacteristicsOfAMicroserviceArchitecture

  • 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
  • 大型分布式网络服务的调用,这部分springcloud来完成,实现分布式
  • 在分布式中间,进行流式数据计算,批处理,我们有spring cloud data flow
  • spring为我们想清楚了整个开始构建应用到大型分布式应用全流程方案

二、第一个SpringBoot程序

2.1、环境配置

IDEA+Maven+SpringBoot2+java1.8

2.2、创建基础项目说明

Spring官方提供了非常方便的工具让我们快速构建应用,IDEA也集成了这个网站

Spring Initializr::https://start.spring.io/

2.2.1、项目创建方式一

使用Spring Initializr 的 Web页面创建项目

①打开 https://start.spring.io/

②填写项目信息

③点击”Generate Project“按钮生成项目;下载此项目

④解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。

⑤如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

2.2.2、项目创建方式二(推荐)

使用 IDEA 直接创建项目

①创建一个新项目

②选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现

③填写项目信息

④选择初始化的组件(初学勾选 Web 即可)

⑤填写项目路径

⑥等待项目构建成功

2.2.3、项目结构分析

  • 程序的主启动类(程序的主入口)
  • 一个 application.properties 配置文件(SpringBoot的核心配置文件)
  • 一个 测试类
  • 一个 pom.xml

编写Controller
(注意:建包controller、dao、pojo 建立在HelloWorldApplication同级目录下)

启动SpringBoot


更改端口号:在application资源文件中配置

 server.port = xxxx

2.3、SpringBoot特点

2.3.1、依赖管理

  • 父项目做依赖管理:

  • 开发导入starter场景启动器:
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope>
</dependency>
  • 无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
  • 可以修改默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置<properties><mysql.version>5.1.43</mysql.version></properties>

2.3.2、自动配置

  • 自动配好Tomcat

    • 引入Tomcat依赖。
    • 配置Tomcat
 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope></dependency>
  • 自动配好SpringMVC

    • 引入SpringMVC全套组件

    • 自动配好SpringMVC常用组件(功能)

    • 自动配好Web常见功能,如:字符编码问题

    • SpringBoot帮我们配置好了所有web开发的常见场景

    • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

    • 无需以前的包扫描配置

    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.gq”)

三、自动配置原理初探

3.1、pom.xml

  • Spring-boot-dependencies:核心依赖在父工程中
  • 我们在写或者引入springboot依赖的时候,不需要指定版本,因为有这些版本仓库

启动器

<!--启动器-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器:说白了就是Springboot的启动场景
  • 比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖
  • springboot会将所有的功能场景,都变成一个个的启动器
  • 我们要使用什么功能,就只需要找到对应的启动器starte

3.2、自动配置原理结构图

Ctrl+Enter 主程序的SpringBootApplication查看源码

结论:SpringBoot所有的自动配置都是在主程序启动的时候扫描并加载:spring.factories.

所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只有导入了对应的starter(启动器),有了启动器,对应的自动配置类才会生效,然后配置成功

3.3、@SpringBootApplication

作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用
继续Ctrl+Enter:@SpringBootApplication 得到非常重要的三个注解:
(@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)


下面,分别分析这三个注解的作用。

3.3.1、@ComponentScan

首先分析:@ComponentScan

@ComponentScan这个注解在Spring中很重要 ,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

3.3.2、@SpringBootConfiguration

其次分析:@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

我们继续进去这个注解查看:

// 点进去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}@Component
public @interface Configuration {}

这里的 @Configuration说明这本质上是一个Spring配置类 ,配置类就是对应Spring的xml 配置文件;

里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

3.3.3、@EnableAutoConfiguration

再分析:@EnableAutoConfiguration
(这个注解最重要,因此单独弄到3.4节来分析)

3.4、@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

点击注解@EnableAutoConfiguration,进一步查看结构:

3.4.1、@AutoConfigurationPackage

首先分析: @AutoConfigurationPackage :自动配置包
点击注解,得到结构:

@Import({Registrar.class})
//利用Registrar导入一系列组件
//将指定的一个包下的所有组件导入进来 是主程序所在的包下
public @interface AutoConfigurationPackage {}

@import :Spring底层注解@import , 给容器中导入一个组件

Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

3.4.2、@Import({AutoConfigurationImportSelector.class})

其次分析:@Import({AutoConfigurationImportSelector.class}):自动配置导入选择器

​ 这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描spring.factories文件
此文件包含了SpringBoot所有的自动配置类,路径为:

AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:

1. 这个类中有一个这样的方法

// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {//这里的getSpringFactoriesLoaderFactoryClass()方法//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfigurationList<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");return configurations;
}

2. 这个方法又调用了SpringFactoriesLoader类的静态方法!我们进入SpringFactoriesLoaderloadFactoryNames() 方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();//这里它又调用了 loadSpringFactories 方法return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,Collections.emptyList());
}

3. 继续点击查看 loadSpringFactories 方法

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {//去获取一个资源 "META-INF/spring.factories"Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();//将读取到的资源遍历,封装成为一个Propertieswhile(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (Entry)var6.next();String factoryClassName = ((String)entry.getKey()).trim();String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryName = var9[var11];result.add(factoryClassName, factoryName.trim());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}
}
  1. 发现一个多次出现的文件:spring.factories,全局搜索它

文件里面写死了spring-boot一启动就要给容器中加载的所有配置类

我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,如下图所示:


​ 这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了SpringBoot的启动类上。在SpringApplication.run(…)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

3.5、自动配置失效

每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:

@ConditionalOnBean:当容器里有指定的bean的条件下。@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。@ConditionalOnClass:当类路径下有指定类的条件下。@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。

3.6、例子

ServletWebServerFactoryAutoConfiguration配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。
ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解:开启配置属性,而它后面的参数是一个ServerProperties类,这就是约定大于配置的最终落地点。

3.7、自动配置结论

springboot所有的自动配置都是在启动的时候扫描并加载,扫描了spring.properties配置文件,所有的自动配置类都在这里面,但是不定生效,因为要判断条件是否成立,只要导入了对应的start,就有对应的启动器,有了启动器我们自动装配就会生效,然后就配置成功

步骤:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 以前需要我们配置的文件,springboot帮我们配置了!
  4. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  5. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration)@Bean, 就是给容器中导入这个场景需要的所有组件 ,
    并配置好这些组件 @Configuration
  6. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

3.8、SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行

SpringApplication
这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {// ......this.webApplicationType = WebApplicationType.deduceFromClasspath();this.setInitializers(this.getSpringFactoriesInstances();this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = this.deduceMainApplicationClass();
}

run()方法

1、判断当前项目是普通项目还是web项目
2、推断并设置main方法的定义类,找到运行的主类
3、run方法里面有一些监听器,这些监听器是全局存在的,它的作用是获取上下文处理一些bean。

四、SpingBoot配置文件

配置文件的作用:修改SpringBoot自动配置的默认值。SpringBoot在底层配了默认值,但是可能实际中会修改

4.1、配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties(不推荐)

    • 语法结构 :key=value
server.port=8081

application.yaml(推荐)

  • 语法结构 :key:空格value
server:port: 8081

4.2、yaml概述

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

传统xml配置:

<server><port>8081<port>
</server>

yaml配置:

server:prot: 8080

yaml基础语法

说明:语法要求严格!

1、空格不能省略

2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

3、属性和值的大小写都是十分敏感的。

字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

    • 比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
  • ‘ ’单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
    • 比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen

**对象、Map(键值对)**

#对象、Map格式
key: value1:value2:

在下一行来写对象的属性和值的关系,注意缩进;比如:

student:name: qinjiangage: 3

行内写法

student: {name: qinjiang,age: 3}

**数组( List、set )**

用 - 值表示数组中的一个元素,比如:

pets:- cat- dog- pig

行内写法

pets: [cat,dog,pig]

4.3、yaml注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

4.3.1、原始得给实体对象赋值

@Component  //注册bean到容器中,使得这个类可以被扫描到
public class Dog {private String name;private Integer age;//有参无参构造、get、set方法、toString()方法
}
@Component //注册bean
public class Dog {@Value("阿黄")private String name;@Value("18")private Integer age;
}

4.3.2、通过yaml文件赋值

1、我们在编写一个复杂一点的实体类:Person 类


@Component //注册bean到容器中
public class Person {private String name;private Integer age;private Boolean happy;private Date birth;private Map<String,Object> maps;private List<Object> lists;private Dog dog;//有参无参构造、get、set方法、toString()方法
}

2、使用application.yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

person:name: qinjiangage: 3happy: falsebirth: 2000/01/01maps: {k1: v1,k2: v2}lists:- code- girl- musicdog:name: 旺财age: 1

3、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {private String name;private Integer age;private Boolean happy;private Date birth;private Map<String,Object> maps;private List<Object> lists;private Dog dog;
}

4、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional>
</dependency>

5、确认以上配置都OK之后,我们去测试类中测试一下:

@SpringBootTest
class DemoApplicationTests {@AutowiredPerson person; //将person自动注入进来@Testpublic void contextLoads() {System.out.println(person); //打印person信息}}

4.4、加载指定的配置文件

@PropertySource :加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

1、我们去在resources目录下新建一个person.properties文件
注意在使用之前要去setting把字符集调整为UTF-8否则会乱码(在后面的回顾properties配置中有具体写到)

name=guoshuai

2、然后在我们的代码中指定加载person.properties文件

@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {@Value("${name}")//有点像jsp了哦,EL表达式private String name;......
}

4.5、配置文件占位符

配置文件还可以编写占位符生成随机数

person:name: qinjiang${random.uuid} # 随机uuidage: ${random.int}  # 随机inthappy: falsebirth: 2000/01/01maps: {k1: v1,k2: v2}lists:- code- girl- musicdog:name: ${person.hello:other}_旺财age: 1

4.6、回顾properties配置

我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yaml还有我们之前常用的properties , 我们没有讲,我们来唠唠!
【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings 中配置:

4.7、Properties和Yaml的对比小结

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

2、松散绑定:这个什么意思呢? 比如我的yaml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

4、复杂类型封装,yaml中可以封装对象 , 使用value就不支持。

结论

配置yaml和配置properties都可以获取到值 , 但是强烈推荐 yaml

如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

五、JSP303数据校验及多环境切换

5.1、JSP303

JSR303数据校验是用来校验输入内容的

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {@Email(message="邮箱格式错误") //name必须是邮箱格式private String name;
}

如果没有@Email注解,需要在pom.xml文件中添加依赖:

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

使用数据校验,可以保证数据的正确性

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true
@AssertFalse    验证 Boolean 对象是否为 false  长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前
@Future     验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern    验证 String 对象是否符合正则表达式的规则.......等等
除此以外,我们还可以自定义一些数据校验规则

源码:

5.2、多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;

我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

5.3、yaml实现多环境切换(推荐)

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

通过- - - 来分割模块

server:port: 8081
#选择要激活那个环境块
spring:profiles:active: test---
server:port: 8083
spring:profiles: dev #配置环境的名称---server:port: 8084
spring:profiles: test#配置环境的名称

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:Resorces路径下的config文件夹配置文件
优先级4:Resorces路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

#配置项目的访问路径
server.servlet.context-path=/gq

5.4、拓展、运维小技巧

指定位置加载配置文件

我们还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

六、※自动配置原理

配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,我们无法全部记住

6.1、分析自动配置原理

我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

@Configuration //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;//启动指定类的ConfigurationProperties功能;//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;//如果不存在,判断也是成立的//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "spring.http.encoding",value = {"enabled"},matchIfMissing = true
)public class HttpEncodingAutoConfiguration {//他已经和SpringBoot的配置文件映射了private final Encoding properties;//只有一个有参构造器的情况下,参数的值就会从容器中拿public HttpEncodingAutoConfiguration(HttpProperties properties) {this.properties = properties.getEncoding();}//给容器中添加一个组件,这个组件的某些值需要从properties中获取@Bean@ConditionalOnMissingBean //判断容器没有这个组件?public CharacterEncodingFilter characterEncodingFilter() {CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();filter.setEncoding(this.properties.getCharset().name());filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));return filter;}//。。。。。。。
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;这样就可以形成我们的配置文件可以动态的修改springboot的内容。
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

**通俗理解:**把我们原先需要在bean中手打的属性(property)封装成了一个类,然后通过yaml文件进行自动注入,而我们也可以在application.yaml文件中对这些property进行赋值。

//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {// .....
}

6.2、精髓

1.SpringBoot启动时会加载大量的自动配置类
2.我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
3.我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在其中,我们就不需要再去手动配置了,如果不存在我们再手动配置)
4.给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性即可;

XXXXAutoConfiguration:自动配置类:给容器添加组件,这些组件要赋值就需要绑定一个XXXXProperties类
XXXXProperties:里面封装配置文件中相关属性;

怎么去修改这些属性呢:说白了就是SpringBoot配置,---->.yaml、.properties这些文件

6.3、了解:@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,*自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug: true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug: true

Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)

七、整合第三方技术

笔记小结

整合第三方技术通用方式

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

7.1、整合Mybatis

笔记小结

步骤:

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

步骤一:创建新模块,

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

步骤二:设置数据源参数(application.yml)

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTCusername: rootpassword: 123456

注意:

SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区 xml jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC

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

创建实体类 com.gq.pojo.Books

package com.gq.pojo;import lombok.Data;@Data
public class Books {private int bookId;private String bookName;private int bookCounts;private String detail;
}

书写映射com.gq.dao.BookDao;

package com.gq.com.gq.dao;import com.gq.pojo.Books;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;@Mapper
public interface BookDao {@Select("select * from ssmbuild.books where bookId = #{bookId}")public Books getBookById (@Param("bookId") int id);
}

@Mapper注解

  • 1:为了把mapper这个DAO交給Spring管理
  • 2:为了不再写mapper映射文件
  • 3:为了给mapper接口 自动根据一个添加@Mapper注解的接口生成一个实现类
  • 换种方式也可以在启动类上面加MapperScan(“mapper层所在包的全名”),让springboot认识你的mapper层
补充:常见MyBatis问题MySQL 8.X驱动强制要求设置时区修改url,添加serverTimezone设定修改MySQL数据库配置(略)驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver

7.2、整合Mybatis-Plus

笔记小结:

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

MyBatis-Plus与MyBatis区别

  • 导入依赖不同
  • 数据层实现简化

步骤一:添加依赖

上面整合Mybatis中步骤一中找不到MyBatis-Plus对应的依赖,因此需要手动添加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 可前往网站获取最新依赖:https://mvnrepository.com/artifact/org.hamcrest/hamcrest/2.2

步骤二:设置数据源参数(application.yml)

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTCusername: rootpassword: 123456#关闭驼峰映射
mybatis-plus:configuration:map-underscore-to-camel-case: false

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

创建实体类 com.gq.pojo.Books

package com.gq.pojo;import lombok.Data;@Data
public class Books {@TableId("bookId") //告诉mybatis-plus这是主键, selectById的Id是它private int bookId;private String bookName;private int bookCounts;private String detail;
}

书写映射com.gq.dao.BookDao;

package com.gq.com.gq.dao;import com.gq.pojo.Books;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;@Mapper
public interface BookDao extends BaseMapper<Books>{}

7.3、整合Dirud

笔记小结

  1. 整合Druid需要手动导入Druid对应的starter
  2. 根据Druid提供的配置方式进行配置

步骤一:导入对应的starter

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

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

可前往网站获取最新坐标(依赖):https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter

步骤二:配置application.yml

  • 方式一:指定数据源类型
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTCusername: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSource
  • 方式二:变更Druid的配置方式(标准版)✳
spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTCusername: rootpassword: root

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

选择mybatis或者mybatis-plus中的步骤三都可以

八、SSMP整合案例(重点)

Spring+SpringMVC+Mybatis-plus

笔记小结

  • 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案例制作流程解析

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

8.0 数据库准备

创建表格:

create database ssmp
use ssmp
DROP TABLE IF EXISTS `tbl_book`
CREATE TABLE `tbl_book` (`id` int(11) NOT NULL AUTO_INCREMENT,`type` varchar(20) DEFAULT NULL,`name` varchar(50) DEFAULT NULL,`description` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

8.1、模块创建

第一步:创建SpringBoot项目

第二步:添加依赖

(Mybatis-plus Dirud)

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

第三步:修改配置文件为yml格式

8.2、实体类开发

使用Lombok

package com.gq.pojo;import lombok.Data;@Data
public class Book {private int id;private String type;private String name;private String description;
}

8.3、数据层开发

8.3.1、基础功能

笔记小结

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

技术实现方案

  • MyBatisPlus
  • Druid

步骤一:导入mybatis-plus 以及Dirud依赖

(前面已经导入)

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

application.yml:

#Jdbc,druid
spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssmp?serverTimezone=UTCusername: rootpassword: 123456#mybatis-plus
#注意:在mybatis-plus中需要定义table-prefix,因为MyBatisPlus的映射缘故。例如mysql表名为tbl_user,会被MP变为user
mybatis-plus:global-config:db-config:table-prefix: tbl_id-type: auto

步骤三:dao层书写BookDao

package com.gq.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.pojo.Book;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BookDao extends BaseMapper<Book> {}

步骤四:测试

package com.gq;import com.gq.dao.BookDao;
import com.gq.pojo.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringBootSsmpApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid contextLoads() {Book book = bookDao.selectById(1);System.out.println(book.toString());}}

跑通了 环境没有问题!

8.3.2、日志功能

笔记小结

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

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

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

8.3.3、分页功能

笔记小结

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

步骤一:新建目录com.gq.config 在下面新建配置类MPConfig 用以定义拦截器

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

package com.gq.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MPConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//1、定义拦截器MybatisPlusInterceptor myInterceptor = new MybatisPlusInterceptor();//2、添加具体的拦截器myInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return myInterceptor;}
}

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

@Configuration注解作用

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

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

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

@Bean注解作用

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

步骤二:分页操作需要设定分页对象IPage

@SpringBootTest
class SpringBootSsmpApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid testGetPage() {IPage<Book> iPage = new Page<>(1, 5);//selectPage(page,queryWrapper),第二个参数用作条件查询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());}}
  • 数据 getRecords
  • 当前页码值 getCurrent
  • 每页数据总量 getTotal
  • 最大页码 getPages
  • 数据总量 getTota

8.3.4、条件查询功能

笔记小结

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

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

@SpringBootTest
class SpringBootSsmpApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid testGetByCondition(){QueryWrapper<Book> qw = new QueryWrapper<Book>();//相当于sql中:select * from xxx where name like %Spring%;//这里有风险,因为把 name 写死了  所以考虑使用LambdaQueryWrapperqw.like("name","Spring");bookDao.selectList(qw);}}
@SpringBootTest
class SpringBootSsmpApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid contextLoads() {LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper();//相当于sql中:select * from xxx where name like %Spring%;lqw.like(Book::getName,"Spring");List<Book> books = bookDao.selectList(lqw);}
补充:
Java中的双冒号写法::
1.表达式:person -> person.getName();可以替换成:Person::getName

但是 但是。。 可能有风险 因为like 中的 “Sping”是从前端传递来的,有可能是null,因此需要改进代码

@SpringBootTest
class SpringBootSsmpApplicationTests {@Autowiredprivate BookDao bookDao;@Testvoid testGetByCondition(){String name = "Spring";IPage page = new Page(1,10);LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();//if(Strings.isNotEmpty(name)){lqw.like(Book::getName,"Spring")}lqw.like(Strings.isNotEmpty(name),Book::getName,"Spring");bookDao.selectPage(page,lqw);}

8.4、业务层开发

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

  • 数据层:这样命名 selectByUserNameAndPassword(String username,String password);
  • 业务层:这样命名login(String username,String password)
    (当然,也可以命名一样,只不过那样更好)

8.4.1、通用方式

笔记小结

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

定义接口:com.gq.service 下 BookService

package com.gq.service;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gq.pojo.Book;import java.util.List;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);
}

实现接口:com.gq.service 下 BookServiceImpl

package com.gq.service;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gq.dao.BookDao;
import com.gq.pojo.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;@Overridepublic boolean save(Book book) {return bookDao.insert(book) > 0;}@Overridepublic boolean delete(Integer id) {return bookDao.deleteById(id) > 0;}@Overridepublic boolean update(Book book) {return bookDao.updateById(book) > 0;}@Overridepublic Book getById(Integer id) {return bookDao.selectById(id);}@Overridepublic List<Book> getALl() {return bookDao.selectList(null);}@Overridepublic IPage<Book> getByPage(int currentPage, int pageSize) {return null;}
}

8.4.2、快速开发*

上面通用方式中 若是实体类Book,想要换成User实体类,那么几乎所有代码都要改,所以有了一个快速开发(但是如果涉及多表查询,尽量不要使用)

笔记小结

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

步骤一:书写接口IBookService

package com.gq.service;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.pojo.Book;public interface IBookService extends IService<Book> {//其本身有很多增删改查方法//自定义方法  追加的操作与原始操作通过名称区分,功能类似Boolean delete(Integer id);IPage<Book> getPage(int currentPage,int pageSize);}

步骤二:书写接口实现类BookServiceImpl2

package com.gq.service;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.gq.dao.BookDao;
import com.gq.pojo.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao, Book> implements IBookService {@Autowiredprivate BookDao bookDao;//IBookService自定义了方法,那么也需要实现这些方法@Overridepublic Boolean delete(Integer id) {return bookDao.deleteById(id) > 0;}//查询 分页@Overridepublic IPage<Book> getPage(int currentPage, int pageSize) {IPage<Book> page = new Page<Book>(currentPage,pageSize);return bookDao.selectPage(page,null);}
}

测试分页查询功能是否成功:

8.5、Controller层开发

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

8.5.1、基础开发

笔记小结:

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

功能测试:(新建com.gq.controller目录,创建BookController 类)

package com.gq.controller;import com.gq.pojo.Book;
import com.gq.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("/books")
public class BookController {//此处是IBookService接口@Autowiredprivate IBookService bookService;@GetMappingpublic List<Book> getAll(){return bookService.list();}
}

Controller层开发:com.gq.controllermu目录下:BookController.java

package com.gq.controller;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gq.pojo.Book;
import com.gq.service.IBookService;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/books")
public class BookController {@Autowired@Qualifier(value = "bookServiceImpl2")private IBookService bookService;//查询所有@GetMappingpublic List<Book> getAll() {return bookService.list();}//增加@PostMappingpublic Boolean save(@RequestBody Book book) {return bookService.save(book);}//删除@DeleteMapping("/{id}")public Boolean delete(@PathVariable Integer id) {return bookService.delete(id);}//修改@PutMappingpublic Boolean update(@RequestBody Book book) {return bookService.updateById(book);}//查询  单个@GetMappingpublic 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);}
}

8.5.2、消息一致性处理

笔记小结

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

步骤一:设计表现层返回结果的模型类

用于后端与前端进行数据格式统一,也称为前后端数据协议

创建controller/utils下的R

package com.gq.controller.util;import lombok.Data;@Data
public class R {private Boolean flag;private Object data;public R(Boolean flag) {this.flag = flag;}public R() {};public R(Boolean flag, Object data) {this.flag = flag;this.data = data;}
}

步骤二:修改:com.gq.controllermu目录下:BookController.java

@RestController
@RequestMapping("/books")
public class BookController {@Autowiredprivate IBookService bookService;@PostMappingpublic R save(@RequestBody Book book){Boolean flag = bookService.insert(book);return new R(flag);}@PutMappingpublic 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);}@GetMappingpublic 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);}
}

测试:

8.5.3、前后端协议联调

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

将如下四个静态资源导入到SpringBoot项目Resources下static目录内
(资源在公众号:黑马程序员 输入 springboot 基础篇内 )
注意:项目最好clean一下!

操作前端页面:books.html

1、列表页

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

2、弹出添加窗口以及清理数据
补充:弹出添加Div时清除表单数据

//弹出添加窗口
handleCreate() {//弹出添加窗口this.dialogFormVisible = true;//清理数据this.resetForm();
}
//重置表单
resetForm() {this.formData = {};
}

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

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

4.取消添加

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

5.删除

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值到后,删除操作结束后动态刷新页面加载数据,根据操作结果不同,显示对应的提示信息,删除操作前弹出提示框避免误操作(防手抖)

6、弹出修改窗口

//弹出编辑窗口
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值到后台查询数据,利用前端数据双向绑定将查询到的数据进行回显

7.修改

//修改
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调用后台对应操作

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

8.取消添加和修改

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

8.5.4、业务消息一致性处理

笔记小结

  • 1、使用注解**@RestControllerAdvice**定义SpringMVC异常处理 器用来处理异常的
  • 2、异常处理器必须被扫描加载,否则无法生效
  • 3、表现层返回结果的模型类中添加消息属性用来传递消息到页面
  • 由于之前对数据一致性的处理,业务操作成功或失败返回的数据格式为:
  • 但是后台产生BUG,会输出不一致的数据格式

  • 因此,对于前端就非常痛苦,所以需要考虑数据一致性(包括异常情况)
  • 构造异常处理器,这是SpringMVC的技术,因此放在controller层,放到com.gq.controller.util目录下,创建ProjectExceptionAdvice.java

R中重新添加一个构造器:

package com.gq.controller.util;import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class ProjectExceptionAdvice {//拦截所有的异常信息@ExceptionHandlerpublic R doException(Exception exception){//记录日志//发送信息给运维//发送邮件给开发人员exception.printStackTrace();return new R(false,"系统错误,稍后再试");}
}

参考链接:RestControllerAdvice注解与全局异常处理参考

可以在表现层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();});
}

8.5.5、分页功能

千万不要忘记添加拦截器,具体见8.3.3

笔记小结

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

步骤一:页面使用el分页组件添加分页功能

<!--分页组件-->
<div class="pagination-container"><el-paginationclass="pagiantion"@current-change="handleCurrentChange":current-page="pagination.currentPage":page-size="pagination.pageSize"layout="total, prev, pager, next, jumper":total="pagination.total"></el-pagination>
</div>

步骤二:定义分页组件需要使用的数据并将数据绑定到分页组件

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

步骤三:替换查询全部功能为分页功能

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

步骤四:加载分页数据

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

步骤五:分页页码值切换

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

步骤六:删除功能维护

(假如有三页数据,当前呈现的正是第三页,只有一条数据,把这一条数据删掉,那么这一页就会空白,但希望页面自动切换到前面有数据的页面)

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

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

8.5.6、条件查询功能

(条件查询一般和分页查询做在一起)

笔记小结

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

步骤一:查询条件数据封装

与分页操作混合封装

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>

步骤三:组织数据成为get请求发送的数据

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

步骤四:Controller层修改 (用以接受前端条件查询的参数)

    @GetMapping("/{currentPage}/{pageSize}")public R getAll(@PathVariable Integer currentPage, @PathVariable Integer pageSize,Book book) {IPage<Book> page = bookService.getPage(currentPage, pageSize, book);if (currentPage > page.getPages()) {page = bookService.getPage((int) page.getPages(), pageSize, book);}return new R(true, page);}

备注:模型类中的属性和请求参数相同,会直接注入到模型类中,所以这里直接用Book book接收前端来的 type、name、description参数

步骤五:Service层修改

IBookService修改

package com.gq.service;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.pojo.Book;public interface IBookService extends IService<Book> {IPage<Book> getPage(int currentPage,int pageSize,Book book);}

BookServiceImpl2修改

    //查询 分页@Overridepublic IPage<Book> getPage(int currentPage, int pageSize, Book book) {LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();lqw.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());lqw.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());lqw.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());IPage<Book> page = new Page<Book>(currentPage, pageSize);return bookDao.selectPage(page, lqw);}

8.6、完整步骤(重要)

代码结构:

步骤零:数据表以及静态资源

数据表:

create database ssmp
use ssmp
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;

静态资源以及源码:

gitee:https://gitee.com/gaoqiangmath/ssmp

步骤一:基础模块

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、代码目录:

步骤三:实体类

4.创建com.gq.pojo.Book.java

package com.gq.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {private int id;private String type;private String name;private String description;
}

步骤四:dao层

5.创建com.gq.dao.BookMapper接口

package com.gq.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.pojo.Book;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BookMapper extends BaseMapper<Book> {}

步骤五:Service层

6.创建com.gq.service.IBookService接口

package com.gq.service;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.pojo.Book;public interface IBookService extends IService<Book> {//自定义按id修改Bookboolean modifyById(Book book);//自定义按条件查询IPage<Book> getPageAndQueryByCondition(int currentPage,int pageSize, Book book);}

7.创建com.gq.service.BookServiceImpl实现类

package com.gq.service;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.gq.dao.BookMapper;
import com.gq.pojo.Book;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements IBookService {@Autowiredprivate BookMapper bookMapper;@Overridepublic boolean modifyById(Book book) {return bookMapper.updateById(book) > 0;}//查询 分页@Overridepublic IPage<Book> getPageAndQueryByCondition(int currentPage,int pageSize, Book book) {LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();lqw.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());lqw.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());lqw.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());IPage<Book> page = new Page<>(currentPage, pageSize);return bookMapper.selectPage(page, lqw);}
}

步骤六:表现层

8.创建com.gq.controller.util.R.java

package com.gq.controller.util;import lombok.Data;@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;}
}

9.创建com.gq.controller.util.ProjectExceptionAdvice.java

package com.gq.controller.util;import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class ProjectExceptionAdvice {//拦截所有的异常信息@ExceptionHandlerpublic R doException(Exception exception) {//记录日志//发送信息给运维//发送邮件给开发人员exception.printStackTrace();return new R(false, null, "系统错误,请稍后再试");}
}

注意:exception.printStackTrace();异常打印 方便开发人员维护

10.创建com.gq.controller.BookController.java

package com.gq.controller;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gq.controller.util.R;
import com.gq.pojo.Book;
import com.gq.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/books")
public class BookController {@Autowiredprivate IBookService bookService;//显示全部数据@GetMappingpublic R getAll() {List<Book> list = bookService.list();return new R(true, list, null);}//新增@PostMappingpublic R addItem(@RequestBody Book book) {boolean flag = bookService.save(book);return new R(flag, null, flag ? "新增成功" : "新增失败");}//根据ID查找数据@GetMapping("/{id}")public R getById(@PathVariable int id) {Book book = bookService.getById(id);return new R(true, book, null);}//更新数据@PutMappingpublic R update(@RequestBody Book book) {boolean flag = bookService.modifyById(book);return new R(flag, null, flag ? "修改成功" : "修改失败");}//根据id删除@DeleteMapping("/{id}")public R deleteById(@PathVariable int id) {boolean flag = bookService.removeById(id);return new R(flag, null, flag ? "删除成功" : "删除失败");}//分页@GetMapping("/{currentPage}/{pageSize}")public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book book) {IPage<Book> page = bookService.getPageAndQueryByCondition(currentPage, pageSize, book);if (currentPage > page.getPages()) {//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值page = bookService.getPageAndQueryByCondition((int) page.getPages(), pageSize, book);}return new R(true, page, null);}}

步骤七:前端

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

SpringBoot2基础篇相关推荐

  1. SpringBoot2:基础篇(黑马程序员:P1~P53)

    基础篇 1.SpringBoot入门案例 1.1 IDEA联网版 官方URL https://start.spring.io 注:如果创建不了,更改URL https://start.aliyun.c ...

  2. spring Boot 2 基础篇 。内含 整合一个spring boot 的 小案例

    目录 springBoot2基础篇 前言与开发环境 一.快速创建Boot项目 1.使用spring提供的快速构建 2.基于maven的手动构建 3.在Idea中隐藏指定文件/文件夹 二.SpringB ...

  3. SpringBoot【基础篇】

    SpringBoot2[基础篇] 官方网址:https://spring.io/projects/spring-boot#learn 文章目录 SpringBoot2[基础篇] 第一章:springb ...

  4. SpringBoot2零基础到项目实战-基础篇

    springboot2零基础到项目实战-基础篇 课程内容说明 课程单元 学习目标 基础篇 能够创建SpringBoot工程 基于SpringBoot实现ssm/ssmp整合 应用篇 能够掌握Sprin ...

  5. M5(项目)-01-尚硅谷谷粒商城项目分布式基础篇开发文档

    M5(项目)-01-尚硅谷谷粒商城项目分布式基础篇开发文档 分布式基础篇 一.环境搭建 各种开发软件的安装 虚拟机: docker,mysql,redis 主机: Maven, idea(后端),Vs ...

  6. 谷粒商城基础篇(保姆级总结)

    谷粒商城基础篇 文章目录 谷粒商城基础篇 项目相关基础 知识介绍 微服务架构图和项目描述 **微服务划分图** Vrgrant systemctl命令 配置环境 Docker自启动命令 下载mysql ...

  7. SpringBoot SpringBoot 基础篇(第一篇) 第2章 SpringBoot 全局配置 2.2 yaml 文件

    SpringBoot [千锋教育java教程SpringBoot2全套,springboot快速入门到项目实战视频教程] SpringBoot 基础篇(第一篇) 第2章 SpringBoot 全局配置 ...

  8. Python Qt GUI设计:信号与槽的使用方法(基础篇—7)

    目录 1.信号与槽的概念 2.信号与槽的基础函数 2.1.创建信号函数 2.2.连接信号函数 2.3.断开信号函数 2.4.发射信号函数 3.信号和槽的使用方法 3.1.内置信号与槽的使用 3.2.自 ...

  9. Python Qt GUI设计:窗口布局管理方法【强化】(基础篇—6)

    目录 1. 水平布局类(QHBoxLayout) 2.垂直布局类(QVBoxLayout) 3.网格布局类(QGridLayout) 3.1.单一的网络布局 3.2.跨越行.列的网络布局 4.表单布局 ...

最新文章

  1. zabbix监控系统客户端安装
  2. python 集合
  3. leetcode- Single Number
  4. 设定自动获得DNS服务器地址
  5. DeleteDC() 与 ReleaseDC() 的区别 [转]
  6. YOLOv3目标检测有了TensorFlow实现,可用自己的数据来训练
  7. 能提升你的东西,都不在舒适区
  8. Lua 脚本汇编-入门到精通
  9. 《python源代码分析》笔记 pythonVM一般表达式
  10. linux安全模块学习之LSM的介绍实现
  11. 埋葬了我曾经的执着与思恋题记不过是所谓的世界末日
  12. The Love Dare爱的挑战
  13. 计算机常用英语单词(带音标)
  14. javascript趣味钢琴小游戏(附源码)js+css+html
  15. 百度地图3.1教程—检索功能演示
  16. Hex文件头部修改软件
  17. git clone时遇到问题:remote: Incorrect username or password ( access token )
  18. VS2013+Windows+CPU下搭建caffe框架并利用mnist数据集实验
  19. APK反编译工具及方法
  20. AutoCAD二次开发(.Net)之动态块

热门文章

  1. Android | 教你如何开发一键银行卡绑定功能
  2. dell加装固态硬盘_[图解]戴尔灵越15R 5537怎么更换加装固态硬盘?
  3. 加油站都需要什么手续_开一个加油站需要什么手续,需要多少启动资金?
  4. 考研807程序设计C语言教程,中央财经大学
  5. html文本下一页,Javascript html2canvas + jsPDF 导出PDF,解决一半文字在上一页一半文字在下一页的问题...
  6. 如何给PDF删除空白页面?来看看这种方法
  7. VINS-Mono翻译
  8. bootstrap class path not set in conjunction with -source 1.6
  9. 原创超简单代码(1.21.50)
  10. Method isEmpty in android.text.TextUtils not mocked