Spring Boot

Spring Boot

  • [1.什么是Spring Boot](#1.什么是Spring Boot)
    • [1.1 Spring、Spring MVC、Spring Boot的关系](#1.1 Spring、Spring MVC、Spring Boot的关系)
    • [1.2 Spring Boot的核心功能](#1.2 Spring Boot的核心功能)
    • [1.3 spring-boot-starter-parent](#1.3 spring-boot-starter-parent)
    • [1.4 Spring Boot Stater](#1.4 Spring Boot Stater)
  • 2.自动配置
    • [2.1 自动配置注解](#2.1 自动配置注解)
      • [2.1.1 EnableAutoConfiguration注解](#2.1.1 EnableAutoConfiguration注解)
      • [2.1.2 SpringBootApplication注解](#2.1.2 SpringBootApplication注解)
      • [2.1.3 条件注解](#2.1.3 条件注解)
    • [2.2 自动配置的实现方式](#2.2 自动配置的实现方式)
    • [2.3 自动配置的原理](#2.3 自动配置的原理)
    • [2.4 引导类](#2.4 引导类)
  • 3.配置文件
    • [3.1 属性的合并、覆盖](#3.1 属性的合并、覆盖)
    • [3.2 yaml和yml的区别](#3.2 yaml和yml的区别)
    • [7.3 获取配置文件中的属性](#7.3 获取配置文件中的属性)
  • 8.获取配置文件的属性(SpringBoot)
  • 9.整合SpirngMvc
    • [9.1 注册DispatcherServlet(回顾)](#9.1 注册DispatcherServlet(回顾))
      • [9.1.1 Spring Framework 2.0时代 XML配置](#9.1.1 Spring Framework 2.0时代 XML配置)
      • [9.1.2 Spring Framework 3.0时代 java配置](#9.1.2 Spring Framework 3.0时代 java配置)
    • [9.2 SpringBoot注册DispatcherServlet](#9.2 SpringBoot注册DispatcherServlet)
    • [9.3 设置端口](#9.3 设置端口)
    • [9.4 设置DispatcherServlet路径](#9.4 设置DispatcherServlet路径)
    • [9.5 访问静态资源](#9.5 访问静态资源)
    • [9.6 拦截器](#9.6 拦截器)
      • [9.6.1 Spring2.0时代](#9.6.1 Spring2.0时代)
      • [9.6.2 Spring3.0时代](#9.6.2 Spring3.0时代)
      • [9.6.3 SpringBoot注册拦截器](#9.6.3 SpringBoot注册拦截器)
    • [9.7 WebMvcConfigurer](#9.7 WebMvcConfigurer)
    • [9.8 配置静态资源路径](#9.8 配置静态资源路径)
    • [9.9 配置消息转化器](#9.9 配置消息转化器)
      • [9.9.1 视图解析器、消息转换器转的选择](#9.9.1 视图解析器、消息转换器转的选择)
      • [9.9.2 默认的消息转换器](#9.9.2 默认的消息转换器)
      • [9.9.3 测试各类消息转换器](#9.9.3 测试各类消息转换器)
    • [9.10 配置跨域资源共享](#9.10 配置跨域资源共享)
      • [9.10.1 哪些情况下会出现跨域问题](#9.10.1 哪些情况下会出现跨域问题)
      • [9.10.2 不同域名、不同协议、不同端口](#9.10.2 不同域名、不同协议、不同端口)
        • [9.10.2.1 CORS](#9.10.2.1 CORS)
        • [9.10.2.2 SpringBoot的解决方式(使用CORS)](#9.10.2.2 SpringBoot的解决方式(使用CORS))
      • [9.10.3 跨域Cookie受限](#9.10.3 跨域Cookie受限)
  • 10.整合连接池
    • [10.1 HikariCP连接池](#10.1 HikariCP连接池)
    • [10.2 Druid连接池](#10.2 Druid连接池)
  • 11.整合Mybatis
    • [11.1 属性详解](#11.1 属性详解)
    • [11.2 一级缓存、二级缓存](#11.2 一级缓存、二级缓存)
    • [11.3 一对一、一对多、多对多查询](#11.3 一对一、一对多、多对多查询)
      • [11.3.1 解决无法执行嵌套Select语句](#11.3.1 解决无法执行嵌套Select语句)
      • [11.3.2 association标签](#11.3.2 association标签)
      • [11.3.3 collection标签](#11.3.3 collection标签)
    • [11.4 通用mapper(基础的CRUD操作)](#11.4 通用mapper(基础的CRUD操作))
  • 12.整合事务
    • [12.1 Spring 2.0时代整合事务](#12.1 Spring 2.0时代整合事务)
    • [12.2 Spring 3.0时代整合事务](#12.2 Spring 3.0时代整合事务)
    • [12.3 SpringBoot整合事务](#12.3 SpringBoot整合事务)
  • 13.过滤器和监听器
  • 14.SpringBoot项目快速启动
  • 15.yaml
    • [15.1 语法](#15.1 语法)
      • [15.1.1 yaml数组数据](#15.1.1 yaml数组数据)
      • [15.1.2 yaml数据读取和注入](#15.1.2 yaml数据读取和注入)
    • [15.2 多环境开发配置](#15.2 多环境开发配置)
      • [15.2.1 多环境启动命令格式(运维人员)](#15.2.1 多环境启动命令格式(运维人员))
      • [15.2.2 Maven与SpringBoot多环境兼容](#15.2.2 Maven与SpringBoot多环境兼容)
    • [15.3 配置文件读取优先级](#15.3 配置文件读取优先级)
  • 16.SpringBoot整合Junit

1.什么是Spring Boot

1.1 Spring、Spring MVC、Spring Boot的关系

  • Spring是一个基础框架,提供依赖注入、AOP等核心功能。
  • Spring MVC是Spring框架的一个子模块,专注于Web应用的开发,提供了MVC模式的实现。
  • Spring Boot是在Spring基础上构建的扩展框架,内置了对Spring MVC的支持,通过自动配置和快速启动来解决Spring、Spring MVC需要配置大量的参数的问题。
    • 基于Spring框架
    • 集成Spring MVC
    • 自动配置
    • 快速启动

1.2 Spring Boot的核心功能

  • 独立运行Spring项目

    Spring Boot允许开发者将应用打包为可独立运行的JAR文件,包含所有必需的依赖和配置。这意味着开发者可以通过简单的命令(如 java -jar)来启动应用,无需依赖外部的应用服务器。

  • 内嵌Servlet容器

    Spring Boot支持内嵌的Servlet容器,如Tomcat、Jetty和Undertow。开发者可以选择使用任一容器,而无需单独安装和配置它们。(默认使用Tomcat作为内嵌的Servlet容器)

  • 提供Starter简化Maven配置

    • 起始依赖 : Spring Boot提供了一组起始依赖(如spring-boot-starter-webspring-boot-starter-data-jpa等),使得开发者可以通过引入这些依赖来快速获取所需的功能,而无需手动管理所有具体的库和版本。
    • 依赖管理: 这些起始依赖会自动引入相关的库,简化了Maven或Gradle的依赖配置,并确保使用兼容的版本,减少了因依赖冲突而导致的问题。
  • 自动配置

    • 智能配置: Spring Boot通过自动配置功能,根据类路径中的库、已有的Beans和其他设置,自动推断出应用所需的配置。这意味着开发者可以省去大部分的手动配置工作。
    • 条件配置:Spring Boot使用了条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean等)来确定何时应用某些配置,从而确保只在需要时加载特定的功能。

1.3 spring-boot-starter-parent

Spring Boot提供的一个Maven项目的父POM,它提供了一系列的默认配置、依赖管理和插件设置,以便于项目构建。

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
</parent>

spring-boot-starter-parent中已经对各种常用的依赖(例如mybatis、springmvc等等,但并非全部)的版本进行管理。以这个项目作为父工厂,就可以不用操心依赖的版本问题。

提供默认的版本依赖
spring-boot-dependencies中定义了核心库和框架的默认版本号(在<dependencyManagement>标签中)

只有引入的依赖在spring-boot-dependencies中存在,就可以省略版本号,例如Spring Framework、Spring Data等常用的依赖。

依赖父项目后,就可以省略版本号了(并非全部)

这些版本号都经过了测试和验证,确保了它们之间的兼容性,这样就避免了手动指定版本可能带来的版本冲突问题。

当一个项目中引入了多个依赖,而这些依赖使用了不同的版本号时,Maven会自动选择spring-boot-dependencies中定义的版本号,从而确保依赖的一致性。

1.4 Spring Boot Stater

Spring Boot Stater(启动器)是一种依赖管理机制,旨在简化项目的配置依赖管理。它们以spring-boot-starter-*的形式命名。它是一组预定义的依赖项集合。

  • 简化依赖管理: 启动器将多个相关的库和框架组合成一个依赖,开发者只需在 pom.xml 中添加一个启动器的依赖项,即可引入所有所需的库。这大大减少了手动添加每个库的复杂性。
  • 预定义的版本: 启动器通常会管理其内部依赖的版本,确保它们是兼容的。这样,开发者无需担心版本冲突或不兼容的问题。
  • 默认配置: 启动器不仅提供依赖,还可能包含一些默认的配置设置,帮助开发者快速上手。例如,spring-boot-starter-web 启动器会自动配置 Spring MVC 和内嵌的 Tomcat 服务器。
xml 复制代码
<!-- web启动器 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

实际上我们可以看到这一个启动器中就包含了这么多的依赖

2.自动配置

2.1 自动配置注解

2.1.1 EnableAutoConfiguration注解

@EnableAutoConfiguration告诉Spring Boot启用自动配置机制。它会根据应用的依赖来推测和配置相应的 Spring Beans。

2.1.2 SpringBootApplication注解

@SpringBootApplication 是一个组合注解,它包含了以下三个注解的功能:

  • @EnableAutoConfiguration: 开启自动配置机制
  • @ComponentScan: 自动扫描当前包及其子包中的 Spring组件,注册为 Spring 容器中的 Bean。
  • @SpringBootConfiguration
    • @Configuration注解的派生注解,它的作用是标识一个类是Spring Boot应用的配置类。
    • SpringBoot在启动过程中会自动扫描并加载这些配置类,并根据其中的配置信息来完成应用的初始化。

2.1.3 条件注解

  • @ConditionalOnClass:当类路径中存在指定的类时才加载该配置。
  • @ConditionalOnMissingClass:当类路径中不存在指定的类时才加载该配置。
  • @ConditionalOnBean:当容器中存在指定的 Bean 时才加载该配置。
  • @ConditionalOnMissingBean:当容器中不存在指定的 Bean 时才加载该配置。
  • @ConditionalOnProperty:当某个配置属性的值满足特定条件时才加载该配置。

2.2 自动配置的实现方式

Spring Boot使用@Import注解加载配置类,@Import加载配置类有以下几种方式

  • 导入普通类
  • 导入选择器
  • 导入注册器

Spring Boot的自动配置主要是通过导入选择器来实现的

导入接口ImportSelector的实现类,接口ImportSelector中有一个selectImports方法,它的返回值是一个字符串数组,数组中的每个元素代表一个将被导入的配置类的全限定名。

java 复制代码
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 根据条件返回需要导入的类名
        return new String[]{"com.example.MyConfig"};
    }
}

@Configuration
@Import(MyImportSelector.class)
public class MainConfig {
    // ...
}

2.3 自动配置的原理

  • EnableAutoConfiguration通过@Import注解导入一个ImportSelector,具体是 AutoConfigurationImportSelector
  • selectImports()中的getCandidateConfigurations()就是来读取spring.factories文件
  • SpringFactoriesLoader.loadFactoryNames()方法读取所有位于类路径下META-INF/spring.factories文件中的内容。

Spring Boot的大多数常用自动配置类都是定义在spring-boot-autoconfigure模块中,但对于某些特定功能或第三方库的自动配置类,开发者需要在相应的启动器模块(starter)中寻找spring.factories文件

  • 从Spring Boot 2.7开始,Spring Boot默认会从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中加载自动配置类。

2.4 引导类

引导类(也称为主类)是用于启动Spring应用程序的核心组件。它通常包含一个main方法,并使用@SpringBootApplication注解来配置应用程序的基本设置。

java 复制代码
@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

3.配置文件

默认情况下, SpringBoot会在以下位置搜索配置文件(按优先级排序)

  • 项目根目录下的/config/目录
  • 项目根目录
  • 类路径classpath:/config/: 即src/main/resources/config目录
  • 类路径classpath:/: 即src/main/resources目录

合并:如果多个配置文件包含不同的属性,它们会合并。

覆盖:如果多个配置文件中包含相同的属性,优先级高的文件中的属性值会覆盖优先级低的文件中的相同属性值。

3.1 属性的合并、覆盖

在Spring Boot启动时,如果config目录下存在多个配置文件(例如application.properties、application.yml、application.yaml),Spring Boot会同时加载这些文件,并按照一定的合并和覆盖规则处理配置项。

  • 如果多个配置文件中的属性不同,Spring Boot 会合并所有文件中的配置。每个文件中的不同属性会一起生效,并不会相互影响。
  • 如果多个文件中定义了相同的属性,后加载的文件中的属性会覆盖先加载的文件中的相同属性。

3.2 yaml和yml的区别

yaml和yml的区别仅仅是文件扩展名的不同,它们实际上指的是同一种数据序列化格式。主流使用.yml作为扩展名

7.3 获取配置文件中的属性

如果是application.yml文件, 我们可以直接使用@Value注解来获取配置文件中的属性

这里我们提供一个案例, 从自定义的配置文件中, 获取属性

  • 自定义配置文件: jdbc.properties, 这个文件不会被默认读取, 所以需要手动引入
properties 复制代码
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/leyou
jdbc.username=root
jdbc.password=123
  • 手动引入配置文件, 通过@Value获取属性
java 复制代码
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfiguration {

    @Value("${jdbc.url}")
    String url;
    @Value("${jdbc.driverClassName}")
    String driverClassName;
    @Value("${jdbc.username}")
    String username;
    @Value("${jdbc.password}")
    String password;

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

8.获取配置文件的属性(SpringBoot)

@ConfigurationProperties是Spring Boot 中的一个注解,用于将属性文件中的属性值绑定到一个JavaBean 上

假设application.yml文件中有如下配置

yml 复制代码
myapp:
	name: MyApplication
	port: 8080
  • 步骤一: 定义JavaBean, 前缀 + 属性名称 = 配置文件中的key
java 复制代码
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {
    private String name;
    private int port;
    // getters and setters
}
  • 步骤二: @Autowired注解将MyAppProperties 注入到其他组件
java 复制代码
@Component
public class MyComponent {

    @Autowired
    private MyAppProperties myAppProperties;

    public void printProperties() {
        System.out.println("Name: " + myAppProperties.getName());
        System.out.println("Port: " + myAppProperties.getPort());
    }
}

更简单的方式

随着Spring容器初始化Bean后, SpringBoot就会自动调用这个Bean(此处是DataSource的set方法,然后完成注入。

使用前提:该类(此处是DataSource)必须有对应属性的set方法!

java 复制代码
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfiguration {
    
    @Bean
    // 声明要注入的属性前缀,SpringBoot会自动把相关属性通过set方法注入到DataSource中
    @ConfigurationProperties(prefix = "jdbc")
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        return dataSource;
    }
}

9.整合SpirngMvc

SpringBoot整合SpirngMvc非常简单, 只需要一步: 引入SpringMVC的启动器

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

9.1 注册DispatcherServlet(回顾)

只要有SpringMvc的依赖后,SpringBoot会自动帮我们定义和注册Dispatcher

9.1.1 Spring Framework 2.0时代 XML配置

在web.xml中配置DispatcherServlet, 当web应用启动时,会解析这个文件

在2.0时代, Servlet、过滤器、监听器等组件都是在web.xml中配置的

xml 复制代码
<servlet>
	<!-- 在web应用中注册这个Servlet -->
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
    <!-- 指定读取SpringMvc配置文件的地址 -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc-servlet.xml</param-value>
    </init-param>
    
    <!-- 标记容器是否在启动的时候就加载这个servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>

<!-- 设置拦截路径 -->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

9.1.2 Spring Framework 3.0时代 java配置

  • 第一步:SpringMvc配置类
java 复制代码
@Configuration
@ComponentScan("com.itheima.controller")
// 用来开启支持SpringMvc的一些配置,常配置实现WebMvcConfigurer接口使用,底层是继承了WebMvcConfigurationSupport
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {

}
  • 第二步:继承AbstractAnnotationConfigDispatcherServletInitializer
java 复制代码
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
	// 初始化Spring容器
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
	
	// 初始化SpringMvc容器
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

原理:

  • AbstractAnnotationConfigDispatcherServletInitializer是WebApplicationInitializer的子孙类,当web应用启动时,会调用onStartup方法
  • 在AbstractDispatcherServletInitializer(父类)中不仅执行onStartup,还执行了registerDispatcherServlet,在这个方法中,我们注册了DispatchServlet

9.2 SpringBoot注册DispatcherServlet

引入spring-boot-starter-web启动器后, 我们启动引导类, 因为开启了@EnableAutoConfiguration, 所以会加载spring.factories,我们定位到spring-boot-autoconfigure:2.0.6.RELEASE

在SpringBoot中, DispatcherServlet的初始化和注册都是通过DispatcherServletAutoConfiguration来完成的

  • 自动配置DispatcherServlet
  • 注册到容器中

9.3 设置端口

yml 复制代码
server:
	port: 80

9.4 设置DispatcherServlet路径

请求的拦截路径

yml 复制代码
server: 
	servlet: 
		path: /api/*

//*/**的区别

  • /: 表示应用程序的根路径, 例如 http://example.com/
  • /* : 表示匹配所有请求的路径。例如,/abc会匹配,但/abc/def不会匹配。
  • /**: 表示匹配所有请求的路径,包括子路径, 例如,/abc、/abc/def、/abc/def/ghi等都会被匹配。

9.5 访问静态资源

在ResourceProperties的类,里面就定义了静态资源的默认查找路径:

默认的静态资源路径为:

  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/

只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。

我们习惯会把静态资源放在classpath:/static/目录下。我们创建目录,并且添加一些静态资源:

在SpringBoot中, 默认情况下静态资源不会被DispatcherServlet拦截, SpringBoot的静态资源处理是通过ResourceHttpRequestHandler来完成的, 而不是通过DispatcherServlet

SpringBoot默认配置了ResourceHandlerRegistry, 它会将/META-INF/resources//resources//static//public/等路径下的静态资源的处理交给ResourceHttpRequestHandler来处理

当请求到达时,SpringBoot会尝试将请求交给ResourceHttpRequestHandler处理静态资源,如果请求的路径匹配不到静态资源,那么请求就会被传递给DispatcherServlet进行处理。

9.6 拦截器

9.6.1 Spring2.0时代

  1. 定义拦截器类
java 复制代码
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在请求处理之前进行拦截操作
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 请求处理之后进行拦截操作,但在视图渲染之前
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 整个请求完成之后进行拦截操作,包括视图渲染之后
    }
}
  1. 注册拦截器(springmvc-servlet.xml)
xml 复制代码
<!-- 注册自定义拦截器 -->
<mvc:interceptors>
	<mvc:interceptor>
		<!-- 拦截所有请求 -->
		<mvc:mapping path="/**"/>
		<!-- <mvc:mapping path="/hello/"/> -->
		<!-- 自定义拦截器的全路径 -->
		<bean class="cn.itcast.springmvc.interceptors.MyInterceptor"/>
	</mvc:interceptor>
</mvc:interceptors>

9.6.2 Spring3.0时代

  • 第一步:在SpringMvc的配置类上添加@EnableWebMvc注解同时实现WebMvcConfigurer接口
  • 第二步:复写addInterceptors方法
java 复制代码
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
	
	// 注入拦截器
    @Autowired
    private HandlerInterceptor myInterceptor;
	
	// 设置拦截器(如果是拦截器链则按顺序执行),一个拦截器可配置多个拦截路径
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/books/**","/books");
    }
}

9.6.3 SpringBoot注册拦截器

如果你想要保持SpringBoot 的一些默认MVC特征,同时又想自定义一些MVC配置(包括:拦截器,格式化器, 视图控制器、消息转换器等等), 你应该让一个类实现WebMvcConfigurer,并且添加@Configuration注解,但是千万不要加@EnableWebMvc注解

java 复制代码
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Autowired
    private HandlerInterceptor myInterceptor;

    /**
     * 重写接口中的addInterceptors方法,添加自定义拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
    }
}

9.7 WebMvcConfigurer

在SpringBoot中配置Spring MVC的一些配置通常是通过创建一个配置类,然后实现WebMvcConfigurer接口来完成的

  • 配置拦截器
  • 配置静态资源路径
  • 配置消息转换器
  • 配置跨域资源共享

9.8 配置静态资源路径

/static/开头的URL请求都会被映射到类路径下的/static/目录下的静态资源文件, 这些静态资源文件会由 ResourceHttpRequestHandler 来处理,以提供静态资源的访问服务。

java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
}

9.9 配置消息转化器

用于处理请求和响应中的数据格式转换, 它们负责将 HTTP 请求和响应的内容转换成特定的 Java 对象,或者将 Java 对象转换成特定的数据格式(如 JSON、XML 等)

java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 清除默认的消息转换器列表
        converters.clear();

        // 添加自定义的消息转换器
        converters.add(new CustomXmlMessageConverter());
    }
}

9.9.1 视图解析器、消息转换器转的选择

  • @Controller注解, 认为返回的对象是视图名称, 并使用视图解析器来解析
    • 如果在方法中使用@ResponseBody注解, 则认为方法返回数据, 通过消息转换器转换
  • @RestController注解, 负责返回数据, 通过消息转换器转换为适当的响应格式(JSON、XML)

9.9.2 默认的消息转换器

SpringBoot提供了默认的常见消息转换器,

  • StringHttpMessageConverter: 将字符串与字节数组之间进行相互转换。
  • MappingJackson2HttpMessageConverter:将Java对象与JSON数据之间进行相互转换。
  • MappingJackson2XmlHttpMessageConverter: 可以将Java对象与XML数据之间进行相互转换。
  • ByteArrayHttpMessageConverter: 将字节数组与 InputStream 之间进行相互转换
  • FormHttpMessageConverter: 将表单数据与MultiValueMap之间进行相互转换
  • BufferedImageHttpMessageConverter: 将BufferedImage对象与字节数组之间进行相互转换
  • Jaxb2RootElementHttpMessageConverter: 可以将Java对象与XML数据之间进行相互转换

SpringMvc会根据返回值类型以及客户端请求的Accept头信息来进行选择

9.9.3 测试各类消息转换器

1.普通对象

使用MappingJackson2HttpMessageConverter转换器, JavaBean > JSON

java 复制代码
@GetMapping("converter")
public User converter(){
    return new User();
}

2.集合或数组

使用MappingJackson2HttpMessageConverter转换器, List/Array > JSON

java 复制代码
@GetMapping("converter")
public List converter(){
    return Arrays.asList(1,2,3,4,5);
}

返回格式:
[
    1,
    2,
    3,
    4,
    5
]

3.Map

使用MappingJackson2HttpMessageConverter转换器, Map > JSON

java 复制代码
@GetMapping("converter")
public Map converter(){
    Map<String, String> map = new HashMap<>();
    map.put("abc", "efg");
    return map;
}

返回格式:
{
    "abc": "efg"
}

4.@ResponseBody注解

当在处理方法上使用了 @ResponseBody 注解时,Spring MVC 将会使用消息转换器将处理方法的返回值直接转换为客户端需要的数据格式,而不会经过视图解析器。

9.10 配置跨域资源共享

9.10.1 哪些情况下会出现跨域问题

  • 不同域名、不同协议、不同端口
  • 跨域Cookie受限

9.10.2 不同域名、不同协议、不同端口

  • 不同域名 : 前端页面部署在一个域名下, 后端服务部署在另一个域名下
    • 前端页面:http://example.com
    • 后端服务:http://api.example.com
  • 不同协议: 前端页面使用了HTTPS协议, 而后端服务使用了HTTP协议,或者反过来
  • 不同端口 :前端页面和后端服务使用了不同的端口进行通信
    • 前端页面:http://example.com:8080
    • 后端服务:http://example.com:9090

这是因为浏览器实施了同源策略, 同源策略规定, 当浏览器加载一个页面时, 该页面所包含的脚本(如JavaScript)只能与相同域名、相同协议和相同端口的资源进行通信, 如果脚本试图与不同域名的资源进行通信, 浏览器会阻止这种跨域请求, 从而保护用户的隐私和安全

使浏览器在客户端已经拦截了跨域请求,但实际上浏览器还是会向目标服务器发送一个预检请求(OPTIONS请求), 以确定该跨域请求是否被允许。后端服务可以根据这些信息判断是否允许该跨域请求

解决方式:

  • 使用CORS(跨域资源共享)
    在后端服务中配置CORS策略, 允许特定的域名、协议和端口访问资源. 通过在响应头中添加Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等相关的CORS头部信息, 来告诉浏览器允许跨域访问的来源. 这是解决跨域问题的官方标准方法, 推荐使用.
  • 使用WebSocket
    WebSocket 是一种全双工通信协议, 可以在客户端和服务端之间建立持久性的连接. 由于 WebSocket 协议的特性, 可以在任意域名之间建立连接, 因此不受同源策略的限制. WebSocket通常用于实时通信场景, 不适用于普通的HTTP请求.
9.10.2.1 CORS

CORS是一种用于解决跨域请求的机制

CORS允许服务器在响应头中添加特定的头部信息,以允许或限制跨域请求。这些头部信息包括:

  • Access-Control-Allow-Origin:指定允许访问该资源的域名。可以设置为具体的域名,也可以设置为 *,表示允许任意域名访问该资源。
  • Access-Control-Allow-Methods:指定允许的HTTP请求方法,如GET、POST、PUT、DELETE等。
  • Access-Control-Allow-Headers:指定允许的自定义头部信息,如Authorization、Content-Type等。
  • Access-Control-Allow-Credentials:指定是否允许发送Cookie。默认情况下,跨域请求不会发送Cookie,需要服务器设置该头部信息为true才能发送Cookie。
9.10.2.2 SpringBoot的解决方式(使用CORS)

重写addCorsMappings方法来配置CORS

java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://frontend.com") // 允许的源(域名)
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的HTTP方法
                .allowedHeaders("*") // 允许的HTTP头部
                .allowCredentials(true) // 允许携带Cookie
                .maxAge(3600); // 预检请求的有效期(单位:秒)
    }
}
  • addMapping(String pattern):添加允许跨域请求的路径模式。参数pattern指定了要添加的路径模式,例如"/api/**"
  • allowedOrigins(String... origins):设置允许跨域请求的源(域名)。参数origins是一个可变参数,表示允许的源列表。
  • allowedMethods(String... methods):设置允许跨域请求的HTTP方法。参数methods是一个可变参数,表示允许的HTTP方法列表,例如"GET"、"POST"等。
  • allowedHeaders(String... headers):设置允许跨域请求携带的HTTP头部。参数headers是一个可变参数,表示允许的HTTP头部列表,例如"Content-Type"、"Authorization"等。
  • exposedHeaders(String... headers):设置允许客户端访问的响应头部。参数headers是一个可变参数,表示允许的响应头部列表,例如"Content-Length"、"Content-Disposition"等
  • allowCredentials(boolean allowCredentials):设置是否允许跨域请求携带凭证(例如Cookie)。
  • maxAge(long maxAgeInSeconds):设置预检请求的有效期(单位:秒)。参数maxAgeInSeconds表示预检请求的有效期,例如3600表示1小时。
  • allowedOriginPatterns(String... allowedPatterns):设置允许跨域请求的源(域名)的模式。参数allowedPatterns是一个可变参数,表示允许的源模式列表。
  • allowCredentials(boolean allowCredentials):设置是否允许跨域请求携带凭证(例如Cookie)。

9.10.3 跨域Cookie受限

  • 跨域请求默认不发送Cookie:浏览器默认情况下不会在跨域请求中发送Cookie. 即使在跨域请求的请求头中设置了Cookie, 浏览器也不会发送这些Cookie.
  • Set-Cookie头部被忽略: 当服务器在跨域请求的响应中返回了Set-Cookie头部信息时,浏览器会忽略这些Cookie。换句话说, 即使服务器尝试设置了Cookie,浏览器也不会将这些Cookie存储在浏览器的Cookie存储中。

解决方式:

java 复制代码
allowCredentials(true)

10.整合连接池

常见连接池

  • HikariCP连接池
  • Druid连接池

SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区,或在MySQL数据库端配置时区解决此问题

xml 复制代码
jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC

10.1 HikariCP连接池

HikariCP连接池是jdbc的默认使用的连接池,只有引入jdbc的依赖即可

xml 复制代码
<!--jdbc的启动器,默认使用HikariCP连接池-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!--不要忘记数据库驱动,因为springboot不知道我们使用的什么数据库,这里选择mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
yml 复制代码
# 连接四大参数
spring.datasource.url=jdbc:mysql://localhost:3306/heima
spring.datasource.username=root
spring.datasource.password=root
# 可省略,SpringBoot自动推断
spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.hikari.idle-timeout=60000
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.minimum-idle=10

10.2 Druid连接池

xml 复制代码
<!-- Druid连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.6</version>
</dependency>

<!--不要忘记数据库驱动,因为springboot不知道我们使用的什么数据库,这里选择mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
yml 复制代码
# 连接四大参数
spring.datasource.url=jdbc:mysql://localhost:3306/heima
spring.datasource.username=root
spring.datasource.password=root
# 可省略,SpringBoot自动推断
spring.datasource.driverClassName=com.mysql.jdbc.Driver

#初始化连接数
spring.datasource.druid.initial-size=1
#最小空闲连接
spring.datasource.druid.min-idle=1
#最大活动连接
spring.datasource.druid.max-active=20
#获取连接时测试是否可用
spring.datasource.druid.test-on-borrow=true
#监控页面启动
spring.datasource.druid.stat-view-servlet.allow=true

11.整合Mybatis

SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis官方自己实现了

  • 步骤一: 添加依赖
xml 复制代码
<!--mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <!-- 这里是你使用的MySQL驱动版本 -->
</dependency>
  • 步骤二: 添加Mybatis的配置, 例如别名扫描、扫描mapper文件等等
yml 复制代码
# mybatis 别名扫描
mybatis:
	configuration:
		cache-enabled: false
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
	type-aliases-package: cn.itcast.pojo
	mapper-locations: classpath:mapper/*.xml

需要注意,这里没有配置mapper接口扫描包,因此我们需要给每一个Mapper接口添加@Mapper注解,才能被识别。

java 复制代码
@Mapper
public interface UserMapper {
}

11.1 属性详解

yml 复制代码
mybatis:
  configuration:
    cache-enabled: false  # 禁用缓存
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台输出日志信息
  type-aliases-package: com.heima.entity  # 别名扫描
  mapper-locations: classpath:mapper/*.xml  # 指定MyBatis映射器XML文件的位置
  • 禁用缓存: 每次执行SQL查询时都会直接查询数据库, 不会使用任何缓存. 这样做可能会增加数据库的负载, 但也可以确保数据的实时性, 避免因为缓存而导致数据不一致或过期的问题. 禁用缓存在某些场景下会有用,例如对于实时性要求比较高的系统或者查询频率较低的系统.
  • 控制台输出日志信息: 通过设置日志输出实现类, 可以控制MyBatis输出日志的方式和目的地。除了使用标准输出流外,还可以选择其他日志框架的实现,如Log4j、Logback 等,以便将日志输出到文件、数据库或其他目的地。
  • 禁用延迟加载: 在一些情况下可能是必要的,特别是在需要立即获取完整对象图的场景下。但需要注意,禁用延迟加载可能会导致在加载对象时额外地加载了大量数据,增加了数据库查询的开销。
  • 别名扫描: MyBatis将会扫描该包下的所有类,并将其类名作为别名注册到MyBatis 中。例如,如果在 cn.itcast.pojo包下有一个类User,那么在MyBatis中就可以使用User作为别名来代替该类的全限定名。

11.2 一级缓存、二级缓存

Mybatis的缓存分为: 一级缓存、二级缓存

一级缓存、二级缓存都是默认开启的

  • 一级缓存: 默认开启, 与sqlSession相关, 同一个sqlSession执行相同的SQL, 查询结果会被缓存起来, 这样在同一个sqlSession中重复查询相同的数据时, 不会再次发送SQL查询语句, 直接从缓存中命中
  • 二级缓存: 默认开启, 需要添加<cache />标签, 与nameSpace相关, 指的是在多个SqlSession中执行相同的SQL查询时, 查询结果会被缓存起来, 这样在不同的SqlSession中重复查询相同数据时, 可以直接从缓存中获取结果, 而无需再次发送SQL查询语句

SpringBoot关闭缓存(包含一级缓存, 二级缓存)

yml 复制代码
mybatis:
	configuration:
		cache-enabled: false

实际开发中, 关闭缓存的原因

关闭 MyBatis 缓存通常是为了解决数据实时性、内存消耗、缓存更新复杂性、并发访问等问题,以及根据特定的业务场景选择是否使用缓存。在实际开发中,需要根据具体的需求和情况综合考虑是否关闭缓存。

一级缓存

一级缓存是默认开启的, 不用进行设置

java 复制代码
@Service
public class BookServiceImpl {

    @Autowired
    private BookMapper bookMapper;

    @Transactional
    public void getById(int id) {
        // 第一次查询
        Book book1 = bookMapper.getById(id);
        System.out.println("-----------");
        // 第二次查询
        Book book2 = bookMapper.getById(id);
    }
}

当添加了@Transactional注解后, Spring将确保整个方法在同一个事务中执行, 这意味着sqlSession将会在方法执行结束后关闭, 而不是在每次查询结束后. 这样第二次查询就可以从一级缓存中获取数据, 而不必再次执行SQL查询

这也说明了在同一个sqlSession中, 执行相同的SQL, 不会再次发送SQL查询语句, 直接从缓存中命中

什么情况下会导致缓存失效?

第一个查询到第二个查询期间更新了数据库中的数据,那么MyBatis会将当前SqlSession中的缓存全部清空, 以保证缓存的一致性。

如果没有添加@Transactional事务, 会怎么样?

在没有显式声明事务的情况下,Spring默认的事务处理策略可能是使用一个只读的事务,也就是说,每次方法调用结束时,Spring将会关闭数据库连接,这样导致了MyBatis的一级缓存失效。

二级缓存

在SpringBoot中, 二级缓存是默认开启的, 但是需要在具体的mapper中设置<cache>标签才能生效

java 复制代码
@Service
public class BookServiceImpl {

    @Autowired
    private BookMapper bookMapper;

    public void getById(int id) {
        // 第一次查询
        Book book1 = bookMapper.getById(id);
        System.out.println("-----------");
        // 第二次查询
        Book book2 = bookMapper.getById(id);
    }
}
xml 复制代码
<?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.heima.mapper.BookMapper">

    <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>

    <select id="getById" resultType="com.heima.entity.Book">
        select * from book where id = #{id}
    </select>
</mapper>

即使方法上没有加@Transactional, 每次方法调用都使用了新的sqlSession, 但因为二级缓存是跨sqlSession的, 所以同一个查询, 可以从二级缓存中命中

11.3 一对一、一对多、多对多查询

案例: 现在有四张表(订单表、订单详情表、商品表、用户表)**

订单: 订单详情 = 1 : n

订单详情:商品 = 1 : 1

订单:用户 = 1 : 1

  • 订单表
  • 订单详情表
  • 商品表
  • 用户表
sql 复制代码
# 订单表
CREATE TABLE `order` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '订单标识',
  `order_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '订单编号',
  `user_id` int DEFAULT NULL COMMENT '用户标识',
  `status` int DEFAULT NULL COMMENT '订单状态',
  `time` datetime DEFAULT NULL COMMENT '下单时间',
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '收货地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 订单详情表
CREATE TABLE `order_detail` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '订单详情标识',
  `order_id` int DEFAULT NULL COMMENT '关联订单标识',
  `product_id` int DEFAULT NULL COMMENT '关联商品标识',
  `quantity` int DEFAULT NULL COMMENT '商品数量',
  `amount` decimal(10,0) DEFAULT NULL COMMENT '商品总金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 商品表
CREATE TABLE `product` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '商品标识',
  `name` varchar(255) DEFAULT NULL COMMENT '商品名称',
  `price` decimal(10,0) DEFAULT NULL COMMENT '商品价格',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 用户表
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '用户标识',
  `name` varchar(255) DEFAULT NULL COMMENT '用户名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • 订单类
  • 订单详情类
  • 商品类
  • 用户类
java 复制代码
@Data
public class Order {
    private int id;
    private String orderNo;
    private int status;
    private Date time;
    private String address;
    private List<OrderDetail> detailList;
    private User user;
}

@Data
public class OrderDetail {
    private int id;
    private Product product;
    private int quantity;
    private Double amount;
}

@Data
public class Product {
    private int id;
    private String name;
    private Double price;
}

@Data
public class User {
    private int id;
    private String name;
    private int age;
}

只需要调用getById, 它指定orderResultMap, 在这个resultMap中会依次调用getUserByOrderId、getUserByOrderId、getByProductId

需要注意: <association>标签需要定义在<collection>标签之上

getUserByOrderId、getUserByOrderId、getByProductId中使用到的#{id}#{user_id}#{product_id}都需要在column属性中指定, 它来源于前一个sql查询出的临时结果

xml 复制代码
<mapper namespace="com.heima.mapper.OrderMapper">

    <resultMap id="orderResultMap" type="com.heima.entity.Order">
        <id property="id" column="id" />
        <result property="orderNo" column="order_no" />
        <result property="status" column="status" />
        <result property="time" column="time" />
        <result property="address" column="address" />
        <!-- 订单:用户 1对1 -->
        <association property="user" column="user_id" select="getUserByOrderId">
            <id column="id" property="id"/>
            <result column="name" property="name" />
        </association>
        <!-- 订单:订单详情 1对多-->
        <collection property="detailList" column="id" select="getDetailByOrderId">
            <id column="id" property="id"/>
            <result column="quantity" property="quantity" />
            <result column="amount" property="amount" />
            <!-- 订单详情:商品 1对1 -->
            <association property="product" column="product_id" select="getByProductId">
                <id column="id" property="id"/>
                <result column="name" property="name" />
                <result column="price" property="price" />
            </association>
        </collection>
    </resultMap>

    <select id="getById" resultType="com.heima.entity.Order" resultMap="orderResultMap">
        select * from `order` where id = #{id}
    </select>

    <select id="getUserByOrderId" resultType="com.heima.entity.User">
        select * from `user` where id = #{user_id}
    </select>

    <select id="getDetailByOrderId" resultType="com.heima.entity.OrderDetail">
        select * from `order_detail` where order_id = #{id}
    </select>

    <select id="getByProductId" resultType="com.heima.entity.Product">
        select * from `product` where id = #{product_id}
    </select>

</mapper>

Mybatis默认只会执行直接引用的查询语句, 而不会自动执行嵌套中的查询语句. getByProductId查询语句是在 `标签中嵌套使用的,因此它不会在orderResultMap中被自动执行。

11.3.1 解决无法执行嵌套Select语句

xml 复制代码
<mapper namespace="com.heima.mapper.OrderMapper">

<resultMap id="orderResultMap" type="com.heima.entity.Order">
    <id property="id" column="id" />
    <result property="orderNo" column="order_no" />
    <result property="status" column="status" />
    <result property="time" column="time" />
    <result property="address" column="address" />
    <association property="user" column="user_id" select="getUserByOrderId">
        <id column="id" property="id"/>
        <result column="name" property="name" />
    </association>
    <collection property="detailList" column="id" select="getDetailByOrderId" />
</resultMap>

<resultMap id="detailMap" type="com.heima.entity.OrderDetail">
    <id column="id" property="id"/>
    <result column="quantity" property="quantity" />
    <result column="amount" property="amount" />
    <association property="product" column="product_id" select="getByProductId">
        <id column="id" property="id"/>
        <result column="name" property="name" />
        <result column="price" property="price" />
    </association>
</resultMap>


<select id="getById" resultType="com.heima.entity.Order" resultMap="orderResultMap">
    select * from `order` where id = #{id}
</select>

<select id="getUserByOrderId" resultType="com.heima.entity.User">
    select * from `user` where id = #{user_id}
</select>

<select id="getDetailByOrderId" resultMap="detailMap">
    select * from `order_detail` where order_id = #{id}
</select>

<select id="getByProductId" resultType="com.heima.entity.Product">
    select * from `product` where id = #{product_id}
</select>

保证每个result中没有嵌套查询

最佳方案: 每个select的返回结果都使用单独的<resultMap>封装

xml 复制代码
<mapper namespace="com.heima.mapper.OrderMapper">

    <resultMap id="orderResultMap" type="com.heima.entity.Order">
        <id property="id" column="id" />
        <result property="orderNo" column="order_no" />
        <result property="status" column="status" />
        <result property="time" column="time" />
        <result property="address" column="address" />
        <association property="user" column="user_id" select="getUserByOrderId" />
        <collection property="detailList" column="id" select="getDetailByOrderId" />
    </resultMap>

    <resultMap id="userMap" type="com.heima.entity.User">
        <id column="id" property="id"/>
        <result column="name" property="name" />
    </resultMap>

    <resultMap id="orderDetailMap" type="com.heima.entity.OrderDetail">
        <id column="id" property="id"/>
        <result column="quantity" property="quantity" />
        <result column="amount" property="amount" />
        <association property="product" column="product_id" select="getByProductId" />
    </resultMap>

    <resultMap id="productMap" type="com.heima.entity.Product">
        <id column="id" property="id"/>
        <result column="name" property="name" />
        <result column="price" property="price" />
    </resultMap>

    <select id="getById" resultMap="orderResultMap">
        select * from `order` where id = #{id}
    </select>

    <select id="getUserByOrderId" resultMap="userMap">
        select * from `user` where id = #{user_id}
    </select>

    <select id="getDetailByOrderId" resultMap="orderDetailMap">
        select * from `order_detail` where order_id = #{id}
    </select>

    <select id="getByProductId" resultMap="productMap">
        select * from `product` where id = #{product_id}
    </select>

</mapper>

处理组合键时,需要传递多个参数,可以使用column="{prop1=col1, prop2=col2, prop3=col3...}",设置多个列名传入到嵌套查询语句,mybatis会把prop1,prop2,prop3设置到目标嵌套的查询语句中的参数对象中。

子查询中,必须通过prop1,prop2,prop3获取对应的参数值,你也可以使用这种方式指定参数名例如:

11.3.2 association标签

  • 属性
    • property:指定关联对象在父对象中的属性名。
    • javaType:指定关联对象的Java类型,可以是简单类型、复杂类型或接口。
    • column:指定关联对象在数据库表中的列名。
    • select:指定用于加载关联对象的查询语句 ID。
    • fetchType:设置延迟加载方式,可选值有 eager 和 lazy,默认为 eager。
    • notNullColumn:设置当关联对象的所有属性都为 null 时,是否设置父对象的对应属性为 null,可选值为 true 或 false,默认为 false。
    • autoMapping:设置是否自动映射关联对象的属性到父对象,可选值为 true 或 false,默认为 true。
  • 标签
    • <id>:用于定义关联对象的主键属性。
    • <result>:用于定义关联对象的普通属性。
    • <association>:用于定义关联对象的关联对象,实现多层级关联查询。
    • <collection>:用于定义关联对象的集合属性,实现一对多关联查询。
    • <discriminator>:用于定义关联对象的鉴别器,根据不同的条件加载不同的关联对象。
    • <constructor>:用于定义关联对象的构造函数。

11.3.3 collection标签

基本与<association>标签一样

11.4 通用mapper(基础的CRUD操作)

通用Mapper的作者也为自己的插件编写了启动器,我们直接引入即可:

xml 复制代码
<!-- 通用mapper -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

不需要做任何配置就可以使用了。

java 复制代码
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
}

12.整合事务

12.1 Spring 2.0时代整合事务

xml 复制代码
<!-- druid连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
     <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
     <property name="url" value="jdbc:mysql://127.0.0.1:3306/my_database"/>
     <property name="username" value="root"/>
     <property name="password" value="root"/>
 </bean>

 <!-- 第一步:定义具体的平台事务管理器(DataSource事务管理器) -->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <!-- 注入数据源 -->
     <property name="dataSource" ref="dataSource"/>
 </bean>

12.2 Spring 3.0时代整合事务

java 复制代码
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    // 不同的持久化框架,选择不同的事务管理器
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

12.3 SpringBoot整合事务

当我们引入了mybatis-spring-boot-starter, 就已经引入事务相关的依赖及默认配置了spring-tx

至于添加事务,SpringBoot中通过注解来控制。就是我们熟知的@Transactional

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id){
        return this.userMapper.selectByPrimaryKey(id);
    }

    @Transactional
    public void deleteById(Long id){
        this.userMapper.deleteByPrimaryKey(id);
    }
}

Service层方法上加的注解@Transactional要想生效,需要在引导类上加上注解 @EnableTransactionManagement, 开启对事务的支持。

java 复制代码
@SpringBootApplication
@EnableTransactionManagement //开启对事物管理的支持
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功...");
    }
}

13.过滤器和监听器

  • 创建过滤器类和监听器类:创建过滤器和监听器类,然后在类上添加@WebFilter和@WebListener注解。
java 复制代码
@WebFilter("/*")
public class MyFilter implements Filter {
    // 实现过滤器的逻辑
}

@WebListener
public class MyListener implements ServletContextListener {
    // 实现监听器的逻辑
}
  • 添加@ServletComponentScan注解:在Spring Boot的主配置类上添加@ServletComponentScan注解,以启用对@WebFilter、@WebServlet和@WebListener注解的扫描。
java 复制代码
@SpringBootApplication
@ServletComponentScan
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

14.SpringBoot项目快速启动

1.打成JAR或者WAR包

JAR是可独立执行的, 无需额外的Servlet容器。您可以使用java -jar命令直接启动Spring Boot应用。

WAR需要部署到外部容器(Tomcat、Jetty)中运行

对于多数的SpringBoot Web项目, 打成JAR文件即可, 因为内置了嵌入式的Servlet容器(通常是Tomcat), 使得应用程序可以作为一个独立的可执行文件直接运行。

2.对SpringBoot项目打包(执行Maven构建指令package)

要先clean后package,打包成功后在target文件夹下

3.执行启动指令

进入target文件夹下,cmd进入命令行界面,输入

xml 复制代码
java -jar springboot-demo-1.0-SNAPSHOT.jar	# 项目的名称根据实际情况修改

SpringBoot启动成功


15.yaml

yml和yaml是同一种格式,只是在运算速度上有区别

SpringBoot配置文件加载顺序(了解)
application.properties > application.yml > application.yaml


15.1 语法

  1. 大小写敏感
  2. 属性层级关系使用多行描述,每行结尾使用冒号结束
  3. 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
  4. 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
  5. 核心规则:数据前面要加空格冒号隔开

15.1.1 yaml数组数据

15.1.2 yaml数据读取和注入

一、使用@Value读取单个数据

属性名引用方式:${一级属性名.二级属性名......}

二、将数据封装到Environment对象

三、自定义对象封装指定数据(最常用)

java 复制代码
public class Enterprise {
    private String name;
    private Integer age;
    private String tel;
    private String[] subject;
    //自行添加getter、setter、toString()等方法
}

自定义对象封装数据警告解决方案

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

15.2 多环境开发配置

方式一:同一个yaml中配置多环境

在实际开发中,项目的开发环境、测试环境、生产环境的配置信息是否会一致?如何快速切换?

在yaml文件中通过加 --- 来区分多种环境

通过spring.profiles.active来指定环境


方式二:不同环境使用单位yaml(了解)

格式:application-环境名称.yml

启动时指定环境名称(不指定环境名称,默认读取application.yml)


方式三:共性内容写到主配置中,特性内容写到独立环境中,根据当前需要,在主配置中指向某个环境(常用)

  1. 共性内容写在主配置文件(application.yml)
  2. 非共性的内容(例如数据库连接信息、kafka等),我们写到独立的application-环境.yml
  3. 在主配置文件中,指向独立环境

15.2.1 多环境启动命令格式(运维人员)

一、解决yaml文件中因为存在中文,打包失败的情况

将这几处改为UTF-8


二、多环境启动命令格式

xml 复制代码
java --jar springboot.jar --spring.profiles.active=test
java --jar springboot.jar --server.port=88
java --jar springboot.jar --server.port=88 --spring.profiles.active=test

15.2.2 Maven与SpringBoot多环境兼容

一、在Maven中设置多环境属性

此处的<profile.active>dev</profile.active>只需要有application-dev.yml即可(不用在application-dev.yml中设置spring.profiles:dev)

xml 复制代码
<profiles>
	<!-- 环境dev -->
    <profile>
        <id>dev_env</id>
        <properties>
            <profile.active>dev</profile.active>
        </properties>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    
    <!-- 环境test -->
    <profile>
        <id>test_env</id>
        <properties>
            <profile.active>test</profile.active>
        </properties>
    </profile>
</profiles>

添加了后,在maven的profiles中会显示配置的环境

二、对资源文件开启默认占位符的解析

解析${}占位符

xml 复制代码
<build>
    <plugins>
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <encoding>utf-8</encoding>
                <useDefaultDelimiters>true</useDefaultDelimiters>
            </configuration>
        </plugin>
    </plugins>
</build>

三、SpringBoot中引用Maven属性

在主文件application.yml中引入maven属性

四、勾选对应的环境,启动

五、使用Maven打包

Maven指令执行完毕后,生成了对应的包,其中类参与编译,但是配置文件并没有编译,而是复制到包中,生会根据我们勾选的环境,加载环境


15.3 配置文件读取优先级

  • SpringBoot中4级配置文件
    • 1级 同级目录: config/application.yml 【最高】
    • 2级 同级目录: application.yml
    • 3级 classpath:config/application.yml
    • 4级 classpath:application.yml 【最低】

同级目录指的是与jar包在同一级目录

同样的配置,会采用等级高的

作用:

  • 1级与2级留做系统打包后设置通用属性
  • 3级与4级用于系统开发阶段设置通用属性

16.SpringBoot整合Junit

一、添加依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

二、编写测试类,默认自动生成了一个

java 复制代码
@SpringBootTest
class Springboot07JunitApplicationTests {
    @Autowired
    private BookService bookService;

    @Test
    public void testSave() {
        bookService.save();
    }
}
相关推荐
古月居GYH6 分钟前
在C++上实现反射用法
java·开发语言·c++
一元咖啡1 小时前
SpringCloud Gateway转发请求到同一个服务的不同端口
spring·spring cloud·gateway
儿时可乖了1 小时前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol1 小时前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-13141 小时前
jdk各个版本介绍
java
天天扭码2 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端