本博客 一至六章节笔记 是根据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
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的主要优点:
微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须建成一系列小服务组合,可以通过http方式进行通信。
所谓微服务加购,就是打破之前all in one的架构方式,把每个功能元素独立出来,把独立出来的功能元素的动态组合
,需要的功能元素才去拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行赋值,而没有对整个应用进行复制,这样做的好处是:
程序核心:
高内聚
(在划分模块时,要把功能关系紧密的放到一个模块中)
低耦合
(模块之间的联系越少越好,接口越简单越好)
微服务论文介绍:https://martinfowler.com/articles/microservices.html#CharacteristicsOfAMicroserviceArchitecture
IDEA+Maven+SpringBoot2+java1.8
Spring官方提供了非常方便的工具让我们快速构建应用,IDEA也集成了这个网站
Spring Initializr::https://start.spring.io/
使用Spring Initializr 的 Web页面创建项目
①打开 https://start.spring.io/
②填写项目信息
③点击”Generate Project“按钮生成项目;下载此项目
④解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
⑤如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
使用 IDEA 直接创建项目
①创建一个新项目
②选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
③填写项目信息
④选择初始化的组件(初学勾选 Web 即可)
⑤填写项目路径
⑥等待项目构建成功
编写Controller
(注意:建包controller、dao、pojo 建立在HelloWorldApplication同级目录下)
启动SpringBoot
server.port = xxxx
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.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.3.4.RELEASEversion>
<scope>compilescope>
dependency>
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43mysql.version>
properties>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<version>2.3.4.RELEASEversion>
<scope>compilescope>
dependency>
引入SpringMVC全套组件
自动配好SpringMVC常用组件(功能)
自动配好Web常见功能,如:字符编码问题
SpringBoot帮我们配置好了所有web开发的常见场景
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
无需以前的包扫描配置
想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.gq”)
启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
Springboot的启动场景
Ctrl+Enter 主程序的SpringBootApplication查看源码
结论:SpringBoot所有的自动配置都是在主程序启动的时候扫描并加载:spring.factories
.
所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只有导入了对应的starter(启动器),有了启动器,对应的自动配置类才会生效,然后配置成功
作用:标注在某个类上说明这个类是SpringBoot的主配置类
, SpringBoot就应该运行这个类的main方法来启动SpringBoot应用
继续Ctrl+Enter:@SpringBootApplication
得到非常重要的三个注解:
(@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
我们继续进去这个注解查看:
// 点进去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}
@Component
public @interface Configuration {}
这里的 @Configuration
,说明这本质上是一个Spring配置类
,配置类就是对应Spring的xml 配置文件;
里面的 @Component
这就说明,启动类本身也是Spring中的一个组件而已
,负责启动应用!
再分析:@EnableAutoConfiguration
(这个注解最重要,因此单独弄到3.4节来分析)
@EnableAutoConfiguration
:开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能
,这样自动配置才能生效;
点击注解@EnableAutoConfiguration,进一步查看结构:
首先分析: @AutoConfigurationPackage
:自动配置包
点击注解,得到结构:
@Import({Registrar.class})
//利用Registrar导入一系列组件
//将指定的一个包下的所有组件导入进来 是主程序所在的包下
public @interface AutoConfigurationPackage {
}
@import
:Spring底层注解@import , 给容器中导入一个组件
Registrar.class 作用
:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
其次分析:@Import({AutoConfigurationImportSelector.class})
:自动配置导入选择器
这个注解也是一个派生注解,其中的关键功能由@Import
提供,其导入的AutoConfigurationImportSelector的selectImports()
方法通过SpringFactoriesLoader.loadFactoryNames()
扫描spring.factories文件
。
此文件包含了SpringBoot所有的自动配置类,路径为:
AutoConfigurationImportSelector
:自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
1. 这个类中有一个这样的方法
// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里的getSpringFactoriesLoaderFactoryClass()方法
//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
List<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
类的静态方法!我们进入SpringFactoriesLoader
类loadFactoryNames()
方法
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();
//将读取到的资源遍历,封装成为一个Properties
while(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);
}
}
}
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容器
中。
每一个XxxxAutoConfiguration
自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:
@ConditionalOnBean:当容器里有指定的bean的条件下。
@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。
以ServletWebServerFactoryAutoConfiguration
配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。
在ServletWebServerFactoryAutoConfiguration
类上,有一个@EnableConfigurationProperties
注解:开启配置属性,而它后面的参数是一个ServerProperties类,这就是约定大于配置的最终落地点。
springboot
所有的自动配置都是在启动的时候扫描并加载,扫描了spring.properties
配置文件,所有的自动配置类都在这里面,但是不定生效,因为要判断条件是否成立,只要导入了对应的start,就有对应的启动器,有了启动器我们自动装配就会生效,然后就配置成功
步骤:
SpringBoot
在启动的时候从类路径下的META-INF/spring.factories
中获取EnableAutoConfiguration
指定的值springboot-autoconfigure
的jar包中;(xxxAutoConfiguration)@Bean
, 就是给容器中导入这个场景需要的所有组件 ,@Configuration
;分析该方法主要分两部分,一部分是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。
配置文件的作用:修改SpringBoot自动配置的默认值。SpringBoot在底层配了默认值,但是可能实际中会修改
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
server.port=8081
application.yaml(推荐)
server:
port: 8081
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
注意:
“ ”
双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
‘ ’
单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
**对象、Map(键值对)**
#对象、Map格式
key:
value1:
value2:
在下一行来写对象的属性和值的关系,注意缩进;比如:
student:
name: qinjiang
age: 3
行内写法
student: {name: qinjiang,age: 3}
**数组( List、set )**
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
@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;
}
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: qinjiang
age: 3
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
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.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
5、确认以上配置都OK之后,我们去测试类中测试一下:
@SpringBootTest
class DemoApplicationTests {
@Autowired
Person person; //将person自动注入进来
@Test
public void contextLoads() {
System.out.println(person); //打印person信息
}
}
@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;
......
}
配置文件还可以编写占位符生成随机数
person:
name: qinjiang${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1
我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yaml还有我们之前常用的properties , 我们没有讲,我们来唠唠!
【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:这个什么意思呢? 比如我的yaml中写的last-name,这个和lastName是一样的
, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
4、复杂类型封装,yaml中可以封装对象 , 使用value就不支持。
结论
配置yaml和配置properties都可以获取到值 , 但是强烈推荐 yaml
;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
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.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
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 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
, 用来指定多个环境版本;
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
和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
指定位置加载配置文件
我们还可以通过spring.config.location
来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
配置文件到底能写什么?怎么写?
SpringBoot官方文档中有大量的配置,我们无法全部记住
我们以**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;
}
//。。。。。。。
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
**通俗理解:**把我们原先需要在bean中手打的属性(property)封装成了一个类,然后通过yaml文件进行自动注入,而我们也可以在application.yaml文件中对这些property进行赋值。
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
// .....
}
1.SpringBoot启动时会加载大量的自动配置类
2.我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
3.我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在其中,我们就不需要再去手动配置了,如果不存在我们再手动配置)
4.给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性即可;
XXXXAutoConfiguration:自动配置类:给容器添加组件,这些组件要赋值就需要绑定一个XXXXProperties类
XXXXProperties:里面封装配置文件中相关属性;
怎么去修改这些属性呢:说白了就是SpringBoot配置,---->.yaml、.properties这些文件
了解完自动装配的原理后,我们来关注一个细节问题,*自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug: true
属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug: true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
笔记小结
整合第三方技术通用方式
- 导入对应的starter
- 根据提供的配置格式,配置非默认值对应的配置项
笔记小结
步骤:
- 勾选MyBatis技术,也就是导入MyBatis对应的starter
- 数据库连接相关信息转换成配置 (datasource)
- 数据库SQL映射需要添加**@Mapper**被容器识别到
选择Spring初始化,并配置模块相关基础信息,并选择当前模块需要使用的技术集(MyBatis、MySQL)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC
username: root
password: 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
笔记小结:
- 手工添加MyBatis-Plus对应的starter
- 数据层接口使用BaseMapper简化开发
- 需要使用的第三方技术无法通过勾选确定时,需要手工添加坐标
MyBatis-Plus与MyBatis区别
上面整合Mybatis中步骤一中找不到MyBatis-Plus对应的依赖,因此需要手动添加SpringBoot整合MyBatis-Plus的依赖,可以通过mvnrepository获取
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
补充:由于SpringBoot中未收录MyBatis-Plus的依赖版本,需要指定对应的Version 可前往网站获取最新依赖:https://mvnrepository.com/artifact/org.hamcrest/hamcrest/2.2
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC
username: root
password: 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>{
}
笔记小结
- 整合Druid需要手动导入Druid对应的starter
- 根据Druid提供的配置方式进行配置
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.6version>
dependency>
补充:由于SpringBoot中未收录Druid的坐标版本,需要指定对应的Version
可前往网站获取最新坐标(依赖):https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter
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
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root
选择mybatis或者mybatis-plus中的步骤三都可以
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案例制作流程解析
创建表格:
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;
第一步:创建SpringBoot项目
第二步:添加依赖
(Mybatis-plus Dirud)
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.15version>
dependency>
第三步:修改配置文件为yml格式
使用Lombok
package com.gq.pojo;
import lombok.Data;
@Data
public class Book {
private int id;
private String type;
private String name;
private String description;
}
笔记小结
- 手工导入starter坐标(2个)
- 配置数据源与MyBatisPlus对应的配置
- 开发Dao接口(继承BaseMapper)
- 制作测试类测试Dao功能是否有效
技术实现方案
步骤一:导入mybatis-plus 以及Dirud依赖
(前面已经导入)
步骤二:配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)
application.yml
:
#Jdbc,druid
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssmp?serverTimezone=UTC
username: root
password: 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 {
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
Book book = bookDao.selectById(1);
System.out.println(book.toString());
}
}
跑通了 环境没有问题!
笔记小结
使用配置方式开启日志,设置日志输出方式为标准输出
为方便调试可以开启MyBatisPlus的日志:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
笔记小结
- 使用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 {
@Bean
public 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 {
@Autowired
private BookDao bookDao;
@Test
void 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());
}
}
笔记小结
- 使用
QueryWrapper
对象封装查询条件- 推荐使用
LambdaQueryWrapper
对象- 所有查询操作封装成方法调用
- 查询条件支持动态条件拼装
使用QueryWrapper
对象封装查询条件,推荐使用LambdaQueryWrapper
对象,所有查询操作封装成方法调用
@SpringBootTest
class SpringBootSsmpApplicationTests {
@Autowired
private BookDao bookDao;
@Test
void testGetByCondition(){
QueryWrapper<Book> qw = new QueryWrapper<Book>();
//相当于sql中:select * from xxx where name like %Spring%;
//这里有风险,因为把 name 写死了 所以考虑使用LambdaQueryWrapper
qw.like("name","Spring");
bookDao.selectList(qw);
}
}
@SpringBootTest
class SpringBootSsmpApplicationTests {
@Autowired
private BookDao bookDao;
@Test
void 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 {
@Autowired
private BookDao bookDao;
@Test
void 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);
}
Service层接口定义与数据层接口定义具有较大区别,不要混用
selectByUserNameAndPassword(String username,String password)
;login(String username,String password)
笔记小结
- 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 {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
@Override
public boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
@Override
public List<Book> getALl() {
return bookDao.selectList(null);
}
@Override
public IPage<Book> getByPage(int currentPage, int pageSize) {
return null;
}
}
上面通用方式中 若是实体类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 {
@Autowired
private BookDao bookDao;
//IBookService自定义了方法,那么也需要实现这些方法
@Override
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
//查询 分页
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage<Book> page = new Page<Book>(currentPage,pageSize);
return bookDao.selectPage(page,null);
}
}
笔记小结:
基于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接口
@Autowired
private IBookService bookService;
@GetMapping
public 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;
//查询所有
@GetMapping
public List<Book> getAll() {
return bookService.list();
}
//增加
@PostMapping
public Boolean save(@RequestBody Book book) {
return bookService.save(book);
}
//删除
@DeleteMapping("/{id}")
public Boolean delete(@PathVariable Integer id) {
return bookService.delete(id);
}
//修改
@PutMapping
public Boolean update(@RequestBody Book book) {
return bookService.updateById(book);
}
//查询 单个
@GetMapping
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}
//查询 分页
@GetMapping("/{currentPage}/{pageSize}")
public IPage<Book> getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return bookService.getPage(currentPage, pageSize);
}
}
笔记小结
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也称为前 后端数据协议
步骤一:设计表现层返回结果的模型类
用于后端与前端进行数据格式统一,也称为前后端数据协议
创建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 {
@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);
}
}
- 单体项目中页面放置在
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("操作取消");
}
笔记小结
- 1、使用注解**@RestControllerAdvice**定义SpringMVC异常处理 器用来处理异常的
- 2、异常处理器必须被扫描加载,否则无法生效
- 3、表现层返回结果的模型类中添加消息属性用来传递消息到页面
SpringMVC
的技术,因此放在controller层,放到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 {
//拦截所有的异常信息
@ExceptionHandler
public 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.3.3
笔记小结
- 使用el分页组件
- 定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
步骤一:页面使用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>
步骤二:定义分页组件需要使用的数据并将数据绑定到分页组件
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);
}
补充:对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询。此方法不能有效解决。项目开发中,当删除一项时,可直接跳转到第一页。
(条件查询一般和分页查询做在一起)
笔记小结
- 定义查询条件数据模型(当前封装到分页数据模型中)
- 异步调用分页功能并通过请求参数传递数据到后台
- 后台通过分页查询时携带条件
步骤一:查询条件数据封装
与分页操作混合封装
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层修改
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
修改
//查询 分页
@Override
public 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);
}
数据表:
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文件中手动添加依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.15version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
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;
}
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> {
}
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修改Book
boolean 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 {
@Autowired
private BookMapper bookMapper;
@Override
public boolean modifyById(Book book) {
return bookMapper.updateById(book) > 0;
}
//查询 分页
@Override
public 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 {
//拦截所有的异常信息
@ExceptionHandler
public 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 {
@Autowired
private IBookService bookService;
//显示全部数据
@GetMapping
public R getAll() {
List<Book> list = bookService.list();
return new R(true, list, null);
}
//新增
@PostMapping
public 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);
}
//更新数据
@PutMapping
public 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 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>