Maven、Spring、SpringMVC、MyBatis、SSM整合、SpringBoot

Maven

   Maven 是一款为 Java 项目管理构建、依赖管理的工具(软件)使用 Maven 可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。

   依赖管理: Maven 可以管项自的依赖,包括自动下载所需依赖库、自动载依赖需要的依赖并自保证版本没有冲突依赖版本管理等。通过 Maven,我们可以方便地维护项目所依赖的外部库,而我们仅仅需要编写配置即可。

   构建管理: Maven 可以管理项自的编译、测试、打包、部暑等构建过程。通过实现标准的构建生命周期,Maven 可以确保每一个构建过程都遵循同样的规则和最佳实践。同时,Maven 的插件机制也使得开发者可以对构建过程进行扩展和定制。主动触发构建,只需要简单的命令操作即可。image-20240127022938293

   原理: image-20240127023411742

   mvn -v 验证安装是否成功。额外配置 JDK 版本,不用它的 5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- 不用默认的jdk5.0 -->
<profile>
<id>jdk-17</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>17</jdk>
</activation>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
</properties>
</profile>

<plugins>
<!-- 在build里添加,解决自带site插件报错 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- 自带的打包war插件与JDK17不匹配的话,在build里添加 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
  • GroupID 格式: com.{公司/BU }.业务线.[子业务线],最多 4 级。

  • ArtifactID 格式: 产品线名-模块名。语义不重复不遗漏,先到仓库中心去查证一下。

  • Version 版本号格式推荐: 主版本号.次版本号.修订号

    • 主版本号:当做了不兼容的 API 修改,或者增加了能改变产品方向的新功能
    • 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。
    • 修订号:修复 bug,没有修改方法签名的功能加强,保持 API 兼容性
    • 例如:初始→ 1.0.0 修改 bug 一→ 1.0.1 功能调整→ 1.1.1 等
  • Packaging 定义规则: 指示将项目打包为什么类型的文件,idea 根据 packaging 值,识别 maven 项目类型!

    • packaging 属性为 jar(默认值),代表普通的 Java 工程,打包以后是 jar 结尾的文件。
    • packaging 属性为 war,代表 Java 的 web 工程,打包以后.war 结尾的文件。
    • packaging 属性为 pom,代表不会打包,用来做继承的父工程。

创建 web 项目:

  1. 修改 pom 文件的 packaging 属性为 war,记得重新加载;
  2. src 文件下创建 webapp/WEB-INF/web.xml,不同工具中的 webapp 不一样,有的叫 webwebContentwebRoot
  3. xml 文件不好写,可以用 JBLJavaToWeb 插件。
命令 描述
mvn compile 编译项目,生成 target 文件
mvn package 打包项目,生成 war 文件
mvn clean 清理编译或打包后的项目结构
mvn install 打包后上传到 maven 本地仓库(本地部署)
mvn deploy 只打包,上传到 maven 私服仓库(私服部署)
mvn site 生成站点(报告),报错的话,pom 添加 site 相关插件(百度)
mvn test 执行测试源码(测试)

记不住也没多大事,有工具辅助,在右侧状态栏。

主要生命周期: 懒人操作,大部分命令会执行之前的命令

  • 清理周期:主要是对项目编译生成文件进行清理

    包含命令:clean

  • 默认周期:定义了真正构件时所需要执行的所有步骤,它是生命周期中最核心的部分

    包含命令:compile-test-package-install/deploy

  • 报告周期

    包含命令:site

    打包:mvn clean package 本地仓库:mvn clean install

依赖管理:

  • 引入 :放 dependencies 标签里,写各种依赖。会自动下载依赖以及依赖的依赖。

    1
    2
    3
    4
    5
    6
    7
    <dependency><!-- maven仓库信息官网https://mvnrepository.com,可查,可直接复制 -->
    <!-- 无法访问,可用使用插件maven search,安装好后在Tools里 -->
    <groupId></groupId>
    <artifactId></artifactId>
    <version></version>
    <scope>test测试目录可用/compile任何目录可用/provided除了服务器,都可用。服务器自带,例:servlet /runtime运行时才用,例:mysql</scope><!-- 产生作用的范围 -->
    </dependency>
  • 版本统一提取和维护

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 声明版本 --> 
    <properties>
    <!--命名随便,内部制定版本号即可!-->
    <junit.version>4.11</junit.version><!-- 在上面的引入里的版本处可使用${junit.version} -->
    <!--也可以通过maven规定的固定的key,配需maven的参数!如下配编码格式! -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
  • 下载失败解决:❶检查网络;❷清理之前下载失败的仓库缓存

build 构建插件:

  • <finalName>打包名</finalName>

  • 如果在 java 文件夹中添加 java 类,会自动打包编译到 classes 文件夹下。

    但是在 java 文件夹中添加 xml 文件,默认不会被打包!

    默认情况下,按照 maven 工程结构放置的文件会默认被编译和打包!

    除此之外、我们可以使用 resources 标签,指定要打包资源的文件夹要把哪些静态资源打包到 classes 根自录下!

    1
    2
    3
    4
    5
    6
    7
    <resource>
    <directory>src/main/java</directory> <!-- 打包目录,只配置,则都打包 -->
    <includes>
    <include>**/*.properties</include><!-- 包含任意层级的properties结尾的文件 -->
    <include>**/*.xml</include>
    </includes>
    </resource>

依赖传递和依赖冲突

  • 假如有 Maven 项自 A,项自 B 依赖 A,项目 C 依赖 B。那么我们可以说 C 依赖 A。也就是说,依赖的关系为:C 一 > B 一 A,那么我们执行项目 C 时,会自动把 B、A 都下载导入到 C 项目的 jar 包文件夹中,这就是依赖的传递性。
  • 在 A 依赖 B,B 依赖 C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围
    • B 依赖 C 时使用 comile 范围:可以传递
    • B 依赖 C 时使用 test 或 provided 范围:不能传递,所以需要这样的 jar 包时,就必须在需要的地方明确配置依赖才可以
  • 作用:简化依赖导入过程;确保依赖版本正确。
  • 冲突时(高版本兼容低版本):❶路径最短者优先;❷先声明者优先;❸手动排除 <exclusions> <exclusion><groupId>org.springframeworks/grounId><artifactId>spring-aop</artifactId></exclusion> </exclusions>,直接不要了。

继承和聚合关系:

  • Maven 继承是指在 Maven 的项目中,让一个项目从另一个项目中继承配置信息的机制。继承可以让我们在多个项目中共享同一配置信息,简化项目的管理和维护工作。

  • 作用:在父工程中统一管理项目中的依赖信息。它的背景是:

    • 对一个比较大型的项目进行了模块拆分。

    • 一个 project 下面,创建了很多个 module。

    • 每一个 module 都需要配置自己的依赖信息。

      它背后的需求是:

      • 多个模块要使用同一个框架,它们应该是同一个版本,项目中使用的框架版本需要统一管理。

      • 使用框架时所需要的 jar 包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。

        通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。

  • 父工程不写代码,直接干掉 src。

    • 父工程里

      1
      2
      3
      4
      5
      6
      <!-- 只有父工程的打包方是pom-->
      <packaging>pom</packaging>
      <!-- 罗列了子模块的内容 -->
      <modules>
      <module>maven-son-one</module>
      </modules>
    • 子工程里

      1
      2
      3
      4
      5
      6
      <parent>
      <artifactId>maven-parent</artifactId>
      <groupId>com.atguigu</groupId>
      <version>1.0-SNAPsHoT</version>
      </parent>
      <artifactId>maven-son-one</artifactId>
    • 继承情况:dependencies 默认全部继承,如果要子工程按需继承,在父工程的 dependencies 标签外套上 dependencyManagement,子工程自己写依赖,不需要写 version;这种方式:父工程只做定义,不下载;子工程按需继承,按需下载。

  • 聚合:

    • 背景:各个模块相互依赖的情况下,打包时,要打包和部署父工程以及其他依赖的模块。
    • 概念:Maven 聚合是指将多个项目组织到一个父级项目中,工以便一起构建和管理的机制。聚合可以帮助我们更好地管理一组相关的子项目,同时简化它们的构建和部署过程。
    • 作用:
      • 管理多个子项目:通过聚合,可以将多个子项目组织在一起,方便管理和维护。
      • 构建和发布一组相关的项目:通过聚合,可以在一个命令中构建和发布多个相关的项目,简化了部署和维护工作。
      • 优化构建顺序:通过聚合可以对多个项目进行顺序控制,避免出现构建依赖混乱导致构建失败的情况。
      • 统一管理依赖项:通过聚合,可以在父项目中管理公共依赖项和插件,避免重复定义。
    • 步骤:在父工程执行打包和部署。

仓库之间的关系和优先级:

   Maven 仓库主要分为本地仓库(Local Repository)、中央仓库(Central Repository)和远程仓库(Remote Repository)三种类型。

   本地-> 私服-> 镜像-> 中央,任何环节找到了,都会在之前环节保存一份。

Spring

前言

技术体系

  • 单一架构

    image-20240128015722738
  • 分布式架构

    image-20240128020804551

框架概念和理解

   框架(Framework)是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。

  • 提高开发效率
  • 降低开发成本
  • 提高应用程序的稳定性
  • 提供标准化的解决方案

   站在文件结构的角度理解框架,可以将框架总结:框架 = jar 包+配置文件

SpringFramework 简介

Spring 和 SpringFramework

   广义上的 Spring 泛指以 SpringFramewiork 为基础的 Spring 技术栈;狭义的 Spring 特指 SpringFramework,通常我们将它称为 Spring 框架。

SpringFramework 主要功能模块

功能模块 功能介绍
Core Container 核心容器,控制反转和依赖注入
AOP&Aspects 面向切面编程
TX 声明式事务管理
Testing 快速整合测试环境
DataAccess/lntegration 提供了对数据访问/集成的功能
SpringMVC 提供了面向 Web 应用程序的集成功能。
SpringFramework框架结构图

SpringFramework 主要优势

  1. 丰富的生态系统:Spring 生态系统非常丰富,支持许多模块和库,如 SpringBoot、SpringSecurity、Spring Cloud 等等,可以帮助开发人员快速构建高可靠性的企业应用程序。
  2. 模块化的设计:框架组件之间的松散耦合和模块化设计使得 SpringFramework 具有良好的可重用性、可扩展性和可维护性。开发人员可以轻松地选择自已需要的模块,根据自已的需求进行开发。
  3. 简化 Java 开发:SpringFramework 简化了 Java 开发,提供了各种工具和 APl,可以降低开发复杂度和学习成本。同时,SpringFramework 支持各种应用场景,包括 Web 应用程序、RESTfulAPl、消息传递、批处理等等。
  4. 不断创新和发展:SpringFramework 开发团队一直在不断创新和发展,保持与最新技术的接轨,为开发人员提供更加先进和优秀的工具和框架。

Springloc 容器概念

组件和组件管理概念

  • 什么是组件?

    常规的三层架构处理请求流程 整个项目就是由各种组件搭建而成
  • 我们的期待?:替我们创建组件的对象、帮我们保存组件的对象、帮助我们自动组装、替我们管理事务、协助我们整合其他框架……

  • Sprin 充当组件管理角色(loC)

    组件可以完全交给 Spring 框架进行管理, Spring 框架替代了栏 序员原有的 new 对象和对象属性赋值动作等。

    Spring 具体的组件管理动作包合:

    • 组件对象实例伴
    • 组件属性属性赋值
    • 组件对象之间引用
    • 组件对象存活周期管理
    • ……

   我们只需要编写元数据(配置文件)告知 Spring 管理娜些类组件和他们的管理即可!

   注意:组件是映射到应用程序中所有可重用组件的 aVa 对象,应该是可复用的功能对象!

  • 组件交给 Spring 管理优势
    1. 降低了组件之间的耦合性:Springloc 容器通过依赖注入机制,将组件之间的依赖关系削弱,减少了程序组件之间的耦合性,使得组件更加松散地耦合。
    2. 提高了代码的可重用性和可维护性:组件的实例化过程、依赖关系的管理等功能交给 Spring IoC 容器处理,使得组件代码更加模块化、可重用、更易于维护。
    3. 方便了配置和管理:Spring IoC 容器通过 XML 文件或者注解,轻松的对组件进行配置和管理,使得组件的切换、替换等操作更加的方便和快捷。
    4. 交给 Spring 管理的对象(组件)方可享受 Spring 框架的其他功能(AOP 声明式事务管理)等

Spring IoC 容器和容器实现

   Springloc 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。

   org.springframework.beansorg.springframework.context 包是 SpringFramework 的 loC 容器的基础包。BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是 SpringIoC 容器标准化超接口!ApplicationContext 是 BeanFactory 的子接口。

   ApplicationContext 补充:

  • 更容易与 Spring 的 AOP 功能集成

  • 消息资源处理 星(用于国际化)

  • 特定于应用程序给予此接口实现,例如 Web 应用程序的 WebApplicationContext

    简而言之,BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。ApplicationContextBeanFactory 白 的完整超集!

   ApplicationContext 容器实现类:

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
AnnotationConfigApplicationContext 通过读取 Java 配置类创建 IOc 容器对象
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

   Spring 框架提供了多种配置方式:XML 配置方式、注解方式和 Java 配置类方式:

  • XML 配置方式:是 Spring 框架最早的配置方式之一,通过在 XML 文件中定义 Bean 及其依赖关系、Bean 的作用域等信息,让 SpringIoC 容器来管理 Bean 之间的依赖关系。该方式从 Spring 框架的第一版开始提供支持。

  • 注解方式从 Spring2.5 版本开始提供支持,可以通过在 Bean 类上使用注解来代替 XML 配置文件中的配置信息。通过在 Bean 类上加上相应的注解(如@Component,@Service,@Autowired 等),将 Bean 注册到 SpringIoC 容器中,这样 SpringIoC 容器就可以管理这些 Bean 之间的依赖关系。

  • Java 配置类方式:从 Spring3.o 版本开始提供支持,通过 Java 类来定义 Bean、Bean 之间的依赖关系和配置信息,从而代替 xML 配置文件的方式。Java 配置类是一种使用 Java 编写配置信息的方式,通过@Configuration、@Bean 等注解来实现 Bean 和依赖关系的配置。

    配置方式的使用场景不同,为了更多体验每种方式,SSM 期间,我门使用 XML+注解方式为主。 SpringBoot 期间,我们使用配置类+注解方式为主!

Spring IoC/DI 概念总结

  • loc 容器

    SpringloC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

  • IoC(Inversion of Control)控制反转

    主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理:即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

  • DI(Dependency Iniection)依赖注入

    DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

Spring IoC/DI 实现

实现步骤

  1. 配置元数据(配置)

    配置元数据,既是编写交给 SpringloC 容器管理组件的信息,配置方式有三种。

    基于 XML 的配置元数据的基本结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?xmlversion="1.0"encoding="UTF-8"?> 
    <!-- !-此处要添加一些约束,配置文件的标签并不是随意命名 -->
    < beans xmlns =" http://www.springframework.org/schema/beans "
    xmlns: xsi = "http://www.w3.org/2001/xMLSchema-instance"
    xsi: schemaLocation = "http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd" >

    <bean id="..." [1]class="..."[2]>
    <!--:collaborators and configuration for this bean go here-->
    </bean>
    <bean id="..." class="...">
    <!-- collaborators andconfiguration for this bean go here -->
    </bean>
    <!-more bean definitions go here-->
    </beans>

    SpringloC 容器管理一个或多个组件。这些组件是使用你提供给容器的配置元数据(例如,以 XML <bean/> 定义的形式,它可用写 <bean/>,也可以说写 <bean><bean/>)创建的。

    <bean/> 标签 == 组件信息声明

    • id 属性是标识单个 Bean 定义的字符串。
    • class 属性定义 Bean 的类型并使用完全限定的类名。
  2. 实例化 IoC 容器

    提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。

    我们应该选择个合适的实现类,进行 IoC 容器的实例化:

    1
    ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
  3. 使用容器,获取 Bean(组件)

    ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class<T> requiredType),可以检索 bean 的实例。

    允许读取 Bean 定义并访问它们,如以下示例所示:

    1
    2
    3
    4
    5
    6
    //创建 ioc 容器对象,指定配置文件,ioc 也开始实例组件对象 
    ApplicationContext context = new ClassPathXmlApplicationContext("services.xml" "daos.xml");
    //获取 ioc 容器的组件对象
    PetStoreService service = context.getBean("petstore", PetStoreService.class);
    //使用组件对象
    List <string> userList = service.getUsernameList();

基于 XML 方式管理 Bean

声明配置文件、创建容器和获取 Bean
  • 思路

    声明配置文件和创建容器思路
  • 准备(框架的搭建一般思路 ❶创建 Maven 工程 ❷引入依赖(jar 包) ❸编写配置文件 ❹使用核心类库测试)

    1. 创建 Maven 工程

    2. 导入 Springloc 相关依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <dependencies>
      <!--spring context依赖-->
      <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
      <dependency>
      <groupId> org.springframework </groupId>
      <artifactId> spring-context </artifactId>
      <version> 6.0.6 </version>
      </dependency>
      <!--unit5测试-->
      <dependency>
      <groupId> org.junit.jupiter </groupId>
      <artifactId> junit-jupiter-api </artifactId>
      <version> 5.3.1 </version>
      </dependency>
      </dependencies>
    3. 准备组件类(HappyComponent)

      1
      2
      3
      4
      5
      6
      7
      8
      public class HappyComponent {
      public HappyComponent() {
      System.out.println("进行了构造");
      }
      public void doWork(){
      System.out.println("HappyComponent.doWork");
      }
      }
    4. 创建 spring01.xml 文件

      1
      <bean id = "HappyComponent01" class="com.atguigu.xml.pojo.HappyComponent"/>
    5. 在测试里写

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Test
      public void test(){
      //1.基 springo1.xml 配置文件,创建 Ioc 容器
      ApplicationContext context = new ClassPathXmlApplicationContext("spring01.xml");
      //2.根据 id 获取 Bean 对象,需要强转
      //HappyComponent happyComponent01 = (HappyComponent)context.getBean("HappyComponent01");
      //2.根据类来获取 Bean 对象,需要类型唯一,开发常用
      //HappyComponent happyComponent01 = context.getBean(HappyComponent.class);
      //2.两种者结合获取,学习常有
      HappyComponent happyComponent01 = context.getBean("HappyComponent01", HappyComponent.class);
      //3.使用对象
      happyComponent01.doWork();
      }
  • 其他说明

    • bean 的 id 不能重复。
    • 我:本质是设置配置文件,然后刷新。
    • 通过反射调用无参构造方法,可通过编写无参构造验证。
    • 通过 id 获取需要强转。
    • 仅通过类获取对象,需要 class 唯一。
Bean 属性赋值:setter 注入
  1. 组件添加属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class HappyComponent {
    private String componentName;

    public HappyComponent() {
    System.out.println("进行了构造");
    }

    public void doWork(){
    System.out.println("HappyComponent.doWork");
    }

    public String getComponentName() {
    System.out.println("componentName取值");
    return componentName;
    }

    public void setComponentName(String componentName) {
    System.out.println("componentName赋值:" + componentName);
    this.componentName = componentName;
    }
    }
  2. 修改 xml 文件

    1
    2
    3
    4
    <bean id = "HappyComponent01" class="com.atguigu.xml.pojo.HappyComponent">
    <!-- set方法赋值,标签为property -->
    <property name="componentName" value="开心的组件"/>
    </bean>
  3. 测试

    1
    System.out.println("happyComponent01.getComponentName() = " + happyComponent01.getComponentName());
  4. 说明

    • property 里,如果 name 为属性名,者 value 仅能为 8 大基本类型+8 大包装类型+String
    • 通过 property 进行赋值,必须提供 set 方法
Bean 属性赋值:引用其他 Bean
  1. 声明新组件(HappyMachine)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class HappyMachine {
    private String machineName;

    public String getMachineName() {
    return machineName;
    }

    public void setMachineName(String machineName) {
    this.machineName = machineName;
    }
    }
  2. 原组件(HappyComponent)引用新组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private HappyMachine happyMachine;

    public HappyMachine getHappyMachine() {
    return happyMachine;
    }

    public void setHappyMachine(HappyMachine happyMachine) {
    this.happyMachine = happyMachine;
    }
  3. 注入新组件(HappyMachine),并赋值

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id = "HappyComponent01" class="com.atguigu.xml.pojo.HappyComponent">
    <!-- set方法赋值,标签为property -->
    <property name="componentName" value="开心的组件"/>
    <property name="happyMachine" ref="HappyMachine"/>
    </bean>
    <bean id="HappyMachine" class="com.atguigu.xml.pojo.HappyMachine">
    <property name="machineName" value="小爱同学"/>
    </bean>
  4. 测试

    1
    System.out.println("happyComponent01.getHappyMachine() = " + happyComponent01.getHappyMachine());
  5. 说明

    • ref 的值为 id
    • 声明 bean 不分先后顺序,Spring 容器内部有缓存机制,先实例化后属性赋值
    • ref 容易错写成 value,会抛出 Causedby: java.lang.llegalStateException: Cannot convert value of type
      java.lang.Stringtorequiredtype 异常
    • 只有声明到 ioc 容器,方可被其他 bean 引用
    • 外部 bean 是共享的
Bean 属性赋值:内部 Bean 声明(了解)
  1. 声明内部 bean 配置

    在 bean 里面配置的 bean 就是内部 bean,内部 bean 只能在当前 bean 内部使用,在其他地方不能使用。
    不会在 ioc 容器中,实例和存储内部 bean, 只会缓存类信息,每次获取的时候再实例化!!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <bean id = "HappyComponent02" class="com.atguigu.xml.pojo.HappyComponent">
    <!-- set方法赋值,标签为property -->
    <property name="componentName" value="开心的组件"/>
    <property name="happyMachine">
    <bean class="com.atguigu.xml.pojo.HappyMachine">
    <property name="machineName" value="小艺"/>
    </bean>
    </property>
    </bean>
  2. 说明

    • property 标签也是有两种写法
Bean 属性赋值:集合类型赋值
  1. 组件里声明集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private List <String> stringList;

    public List <String> getStringList() {
    return stringList;
    }

    public void setStringList(List <String> stringList) {
    this.stringList = stringList;
    }
  2. xml 文件里赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <bean id="HappyComponent02" class="com.atguigu.xml.pojo.HappyComponent">
    <property name="happyMachine" ref="HappyMachine"/>
    <property name="componentName" value="开心的组件"/>
    <property name="stringList">
    <list>
    <value></value>
    <value></value>
    <value></value>
    <value> 身体 </value>
    </list>
    </property>
    </bean>
  3. 说明

    • 同理,setmap,在 property 里写相应的标签
    • map 标签外,里面都是 value 标签
    • map 标签里是 entry 标签,entry 标签里面是 keyvalue,引用类型包含在 entry 标签里,String 类型可直接写在 entry 标签上
    • entry 标签也可以写两种方式
    • Properties 对于 xml 里的标签是 <props></props>
    • 数组对于 xml 里的标签是 <arpay></array>
Bean 属性赋值:引入外部 Properties 配置参数

   例子是将 Druid(德鲁伊)连接池对象交给 Spring 的 loC 容器管理!

  1. 添加数据库依赖,放到需要的模块里就可以了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
    </dependency>
    </dependencies>
  2. 一个 Druid 使用的老方法

    1
    2
    3
    4
    5
    6
    7
    8
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverclassName("com.mysql.cj.jdbc.Driver");
    datasource.seturl("jdbc: mysql://localhost: 3306/blog");
    datasource.setusername("root");
    dataSource.setPassword("atguigu");

    DruidPooledConnection connection = dataSource.getConnection();
    System.out.println("connection =" + connection);
  3. 在 xml 里配置

    1
    2
    3
    4
    5
    6
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/book"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    </bean>

    上面的方式还是有点死,接下来改进。

    创建 Resource Bundle,名字取 db.properties。不加前缀的话,会使用电脑当前用户去连接数据库。

    1
    2
    3
    4
    jdbc.driverclassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/book
    jdbc.username=root
    jdbc.password=123456

    配置 xml

    1
    2
    3
    4
    5
    6
    7
    <context:property-placeholder location="classpath:db.properties"/><!-- 前面面不用自己全写,根据提示回车,idea会帮助添加约束 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driverclassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>
  4. 使用

    1
    2
    3
    4
    ApplicationContext context = new ClassPathXmlApplicationContext("spring01.xml");
    DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
    DruidPooledConnection connection = dataSource.getConnection();
    System.out.println("connection = " + connection);
高级特性:FactoryBean 特性

   FactoryBean 接口是 SpringIoC 容器实例化逻辑的可插拔性特点。用于配置复杂的 Bean 对象,可以将创建过程存储在 FactoryBeangetObject 方法。FactoryBean<T> 接口提供三种方法:

  • T getObject():返回此工厂创建的对象的实例。该返回值会被存储到 IoC 容器
  • boolean isSingleton():如果此 FactoryBean 返回单例,则返回 true,否则返回 false。此方法的默认实现返回 true(注意,lombok 插件使用,可能影响效果)。
  • Class<?> getObjectType():返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null

   例如,Connection 对象的创建,我们通过 class.forName("com.mysgl.ci.idbc.Driyer");Connection connection =DriverManager.getConnection(url,user,password);,它和一般的对象创建不一样。

   注意:我们从 getConnection 点进去,我们可以发现返回值上有括号,例如 return (con); 我不明白括号有什么用。

   让 Spring 创建 Connection 对象:

  1. 新建包 factory,创建一个类,实现 FactoryBean 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class ConnectionFactoryBean implements FactoryBean<Connection> {

    //最终生产的对象
    @Override
    public Connection getObject() throws Exception {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/book", "root", "123456");
    return connection;
    }

    //生产的对象的类型
    @Override
    public Class<?> getObjectType() {
    return Connection.class;
    }

    //生产的对象是否为单例
    @Override
    public boolean isSingleton() {
    return true;
    }
    }
  2. 配置 xml

    1
    2
    <bean id="connection" class="com.atguigu.xml.factory.ConnectionFactoryBean">
    </bean>
  3. 测试

    1
    2
    3
    ApplicationContext context = new ClassPathXmlApplicationContext("spring01.xml");
    Connection connection = context.getBean("connection", Connection.class);
    System.out.println("connection = " + connection);
  4. 说明

   显然,直觉会让你在实现时用 BeanFactory,然而 FactoryBeanBeanFactory 是有区别的。

   FactoryBean 是 Spring 中一种特殊的 bean,可以在 getObject 工厂方法自定义的逻辑创建 Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject 方法来得到某所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。

   一般情况下,整合第三方框架,都是通过定义 FactoryBean 实现!!!

   BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例 getBean 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysgl 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如 ApplicationContext 接口)提供了额外的强大功能。

   总的来说,FactoryBeanBeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能;而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

高级特性:Bean 的作用域

   bean 标签声明 Bean,只是将 Bean 的信息配置给 SpringIoC 容器。在 Ioc 容器中,这些 bean 标签对应的 Spring 内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id, class, 属性等等)!这意味着,BeanDefinition 与类概念一样,SpringIoC 容器可以可以根据 BeanDefinition 对象反射创建多个 Bean 对象实例。具体创建多少个 Bean 的实例对象,由 Bean 的作用域 scope 属性指定。默认情况:全局只需要实例化一个 Bean 对象,绝大情况我们也仅需创建一个对象!

取值 含义 创建对象的时机 默认值
singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IoC 容器初始化时
prototype 这个 bean 在 IoC 容器中有多个实例 获取 bean 时
如果是在 WebApplicationcontext 环境下还会有另外两个作用域(但不常用)
request 请求范围内有效的实例 每次请求
session 会话范围内有效的实例 每次会话
高级特性:Bean 的生命周期(此处比较简略)

   SpringFramework 的 Bean 生命周期是指一个 Bean 对象从它的创建、初始化到销毁的整个过程。初始化方法用 init-method 属性指定。销毁方法由 destroy-method 指定。初始化在 set 方法之后。ApplicationContext 不具备关闭能力,ConfigurableApplicationdontext 具备关闭能力。实现 BeanPostProcessor 接口,可以做到在初始化之前和之后执行一些需要的步骤(后置处理器的前置方法、后置处理器的后者置方法),将它放到容器了,对所有 bean 都将起效果,相当于统一处理和过滤。

基于注解方式管理 Bean

Bean 注解标记和扫描(Ioc)

   和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。

   Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

1
2
3
4
5
6
7
<bean id="userDao" class="com.atguigu.spring.xml.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.atguigu.spring.xml.service.impl.UserServiceImpl">
<property name="userDao"ref="userDao"/>
</bean>
<bean id="userController" class="com.atguigu.spring.xml.controller.UserController">
<property name="userService" ref="userService"/>
</bean>
1
2
3
UserDaouserDao = ioc.qetBean("userDao", UserDao.class);
UserService userService = ioc.getBean("userService", Userservice.class);
UserController userController = ioc.qetBean("userController", UserController.class);

   以前,在 service 里调用 dao 是写死的,当要更换时要修改。现在可以写上 set 方法,在 xml 里进行属性配置,将赋值 dao 交给 Spring。当项目庞大时,xml 方式就有些捉襟见肘了。

注解 说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。使用时只需将该注解标注在相应类上即可。
@Repository 仓库 该注解用于将数据访问层(Dao 层-> Mapper-> biz)的类标识为 Spring 中的 Bean,其功能与 @Component 相同
@Service 该注解通常作用在业务层(Service 层)用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 该注解通常作用在控制层(如 SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Configuration 标志为配置类

   现在已经标记了,需要告诉 Spring 哪里有标记了。

1
2
3
<context:component-scan base-package="com.atguigu.spring.anno">
<context:exclude-filter type="arnotation注解" expression="org.springframework.stereotype.Controller注解"/>
</context:component-scan>

   使用 context:component-scan 标记可以在指定的包下扫描,包含子包。以前在 xml 里配置了 id 和 class,现在怎么 getBean 呢?名字用类名的首字母小写(也可以对 value 属性赋值改名,只赋值 value安全 可忽略属性名。),class 用 类.class。排除和包含(只扫某某,则需将 use-default-filters 属性改为 false)用子标签,选择合适的 typeexpression

1
ioc.getBean("userController类名首字母小写",UserController.class);
Bean 属性赋值:引用类型自动装配(DI)

   前面已经通过注解将 bean 放到容器里了,但还需要进行属性赋值。注意参与自动装配的组件(需要装配、被装配) 全部都必须在 loc 容器中。在成员变量(set 方法、构造方法)上自接标记 @Autowired 注解即可,不需要提供 set 方法。

   @Autowired 先按照类型进行寻找,再找按照名字进行寻找。如果类型一样,可以用 @Qualifier 指定名字,不过不能单独使用,和 @Autowired 一起使用。另外 @Autowired 还有一个属性 required=false,表示能装就装。

   假设要装配 Dao,但是容器里一个都没有,会报错,至少一个和Autowired(required=true);当把 required=true 改为 required=false 后,报空指针异常。

   @Resource@Autowired 都可以自动注入,但注入方式是相反的, 而且 @Resource 是 JDK 的。 JDK 还有其他的注解,比如一下一些我没记住的。8 之后,要使用部分注解还有额外添加依赖。小细节:@Resource 在 idea 不显示小白线

注解 说明
@SuppressWarnings 抑制编译时产生的警告消息。
@SafeVarargs 标识一个有安全性警告的可变参数方法。
@PostConstruct 标识一个方法作为初始化方法。
@PreDestroy 标识一个方法作为销毁方法。
@Resource.AuthenticationType 标识注入的资源的身份验证类型。
@Resource.AuthenticationName 标识注入的资源的默认名称。
@SupportedAnnotationTypes 标识注解处理器所处理的注解类型。
@SupportedSourceVersion 标识注解处理器支持的 Java 源码版本。
@Named 标识一个被依赖注入的组件的名称。
@Inject 标识一个需要被注入的依赖组件。
@Singleton 标识一个组件的生命周期只有一个唯一的实例。
@RolesAllowed 标识授权角色。
@PermitAll 活动无需进行身份验证。
@DenyAll 标识不提供针对该方法的访问控制。
Bean 属性赋值:基本类型属性赋值(DI)

   @Value 通常用于注入外部化属性,因为容器一启动,就已经有值了,且不能改变。先简单写一个 properties 文件和配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class ConfigProperties {

@Value("${config.name}")
private String name;
@Value("${config.age:100}")
private Integer age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

   在 xml 里引入 properties 文件。

1
<context:property-placeholder location="classpath:config.properties"/>

   @Value("${config.age:100}") 如果没有写 :100,当找不到时,就会使用 100 作为默认值。

基于配置类方式管理 Bean

   Spring 完全注解配置(FullyAnnotation-basedConfiguration)是指通过 Java 配置类代码来配置 Spring 应用程序使用注解采替代原本在 XML 配置件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

   创建配置类:

1
2
3
4
5
6
@Configuration//表明配置,加入容器
@ComponentScan("com.atguigu.spring.allanno")//扫描的包
@PropertySource("classpath:config.properties")//导入的外部资源
public class SpringConfig {

}

   说明:@ComponentScan@PropertySource 都有带 s 的,可以写多个 ComponentScanPropertySource。其中 @ComponentScan 接收数组,内部的 basePackages 有别名 value。在上面的代码块中,只写了一个包,所以省略了许多。

   扫描和外部资源已经可以实现了,现在实现配置 bean,例子:将 Druid 连接池对象存储到 IoC 容器。(使用 @Bean 注解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
@ComponentScan("com.atguigu.spring.allanno")
@PropertySource({"classpath:config.properties","classpath:db.properties"})
public class SpringConfig {

// @Value("${jdbc.driverclassName}")
// private String driverClassName;
// @Value("${jdbc.url}")
// private String url;
// @Value("${jdbc.username}")
// private String userName;
// @Value("${jdbc.password}")
// private String password;
@Bean
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public DruidDataSource dataSource(@Value("${jdbc.driverclassName}")String driverClassName,
@Value("${jdbc.url}")String url,
@Value("${jdbc.username}")String userName,
@Value("${jdbc.password}")String password){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}

}

@Test
public void test2() throws SQLException {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = context.getBean("dataSource", DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println("connection = " + connection);
}

   说明:@Bean 返回值类型即为 class,方法名即为 id。如果不满意 id,可以自己取,可以取多个。如果不喜欢属性变量被其它使用,可将其写在方法参数列表里,就像 SpringMVC 那样。bean 的属性有 scope,可以用注解 @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 表示,可见有些值 Spring 提供了,不用写魔法值。初始化和销毁方法可以在 @Bean 注解里指示属性 initMethoddestroyMethod

   之前用 xml 时,配置 bean 时可以引用其他 bean。配置类怎么实现呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public HappyMachine happyMachine(){
HappyMachine happyMachine=new HappyMachine();
happyMachine.setMachineName("小爱同学");
return happyMachine;
}

@Bean
public HappyComponent happyComponent(HappyMachine happyMachine){
HappyComponent happyComponent= new HappyComponent();
happyComponent.setHappyMachine(happyMachine);
return happyComponent;
}

   以后配置类肯定不止一个。在总的配置类里添加 @Import(class),在低版本(4),被导入类必须添加 @Configuration 注解;高版本可省略,相当于自动添加 @Configuration

   有时不是所有的 bean 要立马注入。@Conditional 是 Spring4 新提供的注解, 能够根据一定的条件进行判断,满足条件就给容器注入 bean。可以放到类和方法上。传入一个实现了 Condition 接口的类。这个接口是一个断言接口 boolean matches(Condition Contextcontext,AnnotatedTypeMetadata metadata);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyConditional implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取 ioc 使用的 beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//获取类加载器
ClassLoader classLoader = context.getClassLoader();
//获取 bean 定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
//获取当前环境信息
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return osName.contains("Windows");

}
}

三种方式的配置总结

   XML 方式:

  • 所有内容写到 xml 格式配置文件中
  • 声明 bean 通过 bean 标签
  • < bean 标签包含基本信息(id, class)和属性信息 property name value/ref
  • 引入外部的 properties 文件可以通过 context: property-placeholder
  • loC 具体容器实现选择 ClassPathXmlApplicationContext 对象

   XML+注解方式:

  • 注解负责标记 loc 的类和进行属性装配
  • xml 文件依然需要,需要通过 < context: com nponent-scan 标签指定注解范围
  • 标记 loC 注解:@Component @Service @Controller @Repository
  • 标记 Dl 注解:@Autowired @Qualifier @Resource @Value
  • loc 具体容器实现选择 ClassPathXmlApplicationContext 对象

   完全注解方式:

  • 完全注解方式指的是去掉 xml 文件,使用配置类+注解实现
  • xml 文件替换成使用@Configuration 注解标记的类
  • 标记 loC 注解:@Component @Service @Controller @Repository
  • 标记 Di 注解:@Autowired @Qualifier @Resource @Value
  • < context: component-scan 标签指定注解范围使用@ComponentScan(basePackages = {" com.atguigu.components "})替代
  • < context: property-placeholder 引入外部配置文件使用@PropertySource({" classpath: application.properties “,” classpath: jdbc.properties "})替代

轻松测试配置

   好处:不需要自己创建 IOC 容器对象了;任何需要的 bean 都可以在测试类中直接享受自动装配。

   导入依赖:junit-jupiter-api 和 spring-test

   使用:

1
2
@SpringJUnitConfig(locations={"classpath:spring-context.xml"}) //指定配置文件 xml
@SpringJUnitConfig(value={BeanConfig.class}) //指定配置类

Spring AOP 面向切面编程

  • OOP:Object Orinted Programming:面向对象编程,封装、继承、多态
    • 继承强调的是纵向的扩展
  • Aspect Orinted Programming:面向切面编程
    • 对 OOP 作了一种补充
    • 横切强调的是横向的扩展
    • 代理设计模式

   比如,大量的方法要用类似的日志,可以使用代理模式(静态代理、动态代理)。

   动态代理技术分类:JDK 动态代理、cglib。

  • JDK 动态代理:JDK 原生的实现方式,需要被代理的目标类 必须实现接口,他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class ProxyFactory<T> {
    private T target;

    public ProxyFactory(T target) {
    this.target = target;
    }

    public T getProxy() {
    //参数目标类的类加载器 目标类实现的所有接口!代理对象要执行的代码过程
    T proxy = (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new MyInvocationHandler());
    return proxy;
    }
    public class MyInvocationHandler implements InvocationHandler{
    //Method 目标对象的方法 args 目标方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(method.getName() + "方法执行了,入参是" + Arrays.toString(args));
    Object result = method.invoke(target, args);
    System.out.println(method.getName() + "方法执行结束了,出参是" + result);
    return result;
    }
    }
    }

    使用方式是首先你有一个接口,有一个这个接口的实现类,将这个类的对象传入代理工厂,通过工厂获取代理对象,代理对象执行自己要执行的方法就行。

  • cglib:通过继承被代理的目标类实现代理,所以不需要自标类实现接口! (认干爹)

   通过上面的例子,发现代理需要自己编写代理工厂。

   AOP 思想主要的应用场景:日志记录、事务处理、安全控制、性能监控、异常处理、缓存控制、动态代理。

   SpringAOP 框架,基于 AOP 编程思维,封装动态代理技术,简化动态代理技术实现的框架。

Spring基于注解的AOP

注解层:AspectJ:早期的 AOP 实现的框架,SpringAOP 借用了 AspectJ 中的 AOP 注解。导入 spring-aspects。

实现层:spring-aop:不用直接导,在 context 里自带。

   在前面,有一个接口和一个实现该接口的类。以简单日志切入为例子。新建一个切面类, 必须以 Aspect 结尾,并使用 @Aspect 注解。 当让要使 spring 管理,要加到容器里,所以需要 @Component。要切入,则需要知道切入点。对于一个方法,显然有方法执行前、方法执行后、方法正确执行、方法异常执行,对应的注解是 @Before@After@AfterReturning@AfterThrowing(所在的方法可叫做通知 Advice,前置、后置等等),对应的主要参数是切入点表达式 execution(public void com.atguigu.aop.AImpl.*()),包含了切入方法的权限修饰符、方法返回值类型,包路径+类名+方法名+参数,有一种声明方法的感觉。在正确执行后,可以通过 returning 属性指定返回值名,并在切入点方法里声明同样名字的参数。在异常执行时,可以通过 throwing 属性指定异常变量名,并在方法里声明同样名字的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Component
@Aspect
public class LogAspect {
//切入点表达式:方法修饰符 返回值类型 包名路径和类名.方法名(参数)
@Before("execution(public void com.atguigu.aop.AImpl.*())")
public void beforeAdvice(JoinPoint joinPoint){
//joinPoint 代表的就是切入的方法。
System.out.println("joinPoint.getArgs() = " + Arrays.toString(joinPoint.getArgs()));
System.out.println("joinPoint.getKind() = " + joinPoint.getKind());
System.out.println("joinPoint.getSignature() = " + joinPoint.getSignature());
System.out.println("joinPoint.getSignature().getDeclaringType() = " + joinPoint.getSignature().getDeclaringType());
System.out.println("joinPoint.getSignature().getDeclaringTypeName() = " + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("joinPoint.getSignature().getModifiers() = " + joinPoint.getSignature().getModifiers());
System.out.println("joinPoint.getSignature().getName() = " + joinPoint.getSignature().getName());
System.out.println("joinPoint.getSourceLocation() = " + joinPoint.getSourceLocation());
System.out.println("joinPoint.getStaticPart() = " + joinPoint.getStaticPart());
System.out.println("joinPoint.getTarget() = " + joinPoint.getTarget());
System.out.println("joinPoint.getThis() = " + joinPoint.getThis());
System.out.println("前置通知执行了");
}
@After("execution(public void com.atguigu.aop.AImpl.*())")
public void afterAdvice(){
System.out.println("后置通知执行了");
}
//方法正确执行后,正确返回结果,执行该通知。在后置之前。
//不管有没有异常,前置和后置都会执行。
//出现异常,AfterReturning 不执行
@AfterReturning(value = "execution(public void com.atguigu.aop.AImpl.*())", returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
System.out.println("result = " + result);
System.out.println("正确执行通知执行");
}
//在后置之前,异常发生时
@AfterThrowing(value = "execution(public void com.atguigu.aop.AImpl.*())",throwing = "exception")
public void afterThrowing(Exception exception){
System.out.println("exception.getMessage() = " + exception.getMessage());
System.out.println("异常通知执行");
}

}

   @Before 在方法执行前、@After 方法执行结束后、@AfterReturning 方法正确执行拿到返回值,在结束前、@AfterThrowing 方法异常时,且在在结束前。

说明:

  • 切入点表达式里,权限修饰符和返回值类型可以用 一个 * 表示,也可 单独返回值 类型用 * 表示

  • 包路径可固定写,也可将某一层用 * 表示,也可以用 com.. 表示 com 下所有层,也可用 *.. 表示所有层

  • 类名可用 * 表示,类名的部分也可用 * 表示,*..* 表示任意包任意类

  • 方法名也用类名的规则

  • 参数没有用空括号表示,参数是有序的,任意参数用 (..) 表示,也可组合部分任意

  • 切入点表达式重用,写一个空方法,在上面用 @Pointcut 注解,将切入点表达式作为属性,其他地方调用就是直接将原先的表达式换做方法名+括号。支持跨类调用,例:@Before("包.类.方法名"),因此可单独写一个类来写切入点表达式。

    1
    2
    3
    4
    5
    6
    7
    @Pointcut("execution(*  *..AImpl.*())")
    public void pointCut(){}

    @After("pointCut()")
    public void afterAdvice(){
    //代码
    }

   JoinPoint 接口可以获取方法签名、传入的实参等信息,使用时,需要在切入方法参数里声明。

   还有一个通知,包括了前 4 种的功能,对应 try…catch…finally 结构。@Around

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Around("execution(*  *..AImpl.*())")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("前置通知执行了");
//执行目标方法
Object result = proceedingJoinPoint.proceed();
System.out.println("正确执行通知执行");
} catch (Throwable e) {
System.out.println("异常通知执行");
throw new RuntimeException(e);
} finally {
System.out.println("后置通知执行了");
}
}

   当有多个切面时(字母顺序),通过 @Order 注解调整优先级,默认为最大值,越小优先级越大。

   简单了解一下用 xml 实现 aop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 创建目标类对象-->
<bean id="a" class="com.atguigu.aop.AImpl"/>
<!-- 创建切面类对象-->
<bean id="logAspect" class="com.atguigu.aop.anno.LogAspect"/>
<!-- aop配置-->
<aop:config>
<!-- 共用切入点表达式-->
<aop:pointcut id="pc" expression="execution(* *..AImpl.*(..))"/>
<aop:aspect ref="logAspect" order="1">
<aop:before method="beforeAdvice" pointcut-ref="pc"/>
<aop:after method="afterAdvice" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc" throwing="exception"/>
<aop:around method="aroundAdvice" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
</beans>

Spring 声明式事务

  • 概念:是一个原子操作,是一个最小的执行单元。
  • 四大特性:
    • A:原子性:表示事务是一个整体,要么都成功,要么都失败
    • C:一致性:数据要么是事务成功之后的状态,要么是回滚到之前的状态
    • I:隔离性:多个事务并行时,事务之间互不干扰。A 事务只能读取 A 事务修改之前或修改之后的数据。同
      时,B 事务所做的操作对 A 不产生影响
    • D:持久性:事务提交了,对数据库的数据产生的影响是永久性的
  • MySQL 的四大隔离级别:
    • 读未提交:A 事务可以读取到 B 事务已修改但未提交的数据。
    • 读已提交:A 事务可以读取到 B 事务已修改且已提交的数据。未提交的,读取不到。
    • 可重复读( MySQL 的默认隔离级别 ):A 事务只能读取 A 事务开始之后的数据,不会被其他事务所影响。
    • 串行化:A 事务操作时,其他事务均不可操作。加锁:表锁、行锁、列锁。
  • JDBC 中如何操作事务
    1. 关闭当前连接的自动提交:connection.setAutocommit(false)
    2. 提交事务:connection.commit()
    3. 回滚事务:connection.rollback()
  • Spring 认为事务是一种非核心功能。站在核心功能的角度,事务就属于非核心功能
  • Spring 支持两种事务
    • 编程式事务:事务的代码和业务的核心代码,写在一起,事务靠编码实现(耦合度高、代码分散、冗余,不利于维护)
    • 声明式事务:完全利用 AOP 的思想,前置、返回、异常通知(解决了编程式事务的问题,仅需要一个注解,剩余的工作内容全权交由 Spring 处理)

JDBCTemplate 使用

   Spring 封装了很多「Template」形式的模板类。例如:RedisTemplate,RestTemplate 等等。

准备

   在之前的基础(spring-context、junit-jupiter-api、spring-test)上添加一下依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.6</version>
</dependency>
</dependencies>

   创建数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DROP TABLE IF EXISTS `students`;
CREATE TABLE `students` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`gender` varchar(10) NOT NULL,
`age` int(11) DEFAULT NULL,
`classes` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of students
-- ----------------------------
INSERT INTO `students` VALUES ('1', '张三', '男', '20', '高中一班');
INSERT INTO `students` VALUES ('2', '李四', '男', '19', '高中二班');
INSERT INTO `students` VALUES ('3', '王五', '女', '18', '高中一班');
INSERT INTO `students` VALUES ('4', '赵六', '女', '20', '高中三班');
INSERT INTO `students` VALUES ('5', '刘七', '男', '19', '高中二班');
INSERT INTO `students` VALUES ('6', '陈八', '女', '18', '高中一班');
INSERT INTO `students` VALUES ('7', '杨九', '男', '20', '高中三班');
INSERT INTO `students` VALUES ('8', '吴十', '男', '19', '高中二班');

   创建学生类、jdbc 配置文件、spring 配置文件。

使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@SpringJUnitConfig(locations = "classpath:spring.xml")
public class JdbcTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test(){
// String sql = "insert into students (name, gender, age, classes) values(?,?,?,?)";
// Object [] args = {"萧十一", "男", "55", "幼儿园大班"};
// int update = jdbcTemplate.update(sql, args);
// System.out.println("update = " + update);

// String sql = "update students set age = ? where id = ?";
// int update = jdbcTemplate.update(sql, 40,2);
// System.out.println("update = " + update);

// String sql = " select count(*) from students ";
// Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
// System.out.println("count = " + count);

// String sql = "select id, name, gender, age, classes from students where id =?";
// RowMapper <Student> rowMapper = new BeanPropertyRowMapper <>(Student.class);
// Student student = jdbcTemplate.queryForObject(sql, rowMapper, 1);
// System.out.println("student = " + student);

String sql = "select id,name,gender,age,classes from students where age>?";
RowMapper<Student> rowMapper = new BeanPropertyRowMapper<>(Student.class);
List<Student> studentList = jdbcTemplate.query(sql, rowMapper, 10);
studentList.forEach(s-> System.out.println(s));
}
}

声明式事务

  • Spring 声明式事务对应依赖(只导 spring-jdbc 即可,它包含了 spring-tx。spring-orm 没学到)

    • spring-tx:包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
    • spring-jdbc:包含 DataSource 方式事务管理器实现类 DataSourceTransactionManage
    • spring-orm:包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa 等
  • Spring 声明式事务对应事务管理器接口

    Spring声明式事务对应事务管理器接口
  • DataSourceTransactionManage 主要方法

    • doBegin():开启事务
    • doSuspend():挂起事务
    • doResume():恢复挂起的事务
    • doCommit():提交事务
    • doRollback():回滚事务

xml 配置声明式事务

   xml 配置里前面几乎和之前的一样,主要是后几个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <context:component-scan base-package="com.atguigu.jdbc.tx"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverclassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务的注解支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Repository
public class StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;

public void updateNameByID(String name ,Integer id){
String sql = "update students set name = ? where id = ?";
int rows = jdbcTemplate.update(sql, name, id);
}

public void updateAgeById(Integer age ,Integer id){
String sql = "update students set age = ? where id = ?";
int rows = jdbcTemplate.update(sql, age, id);
}

}
/***** **** **** **** **** **** **** **** ********/
@Service
public class StudentServices {
@Autowired
private StudentDao studentDao;

@Transactional
public void changeInfo(){
studentDao.updateNameByID("大一", 1);
System.out.println("---------");
// int i = 1/0;
studentDao.updateAgeById(180,1);
}

}
/***** **** **** **** **** **** **** **** **** *****/
@SpringJUnitConfig(locations = "classpath:spring.xml")
public class TxTest {
@Autowired
private StudentServices studentServices;
@Test
public void test(){
studentServices.changeInfo();
}
}
  • 如果注入的事务管理器 id 叫 transactionManager,那么 tx:annotation-driven 就不用写 transaction-manager="transactionManager",因为它是默认值

配置类配置声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Configuration
@ComponentScan(basePackages = {"com.atguigu.jdbc.tx"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
public class SpringConfig {
@Bean
public DruidDataSource dataSource(@Value("${jdbc.driverClassName}") String driverClassName,
@Value("${jdbc.url}") String url,
@Value("${jdbc.username}") String username,
@Value("${jdbc.password}") String password){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}

@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
return dataSourceTransactionManager;
}
}
  • @EnableTransactionManagement 注解就是 <tx:annotation-driven transaction-manager="transactionManager"/>
  • @EnableTransactionManagement 没有进行属性赋值,是因为使用默认值

@Transactional 注解

  • 定义在类上,意味着该业务类中的所有方法都有事务

  • 局部覆盖全局

  • 只读 @Transactional(readonly= true)

  • 超时(超时回滚,释放资源)timeout = -1:永不超时。单位秒

  • 事务异常:默认只回滚运行时异常,编译(检查)异常不回滚。例子:rollbackFor = FileNotFoundException.classnoRollbackFor = ArithmeticException.class

  • 事务隔离级别:isolation = Isolation.DEFAULT,我:一般不写不改

  • 事务传播行为:默认 propagation = Propagation.REQUIRED

    名称 含义
    REQUIRED
    默认值
    当前方法必须作在事务中
    如果当前线程上有已经开启的事务可用,那么就在这个事务中运行
    如果当前线程上没有已经开启的事务,那么就自己开启新事务,在新事务中运行
    所以当前方法有可能和其他方法共用事务
    在共用事务的情况下:当前方法会因为其他方法回滚而受连累
    REQUIRES_NEW 当前方法必须工作在事务中
    不管当前线程上是否有已经开启的事务,都要开启新事务
    在新事务中运行
    不会和他万法共用事务,避免被其他方法连累

    举例 :①在 StudentServices 里有两个有事务的方法,changeName 和 changeAge,在 TopServices 里有一个有事务的方法,changeInfo。在测试里调用 changeInfo,只要有异常就会都回滚,用的同一个。②将 changeName 和 changeAge 的事务的 propagation 改为 REQUIRES_NEW,就会各管各的。

    注意: REQUIREDREQUIRES_NEW,在一个 REQUIRED 事务方法里先调用具有 REQUIRED 的方法,再调用具有 REQUIRES_NEW 的方法。当具有 REQUIRES_NEW 的方法出异常后,会一直等待

    • Propagation.NESTED:如果当前存在事务,则在该事务中嵌套一个新事务,如果没有事务,则与 Propagation.REQUIRED 一样
    • Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务方式执行
    • Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,挂起该事务
    • Propagation.MANDATORY:必须在一个已有的事务中执行,否则抛出异常
    • Propagation.NEVER:必须在没有事务的情况下执行,否则抛出异常

将事务注解放到 xml 文件

   主要利用 AOP 思想,所以要记得导包。

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!--    开启组件扫描-->
<context:component-scan base-package="com.atguigu.jdbc.tx.xml"/>
<!-- 导入外部配置-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- JDBCTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置声明式事务-->
<!-- 1. 创建事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>

<tx:method name="save*" read-only="false" />
<tx:method name="change*" read-only="false" rollback-for="java.io.FileNotFoundException" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRES_NEW"/>
<tx:method name="updata*" read-only="false"/>
<tx:method name="delete*"/>
<!-- 兜底-->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!-- 2. 利用AOP将事务增强切入到指定位置-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* *..*Services.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>

补充(本地视频2021年,视频内部电脑2017年)

  • 有参构造器赋值,在bean标签内部

    1
    <constructor-arg name="参数名" value="值" type="参数类型" index="第几个参数">此处可赋值集合</constructor-arg>
  • p名称空间赋值,在bean标签上写p:属性名/属性名-引用="值",复杂的值可以在外面创建,然后引用,比如list集合可以用<util:list>标签创建

    1
    2
    3
    4
    5
    6
    7
    8
    <util:list id="strs">
    <!-- 再嵌套一层list标签就重List变成ArrayList-->
    <value></value>
    <value></value>
    <value></value>
    <value>身体</value>
    </util:list>
    <bean id="HappyComponent03" class="com.atguigu.xml.pojo.HappyComponent" p:happyMachine-ref="HappyMachine" p:componentName="开心的组件" p:stringList-ref="strs">
  • 赋值为null,在property里用null标签

  • Properties赋值用<props>标签

    1
    2
    3
    4
    5
    6
    <property name="propertis">
    <props>
    <prop key=""></prop>
    <prop key=""></prop>
    </props>
    </property>
  • 级联属性赋值:属性的属性<property name="car.price" value="9"></property>

  • 继承实现bean配置信息重用,在bean标签上添加parent属性=beanId,不是真继承

  • 只能用来被继承(抽象),不能获取对象abstract="true",写在bean标签上

  • 改变创建顺序,bean标签上添加属性depends-on="person,book",在创建时会先创建person和book

  • 作用域 scope=prototype多例/singleton单例/request同一次请求创建一个Bean/session同一次会话创建一个Bean

  • 静态工厂,在配置工厂时指定factory-method="静态工厂方法名",就会拿到造的对象(注入工厂,得产品:一个)

    实例工厂,造对象时指定factory-bean="工厂id"factory-method="工厂方法名"(造产品,用工厂:两个)

    前面记的FactoryBean

  • Spring Expression Language:#{表达式/其他bean.属性/其他bean/静态调用T(静态全类名).静态方法(参数)/非静态方法bean.方法名}

  • 泛型依赖注入:

    • 两个不同的实体
    • BaseDao<T>定义了增删改查方法,不注入容器。两个不同的Dao继承并指定不同实体类型,实现对应方法,注入容器
    • BaseService<T>不注入容器,注入BaseDao,实现一个方法调用BaseDao里定义的方法。两个不同的Service继承并指定不同实体类型吗,不重写方法,注入容器
    • ioc容器根据不同的Service的类型获取bean,调用方法

SpringMVC

简介

   SpringWebMVc 是基于 ServletAPi 构建的原始 Web 框架,从一开始就包含在 SpringFramework 中。

  • Spring 家族原生产品, 与 IOC 容器等基础设施无缝对接
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 为组件化程度高插拔式组件即插即用,想要什么功能配置相应组件即可
  • 性能卓著,尤其适合现代大型,超大型互联网目要求

SpringMVC 的作用主要覆盖的是表述层,例如:请求映射、数据输入、视图界面、请求分发、表单回显、会话控制、过滤拦截、异步交互、文件上传、文件下载、数据校验、类型转换等等等

处理请求流程简略版
  • DispatcherServlet:SpringMVc 提供,需要使用 web.xml 配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发
  • HandlerMapping:SpringMVC 提供,需要进行 IOC 配置使其加入 IOC 容器方可生效,它内部缓存 handler(controller)方法和 handler(controller)访问路径数据,被 DispatcherServlet 调用,用于查找路径对应的 handler(controller)
  • HandlerAdapter:SpringMVC 提供,需要进行 IOC 配置使其加入 IOC 容器方可生效。它可以处理请求参数和处理响应数据数据,每次 DispatcherServlet 都是通过 handlerAdapter 间接调用 handler(controller),他是 handler(controller)和 DispatcherServlet 之间的适配器
  • Handler:handler 又称处理器,他是 controller 类内部的方法简称,自已定义,用来接收参数,向后调用业务,最终返回响应结果
  • ViewResovler:SpringMVC 提供,需要进行 IOC 配置使其加入 IOC 容器方可生效。视图解析器主要作用简化模版视图页面查找的。相对其他的组件不是必须的

体验

  1. (默认已经是 web 项目了)导包:

    因为 spring-webmvc 依赖了其他包,所以导它即可。

    因为要用 servlet,但新的 servlet 在 jakarta 里,不在 javax。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.0.6</version>
    </dependency>
    <dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-web-api</artifactId>
    <version>9.1.0</version>
    </dependency>
    </dependencies>

    通过查看 HttpServlet 源码,发现

    1
    2
    3
    4
    5
    6
    7
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

    网络解答

  2. 编写 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!--	1. 注册DispatcherServlet-->
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 让spring创建HandlerMapping等,所以要指定springmvc的配置文件,进行IoC容器的创建-->
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!-- 因为DispatcherServlet的组件只有一个,tomcat一启动就创建-->
    <load-on-startup>1</load-on-startup><!-- 放到init-param前面就标红-->
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!-- 拦截所有请求,除了.jsp-->
    <url-pattern>/</url-pattern>
    </servlet-mapping>
  3. 编写 spring 配置文件

    1
    2
    3
    4
    5
    6
    7
    <!--   开启注解扫描-->
    <context:component-scan base-package="com.atguigu.quick"/>
    <!-- 配置HandlerMapping、HandlerAdapter、ViewResolver-->
    <!-- 方案一、自己配-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
    <!-- 方案二、自己不配,默认全配了-->
  4. 编写 Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Controller
    public class HelloController {

    @ResponseBody//不走页面,直接给前端
    @RequestMapping("/hello")
    public String hello(){
    return "Hello";
    }
    }
  5. 添加 tomcat

    必须要用 10 以上。

    坑点: 我没 10,下载,改控制台日志编码为 GBK,修改部分环境变量。启动,报错。

    1
    jakarta.json.bind.JsonbException: JSON Binding provider org.eclipse.yasson.JsonBindingProvider not found

    解决: 添加 org.eclipse 的 yasson 依赖

  6. 运行,浏览器访问 localhost: 8080/hello

@RequestMapping

   请求映射的路径,将路径和方法存储到 HandLerMapping 里,便于 DispatcherServlet 查找。开头的 / 可省略,SpringMVC 自动补全。

  • 精确匹配
    • 单层路径
    • 多层路径
    • 多个路径(我:相当于别名)
  • 模糊匹配(不影响精确匹配)
    • 单层路径:/user/*
    • 多层路径:/user/**
    • 指定层:/user/*/* 不向下兼容
  • 类上写,则表示该类的统一路径前缀
  • 限制请求方式:默认所有请求方式,可通过 method 属性修改 method = RequestMethod.PosT,不允许则 405
  • 请求进阶:非常明确一个方法要以什么请求方式请求时。可以使用以下注解 GetMappingPostMappingPutMappingDeleteMapping,只能写一个

@RequestParam

   @ReguestParam(value=参数名 required=是否为必传,defaultValue=不传,默认值是什么)。soutp 直接打印方法的所有参数。(当然你也可以只写参数,不写注解,只要参数名对上就赋值,对自定义对象也同理)

  • 前后端参数名不一致时,可以进行指定映射
  • 参数是否必传,默认写上注解就必传了
  • 当不传递时,可以赋予指定默认值
  • 如果必传, 但是前端没传,会报 400 错误,必要的参数未提供
  • 一名多值,用 List 接收

路径传参@PathVariable

1
2
3
4
5
@GetMapping("/{id}")
@ResponseBody
public String showUserByID(@PathVariable("id") Integer id){
return id;
}
  • 隐藏数据的 key 名,保护数据安全
  • RESTful 风格开发
  • 当然,参数名和占位符一致,注解可不指定名字

接收 JSON 数据@RequestBody

   @RequestBody 注解来将 JSON 数据转换为 Java 对象,@ReguestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。避免 415 错误,做如下操作。

   添加依赖

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>

   装配 jackson 转换器

1
2
<!-- 装配HandlerAdapter 装配HandlerMapping 装配jackson转换器 -->
<mvc:annotation-driven/>

   使用了上述标签,则不用自己注入 HandlerAdapterHandlerMapping,尤其是 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

获取 cookie@CookieValue

   cookie 可以手动创建,也可以通过 session 触发创建。

1
2
3
4
5
6
7
8
9
10
11
12
@ResponseBody
@RequestMapping("/cookie/set")
private String setCookie(HttpSession session){
System.out.println("session.getId() = " + session.getId());
return "set cookie ok";
};
@ResponseBody
@RequestMapping("/cookie/get")
private String getCookie(@CookieValue("JSESSIONID") String sessionID){
System.out.println("sessionID = " + sessionID);
return "get cookie ok";
};

    通过 setCookie 方法传入 HttpSession,触发创建 cookie。通过 @CookieValue("JSESSIONID") 注解获取 cookie 里 JSESSIONID 的值。

获取请求头

使用 @RequestHeader 注解将请求标头绑定到控制器中的方法参数。(我:可以得到一些信息,可以区分用户等)

使用原生 API

   从前面的获取 cookie 时,传入的 HttpSession 就可得到一些感觉。

部分控制器参数 描述
jakarta.servlet.ServletRequest
jakarta.servlet.ServletResponse
请求/响应对象
jakarta.servlet.http.HttpSession 强制存在会话。因此,这样的参数永远不会为 nul1
java.io.Inputstream
java.io.Reader
用于访问由 ServletAPI 公开的原始请求正文
java.io.Outputstream
java.io.Write
用于访问由 ServletAPI 公开的原始响应正文
java.util.Map
org.springframework.ui.Model
org.sprinaframework.ui.ModelMap
共享域对象,并在视图呈现过程中向模板公开
Errors
BindingResult
验证和数据绑定中的错误信息获取对象

跳转 jsp

   跳转的方法不要加 @RespondBody,并且方法返回一个视图名。在参数里传入一个 Model,就可以设置一些信息,在 jsp 页面就可以通过 ${requestscope.XXX} 获得。

   添加依赖

1
2
3
4
5
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>

   添加视图解析器

1
2
3
4
5
6
7
8
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewclass" value="org.springframework.web.servlet.view.JstlView"/>
<!-- 逻辑视图的前缀 -->
<property name="prefix" value="/WEB-INF/views/"/>
<!-- 逻辑视图的后缴 -->
<property name="suffix" value=".jsp"/>
</bean>
<mvc:annotation-driven/>

转发和重定向

   上面的视图跳转是转发。返回 "forward:/返回页面的方法的路径" 类似的值,就可以转发。重定向(新的请求,故原先的数据拿不到)的话,就使用 redirect: 前缀。

返回 JSON

   之前体验过,在方法上添加 @ResponseBody 注解,然后将返回值类型改为你想要的返回的类型。

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
  • 返同值类型是 String: 直接序列化字符串响应给前端
  • 返回值类型是引用类型、集合,序列化成 JSON
  • @ResponseBody 可以写在类上,如果这样干,可以把 @Controller 去掉,写 @RestControlLer。这个注解包含前两个注解

返回静态资源

   使用的 DispatcherServlet 拦截所有请求(除了 jsp),请求的静态资源,它找不到 404。所以装配和 tomcat 类似的 DefaultServlet。

1
2
<!-- 装配DefaultServlet-->
<mvc:default-servlet-handler/>

RESTful 风格设计

RESTFUL 到底是什么?你真的了解吗?

@CrossOrigin 注解可以实现跨域。

异常处理

异常

   在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如@Throws 或@ExceptionHandler),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。

1
2
3
4
5
6
7
8
9
10
11
@ControllerAdvice
//@RestControllerAdvice 同理 =@ControllerAdvice+@ResponseBody
public class MyExceptionHandler {

@ResponseBody
@ExceptionHandler(ArithmeticException.class)
public Person handleArithmeticException(ArithmeticException exception){
//可以记录日志等等
return new Person();
}
}

   再加一个对 Exception 的捕获,进行兜底。

拦截器

拦截器图示 过滤器VS拦截器

   先实现拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MyInterceptor implements HandlerInterceptor {
//ctrl + i
/*
在目标方法执行之前执行
返回 true,放行。访问目标资源
返回 false,则拦截,不能继续访问了
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}

/*
在目标方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

/*
响应的数据准备好了,响应之前,执行该方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

   将拦截器注入并配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
<mvc:interceptors>
<!-- 拦截规则1:所有的controller都被拦截-->
<bean class="com.atguigu.quick.interceptor.MyInterceptor"/>
<!-- 拦截规则2:指定路径、配置拦截器-->
<mvc:interceptor>
<!-- 拦截路径:1./login/* 2./login/** 3./*/*/* -->
<mvc:mapping path="/login/admin"/>
<!-- 排除一部分路径:-->
<mvc:exclude-mapping path="/login/exit"/>
<bean class="com.atguigu.quick.interceptor.MyInterceptor"/>
</mvc:interceptor>
<!-- 多个拦截器依次根据前一个拦截器情况执行,执行通过的一定会执行afterCompletion释放资源-->
</mvc:interceptors>
执行流程

数据校验

   为了能够让业务逻辑层基于正确的数据进行处理,在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
</dependency>

   在 controler 方法里的参数上使用注解 @Validated(配置一下:<mvc:annotation-driven/>),然后对应参数类里有如下注解。在使用了 @Validated 的参数后面紧跟着,传入 BindingResult 就可以获取错误信息进行判断了。

注解 说明
@Null
@NotNull
@AssertTrue 搭配 @NotNull,标注值必须为 true
@AssertFalse 搭配 @NotNull
@Min(value) 搭配 @NotNull 常搭配整数
@Max(value) 搭配 @NotNull 常搭配整数
@DecimalMin(value) 常搭配小数,有时无限接近时,会出问题
@DecimalMax(value) 常搭配小数,有时无限接近时,会出问题
@Size(max,min) 串、集合、数组
@Digits(integer,fratction) 限制数字的位数,整数部分和小数部分
@Past 搭配 @DateTimeFormat(pattern="yyyy-MM-dd") 传入的时间和当前时间比较,过去
@Future 搭配 @DateTimeFormat(pattern="yyyy-MM-dd"),未来
@Pattern(regexp) 正则
@Email 必须有@符,@符前最少一个字,@符后必须有点
@Length @Size(max,min) 全面,@Length 专注
@NotEmpty 必须要有值:字符串不能是 null 和空,集合不能为 null 和空
@Range
@NotBlank 字符串不能为空、不能为 null、不能只包含空格,Hibernate Validator 附加的注解

文件上传下载

上传

   html 准备如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/save/picture" method="post" enctype="multipart/form-data">
昵称:<input type="text" name="nickName" value="龙猫"/><br/>
头像:<input type="file" name="headPicture"/><br/>
背景:<input type="file" name="backgroundPicture"/><br/>
<button type="submit">保存</button>
</form>
</body>
</html>

   导入依赖。

1
2
3
4
5
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>

   装配文件上传解析器。id 必须是 multipartResolver。

1
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

   文件额外设置, 在 web.xml 里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--	1. 注册DispatcherServlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 让spring创建HandlerMapping等,所以要指定springmvc的配置文件,进行IoC容器的创建-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 因为DispatcherServlet的组件只有一个,tomcat一启动就创建-->
<load-on-startup>1</load-on-startup><!-- 放到init-param前面就标红-->
<multipart-config>
<!-- 定义单个上传时所需的最大值,单位为字节-->
<max-file-size>10485760</max-file-size>
<!-- 定义单次请求上传的最大值,单位为字节-->
<max-request-size>20971520</max-request-size>
<!-- 定义内存中存储文件的最大值,超过此大小的文件会写入到硬盘中- -->
<file-size-threshold>5242880</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!-- 拦截所有请求,除了.jsp-->
<url-pattern>/</url-pattern>
</servlet-mapping>

   接收文件的 controller 方法要有 MultipartFile 参数,用来获取信息或者转存等等。(获取项目真实路径 request.getRealPath()Servletcontext 代表项目,servletContext.getReaiPath("/images");

下载

   在 SpringMVC 中, RsponseEntity 是用于表示 HTTP 响应的一个类,它既能设置响应体的内容,也能设置响应头相关的信息。ResponseEntity 可以封装一个 HTTP 响应,包括响应体、响应头和响应状态码等属性,并将其发送回客户端。它提供了一种灵活的方式来表示 HTTP 响应,可以用于处理 RESTfulAPI、文件下载、异常处理等应用场景。例子:

1
2
3
4
5
6
7
8
@GetMapping("/users/{age}")
public ResponseEntity<User> getUser(@PathVariable("age")int age){
User user =new User;
user.setAge(age);
user.setEmail("test");
user.setName("ll");
return ResponseEntity.ok(user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/download/{filename}")
public ResponseEntity<byte[]> downloadfile(@PathVariable("filename") String filename) throws IOException {
String realPath = servletContext.getRealPath("/download");
String filePath = realPath.concat(File.separator).concat(filename);
FileInputStream fileInputStream = new FileInputStream(filePath);
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("content-disposition", "attachment;filename="+ URLEncoder.encode(filename,"utf-8"));

return new ResponseEntity<byte[]>(bytes,httpHeaders, HttpStatus.OK);
}

头大的记忆

初始化流程和请求流程

说是面试要问

执行流程
  1. doDispatch 里通过 getHandler 创建处理器执行链 HandlerExecutionChain,它包含目标 controller 方法和所有拦截器的属性

    我的理解:请求来了,它去寻找对应路径的 controller 方法和能应用的拦截器,将他们包装起来。

  2. 查找匹配的处理器适配器 getHandlerAdapter

  3. 正序执行拦截器的 preHandle 方法 mappedHandler.applyPreHandle(processedRequest, response)

  4. 执行目标方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());,mv 表示 ModelAndView

  5. 倒序执行拦截器的 postHandle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv);

  6. 处理目标执行后的结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);,内部有对 mv 的判断,到底是视图还是 json、string。

    1. 如果有视图,渲染 render(mv, request, response);

    2. 如果没有视图,执行拦截器的 afterCompletion 方法(不对吧?应该不管有没有视图,afterCompletion 都要执行,老师口误了)

      1
      2
      3
      4
      if (mappedHandler != null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null);
      }

      为什么要判断是否 null 呢?我看下来的感觉:此时 mappedHandler 不为 null,不用判断。

ContextLoaderListener 监听器

   DispatcherServlet 加载 spring-mvc.xml,此时整个 Web 应用中只创建一个 IoC 容器。所以想将配置文件分开:

  • SpringMVC 相关:spring-mvc.xml 配置文件
  • Spring 和 Mybatis 相关

   配置文件分开后,都 web.xml 里导入即可。<param-value>classpath:spring-*.xml</param-value>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--	1. 注册DispatcherServlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 让spring创建HandlerMapping等,所以要指定springmvc的配置文件,进行IoC容器的创建-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</init-param>
<!-- 因为DispatcherServlet的组件只有一个,tomcat一启动就创建-->
<load-on-startup>1</load-on-startup><!-- 放到init-param前面就标红-->
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!-- 拦截所有请求,除了.jsp-->
<url-pattern>/</url-pattern>
</servlet-mapping>

   虽然配置文件分开了,但容器还是一个容器(我:为什么能在 web.xml 里加载配置文件,是因为容器先创建,然后配置配置文件,然后刷新。)。所以分两个容器,在 web.xml 里配置如下:

1
2
3
4
5
6
7
8
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- 配置监听器,监听contextConfigLocation-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

   ContextLoaderListener 创建的容器是父容器,DispatcherServlet 创建的是子容器。因为在 DispatcherServlet 的父类 FrameworkServlet 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
原本只由 DispatcherServlet 创建时,rootContext 没有。ContextLoaderListener 创建时,rootContext 有。
*/
WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());

/*
新特性,不用自己再强转了
*/
wac instanceof ConfigurableWebApplicationContext cwac
/*
设置父容器
*/
cwac.setParent(rootContext);

   分成了两个容器,配置文件里注解扫描一个只扫描 controller 等,另一个排除 controller 等。

MyBatis 持久层框架

简介

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

MyBatis 是一个一流的持久化框架,支持自定义 SQL、存储过程和高级映射。MyBatis 几乎省去了所有的 JDBC 代码,也省去了手动设置参数和检索结果的麻烦。MyBatis 可以使用简单的 XML 或 Annotations 进行配置,并将原语、Map 接口和 Java POJO(Plain Old Java Objects)映射到数据库记录中。

持久层框架对比

jdbc 虽然效率高,但开发效率低。

快速入门

  1. 准备数据和 pojo 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    SET FOREIGN_KEY_CHECKS=0;

    -- ----------------------------
    -- Table structure for `t_emp`
    -- ----------------------------
    DROP TABLE IF EXISTS `t_emp`;
    CREATE TABLE `t_emp` (
    `emp_id` int(11) NOT NULL AUTO_INCREMENT,
    `emp_name` varchar(100) NOT NULL,
    `emp_salary` double(10,5) NOT NULL,
    `emp_gender` varchar(5) NOT NULL,
    PRIMARY KEY (`emp_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

    -- ----------------------------
    -- Records of t_emp
    -- ----------------------------
    INSERT INTO `t_emp` VALUES ('1', 'tom', '200', '男');
    INSERT INTO `t_emp` VALUES ('2', 'jerry', '667', '女');
    INSERT INTO `t_emp` VALUES ('3', 'andy', '778', '女');
  2. 编写 Mapper 接口

    1
    2
    3
    public interface EmployeeMapper {
    List<Employee> selectALL();
    }
  3. 编写同名 Mapper 配置文件 EmployeeMapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.atguigu.quick.mapper.EmployeeMapper">
    <select id="selectALL" resultType="com.atguigu.quick.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary,emp_gender empGender from t_emp
    </select>
    </mapper>
  4. 编写 mybatis 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <!-- 设置mybatis运行时要连接数据库的环境-->
    <environments default="MySQL"><!--默认id-->
    <environment id="MySQL">
    <!-- MyBatis的内置事务管理器是JDBC -->
    <transactionManager type="JDBC"></transactionManager>
    <!-- dataSource是配置数据源,type填写POOLED-->
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    </dataSource>
    </environment>
    </environments>
    <!-- 注册Mapper映射文件-->
    <mappers>
    <mapper resource="EmployeeMapper.xml"/>
    </mappers>
    </configuration>
  5. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
        @Test
    public void test() throws IOException {
    // 1. 读取 MyBatis 配置文件
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

    // 2. 构建 SqlSessionFactory 工厂
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 通过 sqlSessionFactory 获取 sqlSession(connection)
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4. 根据接口的类对象,获取接口的实现类对象(动态代理)
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);

    // 5. 以接口的角度调用方法、
    List<Employee> employees = employeeMapper.selectALL();

    // 6. 处理结果
    employees.forEach(System.out::println);

    // 7. 释放资源
    sqlSession.close();

    }

   看起来似乎更麻烦了,包装一下会好一点。看起来配好后几乎只用管 Mapper 配置文件和注册 Mapper 文件。

封装工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SqlSessionUtil {
public static SqlSessionFactory sqlSessionFactory = null;

static {
try {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}

}

加入日志框架

门面(接口、标准):SLF4J
实现:logback

1
2
3
4
5
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

   已经可以用了。如果不满意输出样式,可以创建 logback.xml。不会写,网上找,互联网精神大有人在,尚硅谷谷粒学院学习笔记 3–统一异常处理,日志_尚硅谷 统一异常-CSDN 博客。使用 MyBatis 的日志实现也可以,配置文件里配置标签 <setting name="logImpl" value="SLF4J"/>,注意标签有顺序,查看对应的 dtd 文件即可。

   在类上标记 @Slf4j 注解(lombok 的),就可以在类里使用 log.info("{}",employee); 类上代码。

#{}和${}

#{}PreparedStatement,预编译 SQL 语句,采用 占位符的方式,会在传的值上加上单引号

  • 可以规定一些规则

    javaType、jdbcType、mode(存储过程)、numericScale、resultMap、typeHandler(处理这个数据的类型处理器)、jdbcTypeName、expression(未来准备支持)

    jdbcType通常需要在某种特定的条件下被设置:数据为null,有数据库可能不能识别mybatis对null的处理,比如Oracle。

    因为mybatis对所有的null都映射的是原生Jdbc的OTHETR

    #{email,jdbcType=NULL}或者<setting name="jdbcTypeForNull" value="NULL"/>(other、varchar、null)

${}Statement,拼接 SQL 语句,是将值和 SQL 拼接在一起

总结: 传值用 #{},传关键字或列名、表名、库名等用 ${}

数据输入输出

主键回填

返回主键值(主键回填,数据库功能 SELECT LAST_INSERT_ID();

  • 主键数值且自增 :在 sql 语句的配置标签上加上 useGeneratedKeys="true" keyProperty="empId"

    1
    2
    3
    <insert id="insert" useGeneratedKeys="true" keyProperty="empId">
    insert into t_emp(emp_name,emp_salary,emp_gender) values (#{empName},#{empSalary},#{empGender})
    </insert>
  • 主键不自增(如 UUID,数据库也有这个函数)(我在想万一天选之子,十亿分之一的概论重复了):在 sql 标签里添加

    1
    2
    3
    <selectKey order="BEFORE" resultType="string" keyProperty="empId">
    select replace(uuid(),'-','')
    </selectKey>

数据输入

   Mybatis 默认已开启事务。只有查询写 resultTyperesultType 不想写太长可以在对应类上 @Alias(别名),当然也可将别名配置在配置文件里,在 typeAliases 配:①指定 <typeAlias type="com.atguigu.quick.pojo.Employee" alias="emp"/> ②类名就可以是别名,不区分大小写 <package name="com.atguigu.quick"/>

  • 单个简单类型

  • 实体类对象:虽然方法传入的是对象,但 sql 语句里写对象的属性名

    1
    insert into t_emp(emp_name,emp_salary,emp_gender) values (#{empName},#{empSalary},#{empGender})
  • 零散简单数据:默认 arg0 开始和 parm1 开始。可以使用 @Param 区别名,取别名后不能用 arg 类型

    1
    2
    -- 我不喜欢
    update t_emp set emp_salary = #{param2} where emp_id = #{param1}
    1
    2
    //参数太多了,怎么办?霸道总裁:不准多
    Integer updateEmpSalary(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);

    零散简单数据太多了,怎么办,使用 Map 集合即可 key 就是名字 value 就是值。

数据输出

   不想给列名取别名,可以配置 <setting name="mapUnderscoreToCamelCase" value="true"/> 将下划线名改成驼峰名。

  • 单个实体和简单类型(看入门)

  • 多个实体类型(看入门)

  • Map(多个列 单行,不能封装对象)resultType="map":key 是列名,value 是值(此时,我猜测多列多行用 List<Map> 封装,就是不知道需不需要自己写转换的接口)

  • resultMap:观察如下配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <resultMap id="emp" type="com.atguigu.quick.pojo.Employee">
    <!--id标签维护的是当前查询结果中的主键列-->
    <id column="emp_id" property="empId"/>
    <!--result标签维护的是当前查通结果中的普通列-->
    <!--column是查询结果的列名。property是封装对家的属性名-->
    <result column="emp_name" property="empName"/>
    <result column="emp_salary" property="empSalary"/>
    <result column="emp_gender" property="empGender"/>
    </resultMap>
    <select id="selectByEmpId" resultMap="emp">
    select emp_id empId,emp_name empName,emp_salary empSalary,emp_gender empGender from t_emp where emp_id = ${empId}
    </select>

映射

SQL JOINS

  客户表,订单表。订单表有客户表的id。对应实体类为客户类,订单类。订单类里有客户对象,客户类里有订单集合。(可以先没有外键,测试好再配)

对一映射

   根据订单id查询订单,包含用户。association标签封装单个对象,javaType表示封装类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="OrderAndCustomer" type="Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<!-- 在查询的结果中,维护的是对一的关系。封装的对象是单个对象,用association-->
<association property="customer" javaType="Customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
</association>
</resultMap>
<select id="selectOrderAndCustomerByOrderId" resultMap="OrderAndCustomer">
select order_id,order_name,t_order.customer_id,customer_name from t_order
inner join t_customer
on t_order.customer_id = t_customer.customer_id
weher t_order.order_id = #{orderId}
</select>

如果不想写association标签,可以用级联属性:<result column="order_name" property="customer.orderName"/>

(2021年)利用association标签,上面的查询可以改写进行分步查询。(可用于对多:用户有多个订单)

  1. 只查订单
  2. 有一个方法只查用户
  3. 结果封装:订单的基础属性封装照样。订单的用户属性使用association标签,标签属性select表明当前属性是调用select指定的方法查出的结果,值为mapper接口全类名.方法;标签属性column指定将哪一列(之前查的列)的值传给这个方法,多列可column="{接收传入的名key1=传入的名column1,key2=column2};标签属性fetchType="lazy/eager"表明延不延迟;

(本地2021年的视频)<setting name=" LazyLoadingEnabled " value ="true"/>懒加载:高版本默认为true,低版本默认为flase
(本地2021年的视频)<setting name="aggressiveLazyLoading" value="false"/>开启,属性全部直接加载,关闭,属性按需加载

(本地2021年的视频)鉴别器:我=if成立操作

1
2
3
4
5
6
7
8
<discriminator javaType="string" column="被鉴别的类" >
<case value="值" resultType="封装类型">
封装过程,如association标签、如id、result标签
</case>
<case>
多个case
</case>
</discriminator>

对多映射

  根据用户id查询用户,包含有订单。collection标签维护对多的关系,ofType表示封装类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="CustomerAndOrder" type="CustomerAndOrder">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<collection property="orderList" ofType="Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
</collection>
</resultMap>
<select id="selectCustomerAndOrdersByCustomerId" resultMap="CustomerAndOrder">
select t_customer.customer_id,customer_name,order_id,order_name, from t_customer
inner join t_order
on t_customer.customer_id = t_order.customer_id
weher t_customer.customer_id = #{CustomerId}
</select>

多对多映射

  学生表(id,name),老师表(id,name)。老师教多个学生,学生被多个老师教,中间表(老师id,学生id)。学生类(id,name,teacherList),老师类(id,name)

1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="allStudentAndTeacher" type="Student">
<id column="s_id" property="sId"/>
<result column="s_name" property="sName"/>
<collection property="teacherList" ofType="Teacher">
<id column="t_id" property="tID"/>
<result column="t_name" property="tName"/>
</collection>
</resultMap>
<select id="selectAllStudentAndTeacher" resultMap="allStudentAndTeacher">
select t_student.s_id,t_student.s_name,t_teacher.t_id,t_teacher.t_name from t_student
inner join t_inner on t_stuednt.s_id = t_inner.s_id
inner join t_teacher on t_teacher.t_id = t_inner.t_id
</select>

多表映射总结

  想轻松封装,可以如下配置:autoMappingBehavior,如果配置为FULL,则可以省略idresult 等标签,但是如果省略了id标签,会出小bug。总结:配置为FULL时,要保留id标签和属性相关关系。

1
2
3
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- NONE关闭自动映射,PARTIAL默认单表映射,FULL自动映射任何复杂的结果集(无论是否嵌套)-->
<setting name="autoMappingBehavior" value="FULL"/>

动态SQL

  

  • ifwhere标签

    • if只能判断,没有else。完成传值的基本判断。
    • where标签可以根据标签内部是否有条件,追加where关键字,自动去除前面多余andor
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <select id="selectByCondition" resultType="com.atguigu.quick.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary,emp_gender empGender from t_emp
    <where>
    <if test="empName!=null">
    emp_id = ${empId}
    </if>
    <if test="empName!=null">
    and emp_name = ${empName}
    </if>
    <if test="empSalary!=null">
    and emp_salary = ${empSalary}
    </if>
    <if test="empGender!=null">
    and emp_gender= ${empGender}
    </if>
    </where>
    </select>
  • set标签,类比where

    • set标签标签可以根据标签内部是否有条件,追加set关键字,会自动去除前后多余的
    • 为了保证set没条件时,set关键字不追加,不会导致SQL语句报错,兜底修改必传且且修改无关,如id。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <update id="update">
    update t_emp
    <set>
    -- id必传
    <if test="empName!=null">
    emp_id = ${empId}
    </if>
    <if test="empName!=null">
    , emp_name = ${empName}
    </if>
    <if test="empSalary!=null">
    , emp_salary = ${empSalary}
    </if>
    <if test="empGender!=null">
    , emp_gender= ${empGender}
    </if>
    </set>
    where emp_id = ${empId}
    </update>
  • trim标签(了解)

    • 可以更加灵活编写SQL
    • prefix属性:指定要动态添加的前缀
    • suffix属性:指定要动态添加的后缀
    • prefixOverrides属性:指定要动态去掉的前缀,使用“"分隔有可能的多个值
    • suffixOverrides属性:指定要动态去掉的后缀,使用"”分隔有可能的多个值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <select id="selectByCondition" resultType="com.atguigu.quick.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary,emp_gender empGender from t_emp
    <trim prefix="where" prefixOverrides="and|or" suffixOverrides="and|or">
    <if test="empName!=null">
    and emp_id = ${empId} and --报错
    </if>
    <if test="empName!=null">
    and emp_name = ${empName} and --报错
    </if>
    <if test="empSalary!=null">
    and emp_salary = ${empSalary} and --报错
    </if>
    <if test="empGender!=null">
    and emp_gender= ${empGender} and --报错
    </if>
    </trim>
    </select>
  • choose/when/otherwise标签

    • 在多个分支条件中,仅执行一个。
    • 从上到下依次执行条件判断,遇到的第一个满足条件的分支会被采纳,被采纳分支后面的分支都将不被考虑。
    • 如果所有的when分支都不满足,那么就执行otherwise分支。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <select id="selectByCondition" resultType="com.atguigu.quick.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary,emp_gender empGender from t_emp
    where
    <choose>
    <when test="empName!=null">emp_name = ${empName} </when>
    <when test="empSalary!=null">emp_name = ${empSalary} </when>
    <when test="empGender!=null">emp_name = ${empGender} </when>
    <otherwise>1=1</otherwise> -- 不这写,只是展示。改where标签就可
    </choose>
    </select>
  • foreach标签

    • collection:要遍历的集合或数组的名称或类型

    • item:从集合或数组中每次循环取出来的值要存储的变量名

    • separator:分隔符

    • open="(":循环开始添加

    • close=")":循环结束添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <select id="selectById" resultType="com.atguigu.quick.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary,emp_gender empGender from t_emp
    where
    emp_id in --(
    <foreach collection="array" item="id" separator="," open="(" close=")">
    #{id}
    </foreach>
    --)
    </select>
    1
    2
    3
    4
    5
    6
    7
    <insert id="insert" >
    insert into t_emp(emp_name,emp_salary,emp_gender)
    values
    <foreach collection="list" item="emp" separator=",">
    (#{emp.empName},#{emp.empSalary},#{emp.empGender})
    </foreach>
    </insert>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <update id="update"> -- 报错 默认一次连接 发送一次sql语句。将url上添加?allowMultiQueries=true
    <foreach collection="list" item="emp" separator=";">-- 应该可以
    update t_emp
    <set>
    -- id必传
    <if test="empName!=null">
    emp_id = ${emp.empId}
    </if>
    <if test="empName!=null">
    , emp_name = ${emp.empName}
    </if>
    <if test="empSalary!=null">
    , emp_salary = ${emp.empSalary}
    </if>
    <if test="empGender!=null">
    , emp_gender= ${emp.empGender}
    </if>
    </set>
    where emp_id = ${emp.empId}
    </foreach>
    </update>
  • sql片段

    1
    2
    3
    4
    5
    6
    <sql id="sqlBase">
    select t_customer.customer_id,customer_name,order_id,order_name, from t_customer
    </sql>
    <select id="selectALL" resultType="com.atguigu.quick.pojo.Employee">
    <include refid="sqlBase"/>
    </select>

(本地2021年视频)

内置参数

_parameter:代表整个参数,①这个参数②这个map
_databaseId:如果配置了DatabaseIdProvider标签,值为数据库别名,可以用来判读数据库写不同的sql

绑定

<bind name="新变量名可:_原变量名" value="'_'+likestr+'%'"/>,可用于模糊查询,但不太好。

缓存

MyBatis系统中默认定义了两级缓存

  • 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。

    失效情况:

    • SqlSession不同
    • SqlSession相同,查询条件不同
    • SqlSession相同,两次查询之间进行了增删改
    • SqlSession相同,手动清空了
  • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。

    为了提高扩展性。MyBatis定义了缓存接口Cache。可以通过实现Cache接口来自定义二级缓存

    • 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中

    • 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容

    • 不同namespace查出的数据会放在自己对应的缓存中(map)

    • 使用

      1. <setting name="cacheEnabled"value="true"/>

      2. mapper.xml里

        1
        2
        3
        4
        5
        <cache eviction="缓存回收策略,类比408里的一些算法,LRU默认/FIFO/SOFT软引用/WAKE弱引用" 
        flushInterval="缓存刷新间隔,默认不清空,毫秒"
        readOnly="非只读,利用序列化和反序列化的技术克隆一份数据给你"
        size="缓存多少个元素"
        type="指定自定义缓存全类名"></cache>
      3. pojo实现序列化接口

  • 有关设置

    • 查询标签里添加属性useCache="true默认",false不使用缓存(除了一级缓存使用,不使用二级缓存)
    • 增删改标签里添加属性flushCache="true默认",执行完后清除一级、二级缓存
    • SqlSession.clearCache();当前会话的一级缓存
    • localCacheScope本地缓存作用域,SESSION,STATEMENT可以禁用一级缓存
  • 第三方缓存(cache接口)

    • putObject方法放到第三方

    • getObject方法从第三方拿

    • 整合ehcache

      1
      2
      3
      4
      ehcache-core
      slf4j-api
      slf4j-log4j漏洞
      mybatis-ehcache

      excahe.xml配置,网上找。mapper.xml配置缓存实现类

高级

批量注册

1
2
3
4
<mappers>
<!-- package扫描包,包下的接口名就是映射文件的名,映射文件的路径就是resources日录下的路径((接口路径).replaceAll(".","/"))-->
<package name="com.atguigu.quick.mapper"/>
</mappers>

分页PageHelper插件(本质拦截器)

  MyBatis对插件进行了标准化的设计,并提供了一套可扩展的插件机制。插件可以在用手语句执行过程中进行扫
截,并充许通过自定义处理程序来拦截和修改SQL语句、映射语句的结果等。

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.1</version>
</dependency>
1
2
3
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

查询之前分页

1
2
3
4
5
6
7
8
9
//开启分页
PageHelper.startPage(当前页1,页大小3);
//查询
//……
//封装
PageInfo; //对象(有中文注释)
//可继续封装
//返回结果

逆向工程

  ORM(Objectr Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。它将对象和关系数据库的概念进行映射,通过一系列的操作将对象关联到数据表中的一行或多行上。

  MyBatis的逆向工程是一种自动化生成持久层代码和映射文件的工具,它可以根据数据库表结构和设置的参数生
成对应的实体类、Mapper.xml文件、Mapper接口等代码文件,简化了开发者手动生成的过程。逆向工程使开发者
可以快速地构建起DAO层,并快速上手进行业务开发
  MyBatis的逆可工程有两种方式:通过MyBatisGenerator插件实现和通过Maven插件实现。无论是哪种方式,逆
可工程一般需要指定一些配置参数,例收如数据库连接URL、用户名、密码、要生成的表名、生成的文件路等等
总的来说,MyBatis的逆尚工程为程序员提供了一种方便快捷的方式,能够快速地生成持久层代码和映射文件
是半自动ORM思维像全自动发展的过程,提高程序员的开发效率。

注意:逆向工程只能生成单表crud的操作,多表查询依然需要自己编写!

不用:①映射文件里的insert太长了

前面回顾

ssm简易问答

SSM整合

配置名 对应内容 对应容器
spring-mvc.xml controller,springmvc相关 web容器
spring-service.xml service,aop,tx相关 root容器
spring-mapper.xml mapper,datasource,mybatis相关 root容器

配置

  • jdbc.properties

    点击显/隐内容
    1
    2
    3
    4
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/mybatis
    jdbc.username=root
    jdbc.password=123456
  • mybatis-config.xml

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        <settings>
    <setting name="logImpl" value="SLF4J"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
    <package name="com.atguigu.pojo"/>
    </typeAliases>
    <!-- 不用了,使用druid,所以在spring-mapper.xml里-->
    <!-- <environments default="MySQL">-->
    <!-- <environment id="MySQL">-->
    <!-- <transactionManager type="JDBC"></transactionManager>-->
    <!-- <dataSource type="POOLED">-->
    <!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
    <!-- <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>-->
    <!-- <property name="username" value="root"/>-->
    <!-- <property name="password" value="123456"/>-->
    <!-- </dataSource>-->
    <!-- </environment>-->
    <!-- </environments>-->
    <mappers>
    <package name="com.atguigu.mapper"/>
    </mappers>
  • spring-mapper.xml

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!--    mapper,datasource,mybatis相关-->
    <!-- 利用mybatis-springy依赖-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 引入配置文件-->
    <!-- <property name="configLocation" value="classpath:mybatis-config.xml"/>-->
    <!-- 不引入引入配置文件,直接配-->
    <!-- 平替settings -->
    <property name="configuration">
    <!-- 创建Configuration对象,交给SqlSessionFactoryBean,(getObject()),赋值给sqlSessionFactory属性configuration-->
    <bean class="org.apache.ibatis.session.Configuration">
    <property name="logImpl" value="org.apache.ibatis.logging.slf4j.Slf4jImpl"/>
    <property name="mapUnderscoreToCamelCase" value="true"/>
    </bean>
    </property>
    <!-- 平替的是typeAliases+package-->
    <property name="typeAliasesPackage" value="com.atguigu.pojo"/>
    <!-- 平替插件-->
    <property name="plugins">
    <array><!--数组-->
    <bean class="com.github.pagehelper.PageInterceptor">
    <property name="properties">
    <props>
    <!-- 不同关系型数据库分页的sql不同-->
    <prop key="helperDialect">mysql</prop>
    </props>
    </property>
    </bean>
    </array>
    </property>
    <!-- 平替的是mappers标签+package-->
    <property name="mapperLocations" value="classpath:com/atguigu/mapper/*.xml"/>
    <!-- 引入德鲁伊dataSource-->
    <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 使用德鲁伊-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- Spring管理Mapper接口的代理对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.atguigu.mapper"/>
    </bean>
  • spring-mvc.xml

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    <!--    开启组件注解扫描-->
    <context:component-scan base-package="com.atguigu.controller"/>
    <!-- 装配HandlerMapping、HandlerAdapter、Jackson-->
    <mvc:annotation-driven/>

    <!-- 静态资源-->
    <mvc:default-servlet-handler/>
  • spring-service.xml

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!--开启组件注解扫描-->
    <context:component-scan base-package="com.atguigu.services"/>
    <!-- 开启aop支持-->
    <aop:aspectj-autoproxy/>
    <!-- 装配事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
    <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 开启事务注解支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
  • webapp/WEB-INF/wen.xml

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mapper.xml,classpath:spring-service.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

我保存了模板。

代码

  • EmployeeController

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.atguigu.controller;

    import com.atguigu.pojo.Employee;
    import com.atguigu.services.EmployeeService;
    import com.atguigu.utils.R;
    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.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.List;

    @RestController
    @RequestMapping("/employee")
    public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping
    @ResponseBody
    public R showAll(){
    List<Employee> employees = employeeService.showAll();
    return R.ok(employees);
    }
    }
  • EmployeeService

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.atguigu.services;

    import com.atguigu.pojo.Employee;

    import java.util.List;

    public interface EmployeeService {
    List<Employee> showAll();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package com.atguigu.services.impl;

    import com.atguigu.mapper.EmployeeMapper;
    import com.atguigu.pojo.Employee;
    import com.atguigu.services.EmployeeService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.List;

    @Service
    @Transactional
    public class EmployeeServiceImpl implements EmployeeService {

    //怎么注入的呢?动态代理对象程序运行生产的,并不在容器里.
    //spring-mapper.xml里配置MapperScannerConfigurer,将生成的代理对象交给spring
    @Autowired
    private EmployeeMapper employeeMapper;
    @Override
    @Transactional(readOnly = true)
    public List<Employee> showAll() {
    return employeeMapper.selectAll();
    }
    }
  • mapper

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.atguigu.mapper;

    import com.atguigu.pojo.Employee;

    import java.util.List;

    public interface EmployeeMapper {
    List<Employee> selectAll();
    }
    1
    2
    3
    <select id="selectAll" resultType="employee">
    select * from t_emp;
    </select>
  • Employee

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.atguigu.pojo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Employee {
    private Integer empId;
    private String empName;
    private Double empSalary;
    private String empGender;
    }
  • R

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.atguigu.utils;

    public class R {
    private int code = 200;
    private boolean flag = true;
    private Object data;
    public static R ok(Object data){
    R r = new R();
    r.data = data;
    return r;
    }
    public static R fail(Object data){
    R r = new R();
    r.code = 500;
    r.flag = false;
    r.data = data;
    return r;
    }

    public int getCode() {
    return code;
    }

    public void setCode(int code) {
    this.code = code;
    }

    public boolean isFlag() {
    return flag;
    }

    public void setFlag(boolean flag) {
    this.flag = flag;
    }

    public Object getData() {
    return data;
    }

    public void setData(Object data) {
    this.data = data;
    }
    }

部分测试

  • EmployeeMapperTest

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.atguigu.test;

    import com.atguigu.mapper.EmployeeMapper;
    import com.atguigu.pojo.Employee;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.jupiter.api.Test;

    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;

    @Slf4j
    public class EmployeeMapperTest {

    @Test
    public void test() throws IOException {
    // 原生的mybatis写法 产生了sqlSessionFactory(要放到ioc容器)、sqlSession、employeeMapper,
    // 1. 读取配置文件
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 2. 构建SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 获取SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 4. 获取Mapper接口代理对象
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    // 5. 使用
    List<Employee> employees = employeeMapper.selectAll();
    // 6. 处理结果
    employees.forEach(employee -> log.info(employee.toString()));
    // 7. 关闭 释放资源
    sqlSession.close();
    }

    }
  • EmployeeServiceTest

    点击显/隐内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    /**
    * Copyright (C), 2015-2024, px有限公司
    * FileName: EmployeeServiceTest
    * Author: 15081
    * Date: 2024/3/2 17:01
    * Description:
    * History:
    * <author> <time> <version> <desc>
    * px 修改时间 版本号 描述
    */
    package com.atguigu.test;

    import com.atguigu.pojo.Employee;
    import com.atguigu.services.EmployeeService;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

    import java.util.List;

    /**
    * 〈一句话功能简述〉<br>
    * 〈〉
    *
    * @author 15081
    * @create 2024/3/2
    * @since 1.0.0
    */
    @Slf4j
    @SpringJUnitConfig(locations = {"classpath:spring-mapper.xml","classpath:spring-service.xml"})
    public class EmployeeServiceTest {
    @Autowired
    private EmployeeService employeeService;

    @Test
    public void test(){
    List<Employee> employees = employeeService.showAll();
    employees.forEach(employee -> log.info(employee.toString()));
    }
    }

我总结

SSM整合

SpringBoot

简介

Level up your Java™ code

With Spring Boot in your app, just a few lines of code is all you need to start building services like a boss.

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

  • Create stand-alone Spring applications
  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
  • Provide opinionated ‘starter’ dependencies to simplify your build configuration
  • Automatically configure Spring and 3rd party libraries whenever possible
  • Provide production-ready features such as metrics, health checks, and externalized configuration
  • Absolutely no code generation and no requirement for XML configuration

快速入门

  • 依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--    继承springboot父亲工程-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
    </parent>
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    </dependencies>
  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    @RequestMapping("/hello")
    public class HelloController {

    @GetMapping
    public String hello(){
    return "Hello,SpringBoot3";
    }
    }
  • application

    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class HelloApplication {
    public static void main(String[] args) {
    SpringApplication.run(HelloApplication.class,args);
    }
    }
  • 如果配置,application.properties

    1
    2
    server.port=8001
    # 所有的配置可见依赖spring-boot-autoconfigure的spring-configuration-metadata.json文件

分析

  • 依赖如何导入:maven的依赖传递原则

  • 版本号不用写:

    父项目spring-boot-starter-parent,管理的是配置、插件

    父项目的父项目spring-boot-dependencies,管理的依赖及依赖的版本

    父项目版本仲裁中心,把所有常见的jar的依赖版本都声明好了

    不喜欢这个版本号,找到版本名,在子项目里覆盖

  • 缺少依赖,尽可能用父工程,也可以自己导

配置文件

  • application.properties

    所有的配置可见依赖spring-boot-autoconfigure的spring-configuration-metadata.json文件

  • application.yaml/yml

    可以减少一些重复的单词

1
2
3
4
5
6
7
server.port=8001
server.servlet.context-path=/springboot
# 自定义
spring.jdbc.driverClassName=com.mysql.cj.jdbc.Driver
spring.jdbc.url=jdbc:mysql://localhost:3306/mybatis
spring.jdbc.username=root
spring.jdbc.password=123456
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@Data
//导入配置文件已经完成了
public class DataSourceProperties{
@Value("${spring.jdbc.driverClassName}")
private String driverClassName;
@Value("${spring.jdbc.url}")
private String url;
@Value("${spring.jdbc.username}")
private String username;
@Value("${spring.jdbc.password}")
private String password;
}

DataSourceProperties就可以进行@Autowired了。

  • 自定义配置批量注入

    使用@ConfigurationProperties注解指定前缀就可以利用属性名批量注入了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component
    @Data
    @ConfigurationProperties(prefix = "spring.jdbc")
    //导入配置文件已经完成了
    public class DataSourceProperties{
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    }
  • 多环境profile切换配置(开发、测试、预生产、生产)

    • application-dev.yaml 开发

    • application-test.yaml 测试

    • application-prod.yaml 生产

    • application.yaml选择上面的一个进行激活

      1
      2
      3
      spring:
      profiles:
      active: dev

自动配置原理

  • @SpringBootApplication标注的类就是主程序类
  • SpringBoot只会扫描主程序所在的包及其下面的子包,自动的component-scan功能
  • 自定义扫描路径
    • @SpringBootApplication(scanBasePackages="com.atguigu")
    • @ComponentScan("com.atguigu")直接指定扫描的路径
  • 所有能在配置文件中写的配置内容,都是和某 一个XxxProperties属性类进行绑定的
    • ServerProperties绑定了所有Tomcat服务器有关的配置
    • MultipartProperties绑定了所有文件上传相关的配置
  • 按需加载自动配置
    • 导入场景spring-boot-starter-web
    • starter,基础核心starter场景启动器除了会导入相关功能依赖,导入一个spring-boot-starter,是所有starter的
    • spring-boot-starter导入了 个包spring-boot-autoconfigure。包里面都是各种场景的AutoConfiguration自动配置类
    • 虽然全场景的自动配置都在spring-boot-autoconfigure这个包,但是不全都开启的
  • @EnableAutoconfiguration:SpringBoot开启自动配置的核心
能生效的自动配置 springbot自动配置原理 自动配置流程

自定义启动器(可以理解配置原理)

场景:抽取聊天机器人场景,它可以打招呼

效果:任何项目导入此starter都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改

  1. 创建模块,继承父工程,导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!--    继承springboot父亲工程-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
    </parent>
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
    </dependencies>
  2. 编写配置读取RobotProperties配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.aiguigu.robot.properties;
    @Component//作为组件注入
    @Data//get和set
    @ConfigurationProperties(prefix = "robot")//指明要如robot.xxx配置
    public class RobotProperties {
    private String name;
    private String age;
    private String email;
    }
  3. 编写RobotService组件,绑定RobotProperties配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.aiguigu.robot.service;
    import com.aiguigu.robot.properties.RobotProperties;
    @Service
    public class RobotService {
    @Autowired
    private RobotProperties robotProperties;
    public String sayHello(){
    return String.format("你好,名字是%s,年龄是%s,邮箱是%s",robotProperties.getName(),robotProperties.getAge(),robotProperties.getEmail());
    }
    }
  4. 编写RobotAutoConfiguration,RobotService.class和RobotProperties.class依赖它,

    1
    2
    3
    4
    5
    6
    7
    8
    package com.aiguigu.robot.config;
    import com.aiguigu.robot.properties.RobotProperties;
    import com.aiguigu.robot.service.RobotService;
    @Configuration
    //RobotAutoConfiguration触发,它俩也会触发。使得RobotService组件可以注入RobotProperties属性类
    @Import({RobotService.class, RobotProperties.class})
    public class RobotAutoConfiguration {
    }
  5. 编写META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,可以从autoconfigure依赖里复制。

    1
    com.aiguigu.robot.config.RobotAutoConfiguration

    想让SpringBoot来读取进行自动配置,要读这个文件。所以①SpingBoot给你写好②自己写。之后其他项目要用这个启动器,它会读这个文件,进行自动加载自动配置。

  6. 编写注解EnableRobotAutoConfiguration。@EnableAutoconfiguration:SpringBoot开启自动配置的核心,但它不认识我们写的类,故自己写一个注解(厂家可以让SpringBoot的注解把他的也给启动)。

    1
    2
    3
    4
    5
    6
    7
    package com.aiguigu.robot.config;
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented//上面三个可以找个注解复制
    @Import(RobotAutoConfiguration.class)
    public @interface EnableRobotAutoConfiguration {
    }

    这个注解写在SpringBoot启动类上,会导入RobotAutoConfiguration.class配置类,配置类又会导入RobotService.class组件, RobotProperties.class属性类。启动时,优先加载第五步的文件,而@Import注解程序运行过程中生效,故用注解不会加载了,直接用。

  7. 使用,

    1. 创建,导入前面创建的模块

      1
      2
      3
      4
      5
      6
      7
      <dependencies>
      <dependency>
      <groupId>com.atguigu</groupId>
      <artifactId>spring-boot-robot-starter模块名</artifactId>
      <version>1.0-SNAPSHOT</version>
      </dependency>
      </dependencies>
    2. 编写配置文件,前面在注解上面要求了前缀

      1
      2
      3
      4
      robot:
      name: "\u5C0F\u7231\u540C\u5B66" # 不加双引号,写中文,String输出的是Unicode
      age: 15
      email: 123@gmail.com
    3. 编写UseRobotConroller

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      package com.atguigu.controller;
      import com.aiguigu.robot.service.RobotService;

      @RestController
      @RequestMapping("/robot")
      public class UseRobotConroller {
      @Autowired
      private RobotService robotService;

      @GetMapping
      public String sayHi(){
      String s = robotService.sayHello();
      System.out.println("s = " + s);

      return "ok";
      }

      }
    4. 编写启动类RobotApplication

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      package com.atguigu;
      import com.aiguigu.robot.config.EnableRobotAutoConfiguration;

      @SpringBootApplication
      @EnableRobotAutoConfiguration//自己模块里编写的注解
      public class RobotApplication {
      public static void main(String[] args) {
      SpringApplication.run(RobotApplication.class,args);
      }

      }

整合

整合MVC

1
2
3
4
5
6
7
8
9
10
11
12
<!--    继承springboot父亲工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
  • 静态资源默认路径如下:静态资源路径

    如果不满意,WebProperties属性类里有一个静态类Resources,故可以在配置文件里进行配置。

  • 拦截器:自定义好拦截器后注入到容器里,需要一个实现WebMvcConfigurer接口的@Configuration配置类,该类还要实现addInterceptors(InterceptorRegistry registry),方法里registry.addInterceptor(自动注入myInterceptor);默认所以都拦截,添加或排除可以继续点方法,形成链。

整合MyBatis

  在web的基础上添加mybatis的starter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!--    继承springboot父亲工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<!-- mysql要自己导,比较数据库那么多而且它的starter老-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- 德鲁伊不用导,SpringBoot自带一个连接池(日语:光,东欧人在日本写的)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

  简单配置一下:数据库连接池和mybatis的别名、日志、下划线转驼峰、mapper.xml位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///mybatis
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource


mybatis:
type-aliases-package: com.atguigu.pojo
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
map-underscore-to-camel-case: true
# 我:接口的代理对象交给spring的方式改为写注解 @Mapper//将该接口的动态代理对象放入IoC容器
mapper-locations: classpath:/mappers/*.xml
server:
servlet:
context-path: /ssm

  使用@Mapper注解将该接口的动态代理对象放入IoC容器

整合定时任务

  • 自动触发,无需手动触发动作
  • 时间准确,会在准确的时间内进行业务处理
  • 低耦合,不影响其他业务功能
1
2
3
4
5
6
<!--        为了获取本地时间,而不是时间戳-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.12.5</version>
</dependency>

  写一个类,在类里写相关的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class ScheduleTask {

@Autowired
private EmployeeService employeeService;

// @Scheduled(cron = "0 0 20 * * ?")
@Scheduled(cron = "0/5 * * * * ?")
public void execute(){
DateTime dateTime = new DateTime();
DateTime tomorrow = dateTime.plusDays(1);
String timeString = dateTime.toString("yyyy-MM-dd");
System.out.println("timeString = " + timeString);
List<Employee> employees = employeeService.showAll();
System.out.println("employees = " + employees);
}
}

可见@Scheduled(cron = "0/5 * * * * ?")注解和cron表达式就可以指定定时任务。

相要启动还需要在启动类上添加@EnableScheduling注解。

打包

  添加打包插件

1
2
3
4
5
6
7
8
9
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.5</version>
</plugin>
</plugins>
</build>

未解决的问题

  • getConnection 点进去,我们可以发现返回值上有括号,例如 return (con); 我不明白括号有什么用。

  • 从 HttpServlet 源码里,让我想起 http 请求。为什么我常见的 put、delete 等要用 post 发送并指定 method ,是浏览器不支持标准的 http 请求吗?能不能不做一些配置,直接就可以见名知意地发送想发送的请求? 似乎有点知道了: 通过 Ajax 发送 PUT、DELETE 请求的两种实现方式

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.atguigu.utils;

public class R {
private int code = 200;
private boolean flag = true;
private Object data;
public static R ok(Object data){
R r = new R();
r.data = data;
return r;
}
public static R fail(Object data){
R r = new R();
r.code = 500;
r.flag = false;
r.data = data;
return r;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.atguigu.quick.uitls;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.io.IOException;
import java.io.InputStream;

public class SqlSessionUtil {
public static SqlSessionFactory sqlSessionFactory = null;

static {
// 1. 读取MyBatis配置文件
try {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}

}

其他

mysql数据库的boolean是0和1

mapper.xml里的sql不加分号,比如分页后面追加limit

EmployeeController里有一个方法,返回的不是视图名,也不是String,而是一个自定义对象会报以下错误,解决方式是:@RestController或者@ResponseBody

Circular view path [employee]: would dispatch back to the current handler URL [/employee] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)