SpringBoot2基础篇

目录

  • 前言
  • 从今天开始进入微服务阶段
  • 一、 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学习内容:

SpringBoot2基础篇_第1张图片

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

SpringBoot2基础篇_第2张图片

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

SpringBoot2基础篇_第3张图片

二、第一个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项目导入,一路下一步即可,直到项目导入完毕。

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

SpringBoot2基础篇_第4张图片

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

使用 IDEA 直接创建项目

①创建一个新项目

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

③填写项目信息

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

⑤填写项目路径

⑥等待项目构建成功

SpringBoot2基础篇_第5张图片SpringBoot2基础篇_第6张图片

2.2.3、项目结构分析

SpringBoot2基础篇_第7张图片

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

编写Controller
(注意:建包controller、dao、pojo 建立在HelloWorldApplication同级目录下)
SpringBoot2基础篇_第8张图片
启动SpringBoot

SpringBoot2基础篇_第9张图片SpringBoot2基础篇_第10张图片
更改端口号:在application资源文件中配置
SpringBoot2基础篇_第11张图片

 server.port = xxxx

2.3、SpringBoot特点

2.3.1、依赖管理

  • 父项目做依赖管理:

SpringBoot2基础篇_第12张图片

  • 开发导入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.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>

2.3.2、自动配置

  • 自动配好Tomcat
    • 引入Tomcat依赖。
    • 配置Tomcat
 <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-tomcatartifactId>
       <version>2.3.4.RELEASEversion>
       <scope>compilescope>
     dependency>
  • 自动配好SpringMVC
    • 引入SpringMVC全套组件

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

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

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

    • 默认的包结构

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

    • 无需以前的包扫描配置

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

三、自动配置原理初探

3.1、pom.xml

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

启动器


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

3.2、自动配置原理结构图

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

SpringBoot2基础篇_第13张图片结论:SpringBoot所有的自动配置都是在主程序启动的时候扫描并加载:spring.factories.

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

3.3、@SpringBootApplication

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

SpringBoot2基础篇_第16张图片
下面,分别分析这三个注解的作用。

3.3.1、@ComponentScan

首先分析:@ComponentScan
SpringBoot2基础篇_第17张图片

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

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

3.3.2、@SpringBootConfiguration

其次分析:@SpringBootConfiguration
SpringBoot2基础篇_第18张图片

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

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

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

@Component
public @interface Configuration {}

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

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

3.3.3、@EnableAutoConfiguration

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

SpringBoot2基础篇_第19张图片

3.4、@EnableAutoConfiguration

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

点击注解@EnableAutoConfiguration,进一步查看结构:
SpringBoot2基础篇_第20张图片

3.4.1、@AutoConfigurationPackage

首先分析: @AutoConfigurationPackage :自动配置包
SpringBoot2基础篇_第21张图片点击注解,得到结构:

@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所有的自动配置类,路径为:
SpringBoot2基础篇_第22张图片

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类的静态方法!我们进入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();

            //将读取到的资源遍历,封装成为一个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);
        }
    }
}

  1. 发现一个多次出现的文件:spring.factories,全局搜索它

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

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

SpringBoot2基础篇_第23张图片
​ 这个@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)。
SpringBoot2基础篇_第24张图片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。

SpringBoot2基础篇_第25张图片

四、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: qinjiang
    age: 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: 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;
}

SpringBoot2基础篇_第26张图片4、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!
SpringBoot2基础篇_第27张图片


<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信息
    }

}

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} # 随机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

4.6、回顾properties配置

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

settings–>FileEncodings 中配置:
SpringBoot2基础篇_第28张图片

4.7、Properties和Yaml的对比小结

SpringBoot2基础篇_第29张图片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.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 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

源码:
SpringBoot2基础篇_第30张图片

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指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
SpringBoot2基础篇_第31张图片那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

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

我们可以通过启用 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)

SpringBoot2基础篇_第32张图片

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

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

7.2、整合Mybatis-Plus

笔记小结:

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

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

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

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

  
}

7.3、整合Dirud

笔记小结

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

步骤一:导入对应的starter

<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

步骤二:配置application.yml

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

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

选择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、模块创建

SpringBoot2基础篇_第33张图片

第一步:创建SpringBoot项目

SpringBoot2基础篇_第34张图片SpringBoot2基础篇_第35张图片

第二步:添加依赖

(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格式

SpringBoot2基础篇_第36张图片

8.2、实体类开发

使用Lombok

SpringBoot2基础篇_第37张图片

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

}

跑通了 环境没有问题!

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

}

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

8.3.4、条件查询功能

笔记小结

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

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

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 {

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

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 {


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

测试分页查询功能是否成功:
SpringBoot2基础篇_第38张图片

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接口
    @Autowired
    private IBookService bookService;


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

SpringBoot2基础篇_第39张图片

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

8.5.2、消息一致性处理

笔记小结

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

SpringBoot2基础篇_第40张图片

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

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

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

测试:
SpringBoot2基础篇_第41张图片

8.5.3、前后端协议联调

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

将如下四个静态资源导入到SpringBoot项目Resources下static目录内
(资源在公众号:黑马程序员 输入 springboot 基础篇内 )
SpringBoot2基础篇_第42张图片注意:项目最好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,会输出不一致的数据格式

SpringBoot2基础篇_第43张图片

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

R中重新添加一个构造器:
SpringBoot2基础篇_第44张图片

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,"系统错误,稍后再试");

    }
}

SpringBoot2基础篇_第45张图片参考链接: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-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);
}

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

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层修改 (用以接受前端条件查询的参数)
SpringBoot2基础篇_第46张图片

    @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修改
SpringBoot2基础篇_第47张图片

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

SpringBoot2基础篇_第48张图片

    //查询 分页
    @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);
    }

8.6、完整步骤(重要)

代码结构:
SpringBoot2基础篇_第49张图片

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

数据表:

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;

静态资源以及源码:
SpringBoot2基础篇_第50张图片

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

步骤一:基础模块

1.创建模块时勾选功能
SpringBoot2基础篇_第51张图片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>

步骤二:功能目录模块

3、代码目录:SpringBoot2基础篇_第52张图片

步骤三:实体类

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


}

步骤七:前端

SpringBoot2基础篇_第53张图片

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>

你可能感兴趣的:(java框架,spring,boot,java,spring)