0


springboot3零基础到做项目,这一篇就够了!耗时一周整理,超详细!

Springboot3概述

什么是Springboot?

  • Spring Boot 是一个基于 Spring 框架的轻量级、快速开发的框架,它的目标是简化 Spring 应用程序的初始设置和开发过程。
  • Spring Boot 提供了一种约定优于配置的方式,使得开发者可以更加专注于业务逻辑,而不是花费大量时间在配置和管理上。
  • Spring Boot 3 是 Spring Boot 的最新版本,基于 Spring Framework 6,它在原有基础上进行了升级和完善,提供了更多的特性和改进。

springboot主要特点如下:

  1. 快速构建项目:Spring Boot 提供了一系列的 Starter 依赖,可以快速引入需要的依赖,简化 Maven 配置。
  2. 内嵌 Servlet 容器:Spring Boot 可以选择内嵌 Tomcat、Jetty 等容器,这样可以无须以 war 包形式部署项目。
  3. 自动配置 Spring:Spring Boot 根据在类路径中的 jar 包、类,为 jar 包里的类自动配置 Bean,这样极大减少我们需要去做的配置项。
  4. 准生产的应用监控:Spring Boot 提供基于 http、ssh、telnet 对运行时的项目进行监控。
  5. 约定优于配置:Spring Boot 遵循习惯优于配置的原则,使用 Spring Boot 我们只需要很少的配置,大多数使用默认配置即可。

为什么选择springboot?

Spring Boot 被广泛推荐给 Java 初学者学习的原因主要有以下几点:

  • 简化配置:Spring Boot 采用了约定优于配置的原则,通过提供默认配置和自动配置来减少繁琐的配置工作。大部分应用程序可以使用默认配置启动,只需要少量的自定义配置即可。
  • 快速启动:Spring Boot 提供了嵌入式的 Web 服务器(如 Tomcat、Jetty),使得应用程序可以独立运行,不需要外部的 Web 容器。这样可以大大加快应用程序的启动速度。
  • 自动配置:Spring Boot 根据应用程序的依赖自动配置各种组件,如数据库连接、消息队列、缓存等。开发者只需提供相应的依赖,Spring Boot 就能够自动配置所需的组件。
  • 集成第三方库和框架:Spring Boot 可以轻松地集成第三方库和框架,如 Spring Data、Spring Security、Hibernate 等。
  • 良好的文档和社区支持:Spring 官方网站提供了详细的 Spring Boot 文档和教程,其中包含大量的示例和指导。同时,Spring Boot 拥有庞大的开发者社区,你可以在社区中找到许多有经验的开发者和专家,提供帮助和支持3。

如何学习springboot?

学习 Spring Boot 3 的基础知识和步骤如下:

基础知识

  1. Java 基础:Spring Boot 是基于 Java 的框架,所以你需要掌握 Java 的基础语法,包括数据类型、控制结构、数组、集合、异常处理、IO 操作等。
  2. Spring 基础:Spring Boot 是在 Spring 的基础上发展而来的,所以了解 Spring 的基本原理和设计思想是很重要的。这包括 Spring 的核心概念(如 Bean、Controller、Service、DAO)、依赖注入、面向切面的编程等。
  3. 数据库基础:Spring Boot 常用于开发数据驱动的应用,因此你需要了解关系型数据库的基本概念,如表、字段、索引、查询语句等。MySQL 是常用的数据库系统。
  4. Web 开发基础:Spring Boot 也常用于开发 Web 应用,因此你需要了解 HTTP 协议、请求和响应、路由、模板引擎、表单处理等。

具体学习步骤

  1. 安装开发环境:首先,你需要在你的计算机上安装 JDK 17+ 和 IDE,如 IntelliJ IDEA 或 Eclipse。
  2. 学习 Spring Boot 基础:了解 Spring Boot 的基本概念,如自动配置、启动器和 Actuator。你可以阅读 Spring Boot 的官方文档,或者参考一些在线教程。
  3. 创建第一个 Spring Boot 项目:使用 Spring Initializr 或 Gradle 创建一个简单的 Hello World 项目,运行它并查看它的运行结果。
  4. 深入学习 Spring Boot:学习 Spring Boot 的更多高级特性,如 AOP、事务管理、消息队列、缓存、安全、测试等。
  5. 实践项目:尝试开发一些小型项目,如博客系统、待办事项列表、天气预报等。这将帮助你更好地理解和应用你所学的知识。
  6. 学习第三方库和框架:Spring Boot 可以轻松地集成各种第三方库和框架,如 Hibernate、MyBatis、Thymeleaf、Angular、React 等。你可以学习如何在这些项目中使用这些库和框架。
  7. 持续学习和跟进新技术:Spring Boot 是一个活跃的开源项目,经常会有新的版本和特性发布。因此,你需要保持对新技术和新特性的关注,并不断更新你的知识。

官方文档与教程推荐

官方文档

Spring的官网

Spring Boot 中文文档

视频教程

黑马程序员SpringBoot3+Vue3全套视频教程

开发环境搭建

系统要求

Spring Boot 3 需要 Java 17

为以下构建工具提供了明确的构建支持。

构建工具

版本

Maven

3.6.3 及其以上

Gradle

7.x (7.5 及其以上) 和 8.x

安装与配置JDK17

Spring Boot 可以使用 “经典的” Java开发工具,也可以作为命令行工具安装。无论哪种方式,你都需要 Java SDK v17 或更高版本。在你开始之前,你应该使用以下命令检查你当前安装的Java。

$ java -version

为Java开发者提供的安装说明:

  • Maven和Gradle 不需要同时安装,只需要其中一个就可以创建springboot工程,本教程仅使用Maven。

Maven 安装

Spring Boot与 Apache Maven 3.6.3 或以上版本兼容。 如果你还没有安装Maven,你可以按照 maven.apache.org 上的说明先进行安装。

检查你的maven版本:

$ mvn -v

如果maven版本在3.6.3以下或显示''maven' 不是内部或外部命令,也不是可运行的程序',就去官网下载符合的版本,解压后配置环境变量

Gradle 安装*

Spring Boot 与 Gradle 7.x(7.5或更高版本)或 8.x 兼容 如果你还没有安装Gradle,你可以按照 gradle.org 上的说明进行安装。官网下载界面

检查你的gradle版本:

$ gradle -v

如果gradle版本在7.5以下或显示''gradle' 不是内部或外部命令,也不是可运行的程序',就去官网下载符合的版本,解压后配置环境变量

MySQL

下载并配置MySQL8

其他

IDE

可以使用IDEA或Eclipse,推荐IDEA。

浏览器

安装:谷歌、Edge、火狐等浏览器均可

作用:对于开发Web应用(如使用Thymeleaf、JSP或Angular、React等前端框架构建的单页面应用),浏览器是用户与之交互的主要工具。开发者可以通过浏览器查看页面布局、设计和功能,并进行初步的用户体验测试。

API开发工具

对于API驱动的应用程序(如RESTful服务),浏览器的作用就有限了。虽然浏览器可以用来发送GET请求并查看返回的JSON或XML数据,但它不支持更复杂的HTTP方法(如POST、PUT、DELETE),也不方便设置请求头、请求体或测试API的多种场景。此外,浏览器不适合执行自动化测试,也不便于管理多个环境或API版本的请求。

因此,除了浏览器之外,还需要安装API开发工具,如Postman或APIPost

API开发工具的主要作用包括:

  • 全面的HTTP方法支持:API开发工具允许使用所有HTTP方法,包括GET、POST、PUT、DELETE等,以便全面测试API。
  • 请求构建和参数设置:这些工具提供了方便的方式来构建请求,包括设置URL参数、请求头、请求体(如JSON、XML、form-data等)。
  • 响应验证:API开发工具可以方便地查看和验证API的响应状态码、响应头和响应体,有些工具还提供了格式化JSON和XML的功能。
  • 自动化测试:开发者可以编写测试脚本来自动化API测试,这对于确保API的稳定性和进行回归测试非常重要。
  • 环境管理:API开发工具通常支持环境变量,使得在不同环境(如开发、测试、生产)中切换变得更加容易。
  • 团队协作:这些工具允许团队成员共享API请求、测试用例和测试集合,有助于团队协作和知识共享。 集成和持续测试:API开发工具可以与CI/CD工具集成,实现自动化的API测试和监控。


SpringBoot基础

第一个Spring Web项目

Hello World

  1. 使用IDEA创建工程。工程名,jdk版本

创建工程时可以将Server URL修改为start.aliyun.com,有初始代码更适合初学者

  1. 选择springboot版本(后面不要有后缀的),导入依赖(依赖后续可以在pom.xml中修改)

基本的web项目只需要Spring Web依赖,其他依赖后续可以在pom.xml添加。

  1. 定义请求处理类:创建controller目录,在其下面创建一个测试Controller(如果工程连Controller都跑不起来就白写了)
@RestController
public class TestController {
    @RequestMapping(value = "/test")
    public String test(){
        return "Hello, world!";
    }
}
  1. 修改启动类(XXXApplication.java)的注解,使springboot工程不需要连接数据库也可以运行

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})

  1. 启动工程,并用浏览器打开http://localhost:8080/test(一般来说springboot默认端口都是8080,可以在控制台查看)

localhost:8080/test,意思是向本地计算机中的8080端口程序,获取资源位置 是/test的数据

打开浏览器显示"Hello world"表示第一个应用开发成功。

如果你使用浏览器访问了其他路由,例如http://localhost:8080或其他随便一个如http://localhost:8080/fl,会出现以下页面:

控制台显示Failed to load resource: the server responded with a status of 404 ()。这是因为你没有在声明处理该请求的controller,你可以仿照前面的方式自己添加controller自己处理请求,后面会进一步讲解。

  • 彩蛋-设置springboot启动时图标
  1. 在resource文件下创建banner.txt
  2. 在ASCII艺术字(图)集网址中生成后复制到banner.txt
___________    ____ 
      ______/   \__//   \__/____\ 
    _/   \_/  :           //____\\ 
   /|      :  :  ..      /        \ 
  | |     ::     ::      \        / 
  | |     :|     ||     \ \______/ 
  | |     ||     ||      |\  /  | 
   \|     ||     ||      |   / | \ 
    |     ||     ||      |  / /_\ \ 
    | ___ || ___ ||      | /  /    \ 
     \_-_/  \_-_/ | ____ |/__/      \ 
                  _\_--_/    \      / 
                 /____             / 
                /     \           / 
                \______\_________/
  1. 启动工程,就会发现控制台中springboot的图标变化了

Hello World探究

项目基本结构

初始项目如下:

完整结构如下:

boot3-01-helloworld/
|-- src/
|   |-- main/
|       |-- java/                                                               # 项目的源代码
|           |-- com/                                
|               |-- fl/
|                   |-- boot/                                        # 包名,例如com.fl.boot
|                       |-- MyApplication.java # 应用的入口类,包含 main 方法,用于启动 Spring Boot 应用
|                       |-- controller/   # 包含所有的控制器类(Controller),它们处理用户的输入并返回响应
|                           |-- MyController.java
|                       |-- service/                    # 包含服务类(Service),它们包含业务逻辑。
|                           |-- MyService.java
|                       |-- repository/     # 用于Spring Data项目,适用于JPA、MongoDB、Neo4j等多种数据源
|                           |-- MyRepository.java
|                       |-- mapper/      # 用于MyBatis项目,用于关系型数据库,也可以通过扩展支持其他数据源
|                           |-- MyMapper.java
|                       |-- entity/                     # 包含实体类(Entity),它们映射到数据库表。
|                           |-- MyEntity.java
|                       |-- config/                             # 包含配置类,用于配置应用的行为
|                           |-- MyConfig.java
|                           |-- exception
|                                          |-- GlobalExceptionHandler.java      # 捕获全局异常并处理
|       |-- resources/                                               # 包含了应用的所有资源文件
|           |-- application.properties                                           # 配置文件
|           |-- application.yml                                 # 配置文件,实际开发比上面的更常用
|           |-- static/                             # 用于存放静态资源,如CSS、JavaScript和图片文件
|           |-- templates/      # 用于存放Web应用的模板文件,这通常是在使用模板引擎(如Thymeleaf)时需要的
|           |-- schema.sql                                      # 用于存放创建数据库结构的SQL脚本
|           |-- data.sql                                        # 用于初始化数据库中的数据的SQL脚本
|       |-- test/                                   # 可以按照与src/main/java相似的包结构组织测试类
|-- pom.xml                                 # Maven构建文件,用于定义项目的依赖、插件和其他构建配置
|-- build.gradle                             # Gradle构建文件,用于定义项目的依赖、插件和其他构建配置
|-- .gitignore                                           # 定义 Git 版本控制系统应该忽略的文件和目录
|-- README.md                       # 这是项目的 README 文件,通常包含项目的基本信息和如何运行应用的指南
重要文件

下面对Springboot3的项目中的几个重要文件做基本的介绍:

入口点MyApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}
  • Spring Boot 应用的入口点是包含 @SpringBootApplication注解的类,以及在该类中定义的 main 方法。这个入口类负责启动 Spring Boot 应用。
  • @SpringBootApplication:这是一个组合注解,它包含了三个主要的注解:
  • @SpringBootConfiguration:标记这个类作为应用的配置类。
  • @EnableAutoConfiguration:让 Spring Boot 根据类路径中的 jar 包、类,为当前项目进行自动配置。
  • @ComponentScan:告诉 Spring 扫描这个类所在的包及其子包中的注解组件(如 @Component, @Service, @Repository, @Controller 等)。
  • main 方法:这是 Java 应用的标准入口点。当运行 Spring Boot 应用时,JVM 调用这个 main 方法启动应用。
  • SpringApplication.run(MyApplication.class, args);:这行代码负责启动 Spring 应用上下文。SpringApplication 提供了一个方便的方式来启动 Spring 应用。run 方法的参数 MyApplication.class 是入口类的类对象,它用于告诉 SpringApplication 起始的配置类是哪一个。args 是从命令行传入的参数。
  • 当运行这个 main 方法时,会发生以下步骤:
  • 创建一个合适的 ApplicationContext 实例(取决于类路径和其它因素)。
  • 注册一个 CommandLinePropertySource 以将命令行参数添加到 Spring 的 Environment 中。
  • 刷新 ApplicationContext,加载所有单例 beans。
  • 执行任何 CommandLineRunner beans。

pom.xml

pom.xml 是 Maven 项目中的一个核心文件,用于定义项目的构建、报告和依赖关系等信息。在 Spring Boot 3 中,pom.xml 文件同样扮演着重要的角色。

一个基本的 pom.xml 文件通常包含以下几个部分:

  1. 项目的基本信息:包括 <groupId>、<artifactId>、<version> 等,它们分别代表项目的组ID、项目ID 和版本号。
  2. 父项目:如果你的项目依赖于其他项目,那么你需要声明你的项目是哪个项目的子项目,这可以通过 <parent> 元素来实现。
  3. 依赖关系:在你的项目中,可能需要用到其他的库或者框架,这时就需要在 <dependencies> 元素下声明对这些库或框架的依赖。
  4. 插件:<plugins> 元素用于声明项目需要的插件,例如编译插件、打包插件等。
  5. 构建配置:<build> 元素用于设置项目的构建参数,例如源代码目录、目标目录、测试目录等。

application.yml

application.yml 是一个用于配置 Spring Boot 应用程序的文件。它允许开发者为应用程序的不同方面(如数据库连接、安全设置、消息服务等)提供特定的配置。

日志★

介绍

  • 日志是指应用程序运行时产生的信息,这些信息可以帮助开发者了解应用程序的运行状态、调试问题以及监控应用程序的行为。日志可以包括信息性的消息、警告、错误以及调试信息等。
  • 开发规范:不要使用System.out.println(),使用专业日志框架记录信息。这是因为使用专业的日志框架可以提高应用程序的可维护性、性能和可扩展性。它允许您以更灵活和高效的方式处理日志记录,而System.out.println()则是一种简陋且不灵活的日志解决方案。
  • Spring Boot默认使用Apache Commons Logging作为内部的日志框架,但允许您通过配置使用其他日志框架,如Logback、Log4J2等。Spring Boot为Logback和Log4J2提供了默认的配置,使得您可以快速开始记录日志。

日志门面是一个抽象层,它定义了日志记录的接口,但不提供具体的日志记录实现。它的目的是提供一种统一的方式来访问日志记录功能,而不关心底层的日志记录系统是什么。这样,无论底层使用的是哪种日志实现,开发者都可以使用相同的方法和API来记录日志。

日志实现是具体实现日志记录功能的库或框架。它实现了日志门面定义的接口,并提供实际的日志记录能力。例如,Logback和Log4J都是日志实现的例子。

基本使用

  • 要自己生成日志我们可以使用Lombok 库提供的一个注解,@Slf4j。
  • @Slf4j 注解可以为当前的类自动生成一个 SLF4J的日志对象,通常这个对象被命名为 log。
  • 在 SLF4J 中,Logger 接口定义了几个常用的日志记录方法,包括 info(), debug(), warn(), 和 error()。这些方法用于记录不同级别的日志信息,以便于开发者根据日志级别来过滤和查看应用程序的运行情况。
  • info(): 用于记录一般信息,通常是应用程序运行中的正常事件或者流程信息。这些信息通常对监控应用程序的日常运行是有帮助的。
  • debug(): 用于记录调试信息,这些信息通常只在开发阶段需要,用于帮助开发者诊断问题。在生产环境中,通常不会启用 debug 级别的日志。
  • warn(): 用于记录警告信息,表明应用程序可能遇到一些不正常或不期望的情况,但这些情况并不影响应用程序的继续运行。
  • error(): 用于记录错误信息,通常是指应用程序遇到了严重的错误,这些错误可能会导致应用程序的部分功能失败或者整个应用程序崩溃。

要使用@Slf4j注解,首先要保证在pom.xml中引入了Lombok 库的依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

示例:

import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class TestController {
    //    无参测试
    @RequestMapping(value = "/test")
    public String test() {
        log.info("Hello, world!");
        log.warn("Hello, world!");
        log.error("Hello, world!");
        return "Hello, world!";
    }

}

使用浏览器访问http://localhost:8080/test,控制台出现下面日志:

在实际使用中,你可以在日志消息中包含变量或者使用占位符来提高日志的可读性。

String userName = "张三";
log.info("用户 {} 尝试登录系统。", userName);

error() 方法通常用于记录异常信息,你可以将异常对象作为参数传递给 error() 方法

try {
    // ... 可能会抛出异常的代码 ...
} catch (Exception e) {
    log.error("发生了一个异常:", e);
}

小技巧

lombok★

介绍

实体类代码臃肿(getter、setter、toString...),太繁琐

Lombok是一个实用的java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、tostring等方法,并可以自动化生成日志变量,简化java开发、提高效率。

注解

作用

@Getter/@Setter

为所有的属性提供get/set方法

@ToString

会给类自动生成易阅读的toString方法

@EqualsAndHashCode

根据类所拥有的非静态字段自动重写equals方法和hashCode方法

@Data

提供了更综合的生成代码功能(@Getter+@Setter+@ToString+@EqualsAndHashCode)

@NoArgsConstructor

为实体类生成无参的构造器方法

@AllArgsConstructor

为实体类生成除了static修饰的字段之外带有各参数的构造器方法。

使用
  • 在pom.xml引入依赖(如果在创建时引入了可以跳过)
<dependency>  
    <groupId>org.projectlombok</groupId>  
    <artifactId>lombok</artifactId>  
    <!--不需要指定版本,在父工程已指定-->
</dependency>
  • 在实体类中使用Lombok注解
@Data
@NoArgsConstructor
@AllArgsConstructor
  • lombok在编译时,会自动生成对应java代码。使用代码时,还需要安装一个lombok插件(idea自带,除非较老版本)

实例

@Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class User {  
    private Integer id;  
    private String name;  
    private Short age;  
    private Short gender;  
    private String phone;  
  //使用lombok省略了以下代码
//    public User() {  
//    }  
//  
//    public User(Integer id, String name, Short age, Short gender, String phone) {  
//        this.id = id;  
//        this.name = name;  
//        this.age = age;  
//        this.gender = gender;  
//        this.phone = phone;  
//    }  
  
//    public Integer getId() {  
//        return id;  
//    }  
// ...
//    @Override  
//    public String toString() {  
//        return "User{" +  
//                "id=" + id +  
//                ", name='" + name + '\'' +  
//                ", age=" + age +  
//                ", gender=" + gender +  
//                ", phone='" + phone + '\'' +  
//                '}';  
//    }  
}


springboot配置

配置文件分类

  • springboot提供了多种属性配置方式

    • application.xml(只有老的spring项目使用)- application.properties- application.yml(或yaml)

优先级:properties>yml>yaml

配置文件

pom.xml★

在Spring Boot项目中,pom.xml文件是Maven项目对象模型(Project Object Model)的定义文件,它用于管理项目的构建、依赖和插件。

Maven是一个流行的自动化构建工具,它通过pom.xml文件来执行构建过程。本教程全部项目都使用Maven构建。

下面是一个基本的pom.xml文件的结构和各个部分的解释:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- 项目的基本信息 -->
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId> <!-- 组织或项目的唯一标识符 -->
    <artifactId>my-spring-boot-app</artifactId> <!-- 项目的基本名称 -->
    <version>1.0-SNAPSHOT</version> <!-- 项目的版本号 -->
    <name>My Spring Boot App</name> <!-- 项目的显示名称 -->
    <description>Spring Boot 3 example project</description> <!-- 项目的描述 -->
    
    <!-- Spring Boot父项目,提供依赖管理和插件管理 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0</version>
    </parent>

    <!-- 项目依赖 -->
    <dependencies>
        <!-- Spring Boot的Web Starter,包含Spring MVC和Tomcat等 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- 其他依赖... -->
    </dependencies>

    <!-- 项目构建配置 -->
    <build>
        <plugins>
            <!-- Spring Boot的Maven插件,用于打包可执行的jar或war文件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            
            <!-- 其他插件... -->
        </plugins>
    </build>

</project>

注意事项:

  • 继承Spring Boot父项目:通过指定spring-boot-starter-parent作为父POM,您的项目将继承Spring Boot的默认配置,包括依赖管理、插件管理等。
  • 依赖管理:在<dependencies>部分,您可以添加项目所需的所有依赖。Spring Boot提供了许多“Starter”依赖,它们是一组方便的依赖描述符,可以简化构建配置。
  • 插件配置:在<build>部分的<plugins>中,您可以配置项目构建时使用的插件。例如,spring-boot-maven-plugin用于创建可执行的jar或war文件。
  • 属性和配置:您可以在pom.xml中定义和使用属性,以便于维护和管理配置。例如,您可以定义一个属性来表示Spring Boot的版本号,并在多个地方引用它。
  • 多环境配置:Maven支持多环境配置,您可以通过 profiles 来定义不同环境下的配置,并在构建时激活相应的 profile。

properties配置文件*

介绍

properties配置文件是一种用于配置应用程序属性的文件格式。它是一个标准的Java属性文件,通常包含键值对,其中键和值之间用等号=分隔。properties文件可以直接放在src/main/resources目录下,或者放在任何类路径(classpath)可以访问的地方。

只有你需要与旧的Java应用程序或框架保持兼容时才使用。

示例
server.port=8080
server.servlet.context-path=/myapp
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=myuser
spring.datasource.password=mypassword
优缺点

优点:

向后兼容性:properties文件格式在Java历史中非常悠久,几乎所有版本的Java都支持这种格式。简单性:properties文件的语法非常简单,对于简单的配置来说,它是非常直观的。

缺点

树状结构的复杂性:对于复杂的配置,尤其是层级结构的数据,properties文件可能会变得难以阅读和维护。类型不明确:properties文件中的所有值都是字符串,这意味着在将它们赋值给配置类中的不同类型字段时,需要手动进行类型转换。

yml配置文件★

介绍

YAML是“YAML Ain’t Markup Language”(递归缩写为“YAML不是标记语言”)的缩写,它是一种直观的数据序列化格式,可以被用于配置文件。

YAML使用空白字符(空格和缩进)来表示结构层次,它比properties文件更适合表示复杂的配置数据,实际开发基本都使用yml配置文件,有的程序员甚至在创建springboot工程后第一件事就是把配置文件的后缀改为yml。

适合复杂的、具有层级结构的配置场景,尤其是当你需要配置大量的、相关的配置项时。

优缺点

优点:

清晰的层次结构:YAML使用缩进来表示层级关系,这使得表示复杂的数据结构变得非常清晰。类型支持:YAML支持多种数据类型,如字符串、数字、布尔值等,并且可以自动将值转换为适当的类型。可读性强:YAML文件通常更易于阅读和理解,尤其是对于具有复杂层次结构的配置。

缺点:

轻微的复杂性:YAML的语法比properties文件稍微复杂一些,初学者可能需要一些时间来适应。

yml基本语法
  • 大小写敏感
  • 数值前必须有空格,作为分隔符
  • 使用缩进表示层级关系,缩进时,不允许使用tab键,只能使用空格
  • 缩进的空格数不重要,只要相同层级的元素左侧对齐即可
  • #表示注解
server:
  # 修改springboot工程运行端口
  port: 8081
#驱动类名称  
spring:  
  datasource:  
    # 设置数据库驱动 
    driver-class-name: com.mysql.cj.jdbc.Driver  
    # 设置数据库地址
    url: jdbc:mysql://localhost:3306/tlias  
    username: root  
    password: 123456  
  servlet:  
       multipart:  
         enabled: true  
         max-file-size: 10MB  
         max-request-size: 100MB  
  
#配置mybatis的日志, 指定输出到控制台  
mybatis:  
  configuration:  
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  
    map-underscore-to-camel-case: true
用户自定义内容
  • 对象(map): 键值对的集合
person:
    name: zhangsan
# 行内写法
person: {name: zhangsan}
  • 数组: 一组按次序排序的值
address:
    - beijing
    - shanghai
# 行内写法
address: [beijing,shanghai]
  • 纯量: 不可分割的值
s1: '123 \n 456' # 单引号不会被转义
s2: "123 \n 456" # 双引号会被转义
  • 参数引用
name: zhangsan
person:
    name: ${name}
获取自定义值
  1. @Value
@Value("${person1.name}")
    private String name;

    @Value("${address1[0]}")
    private String a1;
    @Value("${s1}")
    private String s1;
    @Value("${s2}")
    private String s2;

    @Test
    void test() {
        System.out.println(name);
        System.out.println(a1);
        System.out.println(s1);
        System.out.println(s2);
    }
  1. Envirment
@Autowired
    private Environment env;
    @Test
    void test() {
        System.out.println(env.getProperty("person1.name"));
        System.out.println(env.getProperty("address1[0]"));
        System.out.println(env.getProperty("s1"));
        System.out.println(env.getProperty("s2"));
    }
  1. @ConfigurationProperties

先在pom.xml中添加依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

创建配置类

@Data //lombok
@Component //添加到组件
@ConfigurationProperties(prefix = "person1")//绑定配置文件中配置类
public class Person {
    private String name;
    private int age;
    private String[] address;
}

在测试类中使用

@Autowired
    private Person person;
    @Test
    void test() {
       System.out.println(person.getName());
    }

Profile

介绍:profiles是不同配置选项的集合,它们对应于应用程序的不同运行环境,如开发环境、测试环境和生产环境。每个环境可能需要不同的设置,例如数据库连接、API端点、服务地址等。

使用:

  1. 在application.yml中,添加spring.profiles.active属性可以指定默认使用的配置环境
  2. 在application.yml中可以使用 ${...}指定需要使用的配置

例如:

server:
  port: 8080

spring:
  profiles:
    # 指定 默认使用的配置环境
    active: dev
  main:
    allow-circular-references: true
  datasource:
    druid:
      driver-class-name: ${sky.datasource.driver-class-name}
      url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: ${sky.datasource.username}
      password: ${sky.datasource.password}
sky:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    host: localhost
    port: 3306
    database: sky_take_out
    username: root
    password: 123456

配置加载顺序

内部配置

  1. file:./config/:当前项目下的/config目录下
  2. file:./当前项目的根目录
  3. classpath:/config/classpath的/config目录
  4. classpath:.classpath的根目录

加载顺序为上面的排列顺序,高优先级配置的属性会生效

外部配置

注意:当前项目下的/config目录下的配置文件和当前项目的根目录的配置文件因为不符合maven结构,不会被打入jar包

  1. java -jar xxxx.jar --springboot.port=端口号
  2. java -jar xxxx.jar --springboot.config.location=配置文件路径
  3. 将配置文件写在target根目录或target目录的config目录(比前面优先级高)下,可以使配置文件直接被读取


核心概念

依赖注入和控制反转

  • 内聚:软件中各个功能模块内部的功能联系

  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度

  • 软件设计原则:高内聚低耦合

  • 控制反转(Inversion of Control,简称IoC):

    • 控制反转是一种设计原则,它将对象的创建和管理的责任从应用程序代码转移到框架中。- 在控制反转中,框架负责创建和管理对象,并将它们注入到应用程序中。- 这样做的好处是,应用程序代码只需要关注业务逻辑,而不需要关心对象的创建和管理。
  • 依赖注入(Dependency Injection,简称DI):

    • 依赖注入是控制反转的一种实现方式。- 它通过将对象的依赖关系注入到对象中,来实现对象之间的解耦。- 在Spring中,依赖注入可以通过构造函数注入、属性注入或者方法注入来实现。- 通过依赖注入,我们可以方便地替换对象的依赖关系,提高代码的可测试性和可维护性。
  • Bean对象:Bean是Spring框架中的一个概念,它代表了一个由Spring容器创建、组装和管理的对象实例,Bean对象是构建应用程序的基本组成部分之一。

IOC&DI入门

  • @Componect:@Component是Spring框架中的一个注解,用于将一个Java类标记为可被Spring容器管理的组件。通过@Component注解,我们可以实现依赖注入和组件的自动化管理。
  • @Autowired:@Autowired是Spring框架中的一个注解,用于实现自动化的依赖注入。通过在类的字段、构造函数或方法上添加@Autowired注解,我们可以告诉Spring容器自动解析并注入相应的依赖对象。
  • @Resource:如果多个类实现了同一个接口,需要使用@Resource("名称")来指定一个类
  1. Service层及Dao层的实现类,交给IOC容器管理
  2. 为controller和service注入运行是依赖的对象

控制反转详解

  • Bean的声明

    • @Componect:声明Bean对象的基础注解,不属于以下三类时,用此注解- @Controller:@Componect的衍生注解,标注在控制器上- @Service:@Componect的衍生注解,标注在业务类上- @Respository:@Componect的衍生注解,标注在数据访问类上(由于与mybatis整合,使用较少)
  • 注意

    • 声明bean时,可以通过value指定bean的名称,如果没有指定,默认为类名首字母小写- 在springboot集成web开发中,声明控制器bean只能使用 @Controller
  • Bean组件扫描

    • 声明bean的四大注解要生效,还需要被组件扫描注解@ComponentScan扫描,默认扫描的范围是启动类所在包及其子包- 解决方法:编写代码要按照规范

依赖注入详解

  • Bean注入

    • @Autowired注解默认按照类型进行注入,如果存在多个相同类型的bean,就会报错- 解决方案
      • @Primary:在bean声明注解上面添加该注解,提升注入优先级,使当前bean生效。- @Qualifier("value"):在@Autowired注解下添加该注解,指定让value的bean生效(注意bean默认为类名首字母小写)- @Resource(name=""):通过该注解,指定要生效的bean的名字,不需要配合@Autowired

自动配置原理

介绍

Spring Boot 的自动配置原理基于 Spring Framework 的条件化配置和 Bean 的自动化装配。在 Spring Boot 应用启动时,会根据类路径中的 jar 包、Spring Beans 和各种可用的属性设置,自动配置 Spring 应用。

Spring Boot 的自动配置原理是通过一系列的条件注解和内建的自动配置类,根据应用程序中的类路径和用户配置,智能地配置 Spring 应用。这一机制极大地简化了 Spring 应用的配置过程,使得快速开发成为可能。

主要原理

  • 启动类注解:通常 Spring Boot 应用会有一个启动类,上面会标注 @SpringBootApplication 注解。这个注解是一个组合注解,它整合了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三个注解。

    • @Configuration:允许在上下文中注册额外的 Bean 或导入其他配置类。- @EnableAutoConfiguration:启动自动配置。- @ComponentScan:启用组件扫描,这样 Spring Boot 可以扫描到注解了 @Component、@Service、@Repository 和 @Controller 的类。
  • 条件注解:Spring Boot 使用了大量的条件注解(如 @ConditionalOnClass、@ConditionalOnBean、@ConditionalOnProperty 等),这些注解能够确保只有在特定条件满足时,相应的配置才会被激活。

  • 自动配置类:Spring Boot 的 spring-boot-autoconfigure 模块中包含了许多自动配置类,这些类都是可能被 Spring 容器加载的 @Configuration 类。它们会在应用启动时根据条件注解判断是否需要加载。

  • 属性注入:Spring Boot 允许通过 application.properties 或 application.yml 文件进行属性配置,这些属性会注入到 Bean 中,自动配置类也会根据这些属性来决定是否进行自动配置。

  • Bean 的默认行为:Spring Boot 为许多常用的第三方库提供了默认的 Bean 配置,比如嵌入式服务器(Tomcat, Jetty)、数据库连接池(HikariCP)等。如果用户没有提供自己的配置,Spring Boot 就会使用默认的 Bean。

  • 外部化配置:Spring Boot 支持多种外部化配置,包括环境变量、命令行参数、属性文件、YAML 文件等。这些配置可以在运行时动态替换默认值。

  • 运行阶段:Spring Boot 的自动配置发生在 Spring 应用的运行阶段,即在 Spring 应用上下文刷新之前。自动配置类会被加载并解析,然后根据条件注解来决定哪些配置应该被激活。

Spring Beans和Application Context

Spring Beans

Spring Beans 是由 Spring 容器管理的对象。

在 Spring 中,一个 Bean 是一个被 Spring 容器实例化、组装和管理的对象。这些对象通常被注入(Inject)到应用程序中的其他对象中。Spring Beans 通常是通过配置元数据定义的,配置元数据可以是 XML 文件、注解或 Java 配置类。

Spring Beans 具有以下特点:

  • 生命周期管理:Spring 容器负责管理 Bean 的整个生命周期,包括创建、依赖注入、初始化、使用和销毁。
  • 依赖注入:Spring 可以通过构造函数、setter 方法或字段直接注入 Bean 的依赖项。
  • 单例或多例:默认情况下,Spring Beans 是单例的,但也可以配置为每次请求时创建一个新的实例。
  • 自动装配:Spring 可以自动识别并装配 Bean 的依赖关系。

Application Context

Application Context 是 Spring 容器的一个接口,它提供了应用程序的配置和生命周期管理。它是 BeanFactory 的一个子接口,提供了更多的高级特性,如国际化支持、事件传播、Web 应用上下文等。

Application Context 的主要作用包括:

  • Bean 容器:它是 Spring Beans 的容器,负责创建、配置和管理 Bean。
  • 配置元数据加载:Application Context 负责加载应用程序的配置元数据,这些配置元数据定义了 Bean 及其依赖关系。
  • 生命周期管理:Application Context 管理着 Bean 的生命周期,包括初始化、使用和销毁。
  • 事件传播:Application Context 支持事件的发布和监听,允许 Bean 之间进行消息传递。
  • 国际化:Application Context 提供了国际化(I18n)支持,可以方便地处理不同地区的语言和格式。 Spring 提供了多种 Application Context 的实现,例如:
  • ClassPathXmlApplicationContext:从类路径下的 XML 文件中加载配置元数据。
  • FileSystemXmlApplicationContext:从文件系统中的 XML 文件中加载配置元数据。
  • AnnotationConfigApplicationContext:从注解配置类中加载配置元数据。

在实际应用中,通常会通过创建一个或多个 Spring Beans,并将它们注册到 Application Context 中,然后通过 Application Context 获取和管理这些 Bean。这样,开发者可以专注于业务逻辑的实现,而无需担心对象的创建和管理。



Springboot web

Web请求响应★

请求协议和响应协议在前置知识有详细介绍。

原理概述

  • BS架构

    • BS架构,即浏览器/服务器架构(Browser/Server Architecture),是一种网络架构模式,其中用户通过浏览器向服务器发送请求,服务器处理请求并将结果返回给浏览器,浏览器再负责展示这些结果给用户。- 这种模式的主要特点是用户界面是通过Web浏览器实现的,而数据和应用程序逻辑则存储在服务器上。 在BS架构中,客户端通常不需要安装任何专门的软件,只需一个可以访问Web的浏览器,这大大简化了客户端的维护工作,所有的升级、修改和维护操作几乎都在服务器端完成。
  • Spring Boot Web程序与浏览器通信的过程大致如下:

    • 启动Spring Boot应用程序:当Spring Boot应用程序启动时,它会自动配置嵌入式的Tomcat(或Jetty、Undertow等其他服务器)。- 创建控制器(Controller):在Spring Boot应用程序中,开发者定义控制器来处理HTTP请求。控制器中的方法通常用@RequestMapping注解来映射HTTP请求到相应的处理方法。- 接收HTTP请求:当用户在浏览器中输入URL并提交请求时,这个请求会发送到Spring Boot应用程序的嵌入式服务器。- 处理请求:服务器接收到请求后,根据URL路由到相应的控制器方法。控制器方法执行业务逻辑,并可能访问数据库或其他服务来获取数据。- 返回响应:控制器方法处理完请求后,会返回一个模型和视图(ModelAndView),或者直接返回一个对象,Spring Boot会自动将这个对象转换为JSON或XML等格式,然后服务器将这个响应发送回浏览器。- 浏览器渲染页面:浏览器接收到响应后,根据返回的数据(可能是HTML、CSS、JavaScript,或者是一个JSON数据),渲染页面并展示给用户。

发送请求

使用API开发工具,向“接口地址”发送各种请求。

“接口地址”通常指的是您要测试或开发的API的URL(统一资源定位符)。这个URL是您向API发送请求的地方,它指定了网络上的资源位置,使得客户端(如Postman或Apipost)能够与服务器上的API进行通信。

例如,在前面的HelloWorld程序中,我们api的url是这样的

http://localhost:8080/test

,而实际开发中,一个API的URL可能看起来像这样

https://api.example.com/resources/endpoint

请求包括请求方法、请求头和请求体。常用请求方法包括GET(获取资源)、POST(提交数据)、PUT(更新资源)、DELETE(删除资源),可以在请求头或请求体中传递参数或数据。

切换请求方法

打开Postman或Apipost,可以切换各种请求的方法。

携带参数

在前面的HelloWorld项目中,我们的url是

http://localhost:8080/test

,没有携带参数。实际开发中,我们需要携带各种参数或数据,向服务器发送更复杂的请求。

下面是各种参数的介绍与携带方式:

简单参数

简介:在向服务器发起请求时,向服务器传递的是一些普通的请求数据

http://localhost:8080/simpleParam?name=Tom&age=10

实体参数

简介:如果请求参数比较多且有一定关联,可以考虑将请求参数封装到一个实体类对象中。

方式:如果是简单的实体参数, 直接传递参数即可;如果实体比较复杂(多个实体类嵌套),就需要将所有实体类的属性传递

http://localhost:8080/simpleEntity?name=Tom&age=10
http://localhost:8080/simpleEntity?name=Tom&age=18&address.province=广东&address.city=广州

数组或集合参数

使用场景:在HTML的表单中,有一个表单项是支持多选的(复选框),可以提交选择的多 个值。

发送下面请求,java可以使用数组或集合接收

http://localhost:8080/arrayParam?hobby=game&hobby=java
//或
http://localhost:8080/arrayParam?hobby=game,java

json参数

处理请求

简介
  • 在Spring Boot中,@Controller@RestController 注解用于标识一个类作为Web层的控制器。这些控制器负责处理来自客户端的HTTP请求,并返回相应的响应。

@Controller
  • @Controller 是一个用于定义控制器的注解,主要用于处理HTTP请求并生成响应。当我们在一个类上使用 @Controller 注解时,表明这个类是控制器类,它的方法可以被Spring MVC框架调用以处理HTTP请求。
  • @Controller 主要用于处理传统的HTML请求,并生成视图,@Controller 可以返回任何类型的数据,包括字符串、模型对象、视图名称等。

@RestController
  • @RestController 是一个特殊的控制器,它是 @Controller@ResponseBody 的结合。这意味着,当你在一个方法上使用 @RestController 时,这个方法就不能返回视图名称,只能返回数据。

  • @RestController 主要用于处理RESTful请求,只能返回特定类型的数据,如JSON、XML或自定义的媒体类型。

  • @RequestMapping注解(及其专用变体)用于将HTTP请求映射到具体的处理器方法,可以定义在控制器类上,也可以定义在类里面的方法上,来指定一个方法或类来处理一个或多个HTTP方法和路径。

@RequestMapping 基本用法

@RequestMapping

注解的基本语法如下:

@RequestMapping(value = "/path", method = RequestMethod.GET)
public responseType handlerMethod() {
// ...
}

其中,

value

属性指定了请求的实际地址,

method

属性指定了请求的方法类型,可以是 GET、POST、PUT 或者 DELETE 等。

@RequestMapping注解可以接受多种属性,例如:

  • value:指定请求的实际地址,如"/path"。
  • method:指定请求的HTTP方法类型,如GET、POST、PUT、DELETE等。默认情况下,不指定method属性时,@RequestMapping会处理所有HTTP方法。
  • consumes:指定处理请求的提交内容类型(Content-Type),例如application/json、application/xml等。
  • produces:指定返回的内容类型,仅当请求头中的Accept类型中包含该指定类型才返回。
  • params:指定请求中必须包含某些参数值。
  • headers:指定请求中必须包含某些指定的header值。

@RequestMapping 专用变体

Spring Boot 提供了几个

@RequestMapping

的专用变体,这些变体都带有特定的 HTTP 请求方法,使得代码更加清晰易懂。

@GetMapping
@GetMapping

@RequestMapping

的 GET 请求专用版,其基本语法如下:

@GetMapping("/path")
public responseType handlerMethod() {
// ...
}
@PostMapping
@PostMapping

@RequestMapping

的 POST 请求专用版,其基本语法如下:

@PostMapping("/path")
public responseType handlerMethod() {
// ...
}
@PutMapping
@PutMapping

@RequestMapping

的 PUT 请求专用版,其基本语法如下:

@PutMapping("/path")
public responseType handlerMethod() {
// ...
}
@DeleteMapping
@DeleteMapping

@RequestMapping

的 DELETE 请求专用版,其基本语法如下:

@DeleteMapping("/path")
public responseType handlerMethod() {
// ...
}
  • @RequestParam 主要作用是从HTTP请求中获取参数值,并将这些值绑定到控制器方法的参数上。这些参数值可以来自查询字符串(例如,?name=John&age=30),也可以来自请求体(例如,POST请求的JSON或表单数据)

基本语法如下:

@RequestMapping("/hello")
public String helloWorld(@RequestParam(value = "name", required = false, defaultValue = "World") String name) {
// ...
}
@RequestParam(value = "name")

指定了要从请求中获取名为 "name" 的参数值,

required = false

表示这个参数不是必需的,如果没有找到这个参数,那么

name

的值将是

defaultValue

指定的默认值 "World"。

@RequestBody

@RequestBody 的作用

@RequestBody

的主要作用是将HTTP请求体中的数据自动转化为对象实例。这种转化过程通常是基于请求体中的JSON或XML格式数据进行的。

@RequestBody 的使用方法

@RequestBody

通常与

@RequestMapping

@PostMapping

等注解一起使用,其基本语法如下:

@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user){
System.out.println(user);
return "OK";
}

在这个例子中,

@RequestBody User user

指定了要从请求体中获取一个

User

对象。当请求体是JSON格式时,Spring Boot会自动将其转化为

User

对象。

@RequestBody 的属性

@RequestBody

有一个主要的属性:

  • value:请求参数中的名称。

注意事项

在使用

@RequestBody

时,需要注意以下几点:

  • @RequestBody 主要用于处理请求体中的数据,因此通常与POST请求一起使用。对于GET请求,由于没有请求体,所以不能使用 @RequestBody
  • @RequestBody 会将请求体中的JSON或XML格式数据转化为对象实例,这个过程需要依赖Jackson库,因此在项目中需要加入Jackson的依赖。
  • 在使用 @RequestBody 时,一定要确保参数类型与请求体中的数据类型相符,否则可能会出现数据解析错误的问题。
  • @Valid:当你希望对请求体进行验证时,可以在@RequestBody参数前添加@Valid注解。这会触发Spring MVC对请求体中的数据进行校验,校验规则通常由JSR-303/JSR-349/JSR-380 Bean Validation规范定义。

基本示例:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController //标识一个类作为Web层的控制器
public class TestController {
//    无参测试
    @RequestMapping(value = "/test")//将HTTP请求映射到test()方法
    public String test(){
        return "Hello, world!";
    }

}
各种参数

简单参数

  • 请求参数名与方法形参变量名相同
  • 会自动进行类型转换
@RequestMapping("/simpleParam")
  public String simpleParam(String name, Integer age) {
      log.info("name={}, age={}", name, age);
      return "OK";
  }
//必须携带name参数
@RequestMapping("/simpleParam")  
public String simpleParam(@RequestParam(name="name",required=true) String name, Integer age){  
  System.out.println(name+ ":" + age);  
  return "OK";  
}

实体参数

  • 当简单参数数量较多时,数据不利于维护
  • 可以将数据封装到一个实体类中,但是需要保证请求参数名与实体参数名保持一致
简单的实体参数
//    简单的实体类参数绑定测试
@RequestMapping("/simpleEntity")
  public String simpleEntity(User user) {
      log.info(user.toString());
      return "OK";
  }

//创建一个entity软件包,用来存放实体类
//src/entity/User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
  private Integer age;
}

较复杂的实体参数
  @RequestMapping("/complexEntity")
  public String complexEntity(User user) {
     log.info(user.toString());
      return "OK";
  }

//src/entity/Address.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
  private String province;
  private String city;
}
//src/entity/User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
  private Integer age;
  private Address address;
}

数组集合参数

  • 数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型参数即可接收参数

  • 集合参数:请求参数名与形参数组名称相同且请求参数为多个,需要用注解@RequestParam绑定参数关系

//    数组参数绑定测试
  @RequestMapping("/arrayParam")
  public String arrayParam(@RequestParam(value = "hobby") String[] hobbies){
     log.info(Arrays.toString(hobbies));
      return "OK";
  }
  @RequestMapping("/listParam")
  public String listParam(@RequestParam(value = "hobby") List<String> hobbies){
      log.info(hobbies.toString());
      return "OK";
  }

日期参数

  • 需要用注解@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")指定日期格式

请求:

http://localhost:8080/dateParam?updateTime=2002-11-11 00:00:00

接收:

@RequestMapping("/dateParam")  
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {  
  System.out.println(updateTime);  
  return "OK";  
}

json参数

  • JSON参数:JSON数据键名与形参对象属性名相同,定义POJO类型形参接收参数,需要用@RequestBody注解标识

接收:

  @RequestMapping("/jsonParam")
  public String jsonParam(@RequestBody User user){
      log.info(user.toString());
      return "OK";
  }

路径参数

  • 路径参数:通过请求URL直接传递参数,使用(...)来标识该路径参数,需要使用
  • 路径参数是URL中的一部分,用于标识资源的特定部分或属性。

接收单个路径参数:

@RequestMapping("/path/{id}")  
public String pathParam(@PathVariable Integer id) {  
  System.out.println(id);  
  return "OK";  
}

接收多个路径参数:

@RequestMapping("/path/{id}/{name}")  
public String pathParam2(@PathVariable Integer id,@PathVariable String name) {  
  System.out.println(id+":"+name);  
  return "OK";  
}

响应数据

在前面的例子中,我们都是返回了"Ok"字符串作为响应。

除了字符串,Spring Boot 还可以通过多种方式响应不同的数据格式,例如 JSON、XML、CSV 等。以下是一些常用的方法:

返回 JSON 数据

这是最常见的数据返回方式。在 Spring Boot 中,你可以使用

@RestController

@ResponseBody

注解来直接返回对象,这些对象会被自动转换成 JSON 格式。

@RestController
public class MyController {
    @GetMapping("/api/users")
    public List<User> getUsers() {
        return userService.findAll();
    }
}
返回 XML 数据

如果你需要返回 XML 格式的数据,你可以使用

@RequestMapping

注解,并设置

produces

属性为 "application/xml"。

@RequestMapping(value = "/api/users", produces = "application/xml")
public List<User> getUsers() {
    return userService.findAll();
}
返回 CSV 数据

CSV是一种简单的文件格式,用于存储表格数据,如电子表格或数据库。CSV 文件以纯文本形式存储表格数据,其中每行表示表格中的一行,而行中的每个单元格数据由逗号分隔。

CSV 文件通常用于数据交换,因为它们可以被多种不同的应用程序和系统读取和写入。你可以将电子表格数据导出为 CSV 文件,然后在数据库管理工具中导入这些数据。

要返回 CSV 格式的数据,你可以使用

@ResponseBody

ResponseEntity

@ResponseBody
@RequestMapping(value = "/api/users/csv", produces = "text/csv")
public ResponseEntity<String> downloadCsv() {
    String csvData = convertListToCsv(userService.findAll());
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=users.csv")
            .body(csvData);
}
其他格式

Spring Boot 还支持其他格式,如 HTML、PDF 等。你可以使用类似的方法,设置相应的

produces

属性,并返回正确的数据格式。

内容协商

Spring Boot 还支持内容协商,即根据请求的

Accept

头部自动选择合适的响应格式。

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserDetailsService userDetailsService;
    @GetMapping(produces = { "application/json", "application/xml" })
    public ResponseEntity<User> getUser(@RequestParam Long id) {
        User user = userDetailsService.findById(id);
        return ResponseEntity.ok(user);
    }
}
  1. 使用视图解析器:如果你的应用需要生成HTML页面,可以使用视图解析器。

在这个例子中,

"home"

是一个视图名称,Spring Boot会查找名为

"home"

的视图解析器,并将模型中的

"greeting"

属性填充到视图中。

  1. 自定义响应:如果你需要完全控制响应的内容,可以使用ResponseEntity类。

在这个例子中,

ResponseEntity.ok()

方法设置了响应的状态码为200,

.body()

方法设置了响应的内容。

@GetMapping("/")
public String home(Model model) {
  model.addAttribute("greeting", "Hello, World!");
  return "home";
}
@GetMapping("/users")
public ResponseEntity<List<User>> getUsers() {
  return ResponseEntity.ok()
          .body(userService.findAll());
}
统一响应结果

在实际开发中,为了确保前端和后端之间的交互更加便捷和统一,需要定义一套标准的响应格式。这样,前端开发者可以预期到后端返回的数据结构,从而简化前端逻辑处理,增强系统的稳定性和可维护性。

下面是一个简单的Result实体类,用来向前端统一响应结果:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {//统一响应结果封装类
    private Integer code ;
    private String msg;
    private Object data;
    
    public static Result success(){
        return new Result(1, "success", null);
    }
    public static Result success(Object data){
        return new Result(1, "success", data);
    }
    public static Result error(String msg){
        return new Result(0, msg, null);
    }
}

在控制器(Controller)的方法中,根据操作的结果返回统一格式的响应:

    @RequestMapping("/hello")
    public Result hello() {
        return Result.success("Hello, world!");
    }

上面只是简单的响应类,实际开发处理响应结果的方式可能更加复杂。

响应页面

可以通过return new ModelAndView返回一个新视图。

在thymeleaf详细介绍。


RESTful Web 服务

RESTful Web 服务是一种网络服务的架构风格,它使用 HTTP 协议作为通信手段,并利用 URI 来访问资源,它使用统一的接口和状态无关的请求来构建可扩展的Web服务。

分层架构★

介绍

实际开发中,springboot web程序除了处理请求,还有其他复杂的功能。例如:业务逻辑、数据访问、数据传输、异常处理、登录认证与授权、消息传递、缓存等。

我们不可能将所有功能放在一个程序中,就像我们学习java基础时,将一个个功能从main函数中封装到不同函数和不同文件中一样。我们需要将springboot web程序根据单一职责原则拆分为一个个组件,而这个组件也不过是一个个文件夹(软件包)。

单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。 这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护。

那么,我们要如何根据单一职责原则拆分springboot web程序的各个功能呢?

分层架构

软件架构风格。

分层架构的好处:

  • 每层都有明确的职责,使得应用程序结构清晰,便于开发和维护。
  • 此外,分层架构也有利于代码的重用和测试,因为每层的独立性较高,可以单独开发和测试。

分层架构通常包括以下层次:

  • 表示层:也称为 UI 层,负责与用户交互,展示数据和接收用户的输入。在 Web 应用程序中,这一层通常由前端技术和控制器(Controllers)组成,它接收前端发送的请求,对请求进行处理,并响应数据。
  • 业务逻辑层:也称为服务层,包含应用程序的核心业务逻辑。它处理来自表示层的请求,执行业务规则,并与数据访问层交互。
  • 数据访问层:负责与数据库或其他数据源交互,执行数据的持久化操作。这一层通常包含数据访问对象(Repositories)和实体(Entities)。
  • 基础设施层:提供支持应用程序运行的基础服务,如数据库连接、消息队列、邮件服务、缓存等。

目前我们只是开发基本的springboot web应用,只需要考虑前三层架构。

我们通常将表示层放在

src/contoller

软件包下;业务逻辑层放在

src/service

软件包下;数据访问层的数据库操作放在

src/mapper

软件包下(关系型数据库),实体类放在

src/entity

包下

分层架构的一些核心原则:

  1. 层次清晰:每个层次都有明确的职责和功能,层次的划分通常基于业务逻辑、数据存储、用户界面等不同的关注点。
  2. 单一职责:每个层次应该只负责一个特定的功能领域,避免层次的职责重叠或混淆。
  3. 层次间独立:每个层次应该尽可能地独立于其他层次,这意味着一个层次的变更不应该影响到其他层次。
  4. 下层对上层透明:下层的实现细节应该对上层隐藏,上层只能通过定义良好的接口与下层交互。
  5. 层次间通信:层次之间的通信应该是单向的,通常是从上层到下层,这样可以减少层次间的耦合。
  6. 标准化接口:层次之间的交互应该通过标准化的接口进行,这样可以使得各个层次可以独立开发和测试。
  7. 数据一致性:在层次之间传递数据时,应该保持数据的一致性,避免出现数据冗余或不一致的情况。
  8. 可扩展性:分层架构应该支持层次的扩展,允许在不影响其他层次的情况下,增加新的层次或修改现有层次。
  9. 可维护性:由于层次间的独立性,分层架构通常具有良好的可维护性,每个层次可以独立地进行修改和优化。
  10. 性能优化:分层架构允许对特定层次进行性能优化,而不影响其他层次,例如,可以在数据访问层优化数据库查询。
  11. 安全性:可以在不同层次实施不同的安全策略,例如,在表示层实现用户认证和授权,在数据访问层实现数据加密和访问控制。
  12. 事务管理:事务管理通常在业务逻辑层实现,确保业务操作的原子性、一致性、隔离性和持久性。
  13. 依赖注入:为了降低层次间的耦合,可以采用依赖注入的方式,使得上层依赖于接口而不是具体的实现。
表示层

表示层(通常是控制器Controller)接收用户的请求,并进行初步的验证,如请求格式、权限验证等。然后,控制器会将请求转发给业务逻辑层相应的服务(Service)进行处理。最后,控制器将处理后的的数据根据需要响应给前端。

前面已经介绍了如何接收与响应请求,下面介绍如何进行初步的验证

在Spring Boot的表示层,即控制器(Controller)中,初步验证通常包括以下几个方面的内容:

  1. 请求格式验证
    • 使用@Valid@Validated注解结合JSR 380(Bean Validation 2.0)提供的注解(如@NotNull@Size@Pattern等)对传入的请求实体(DTO)进行验证。- 使用@RequestBody注解确保接收到的数据是JSON格式,并使用@RequestParam@PathVariable等注解对请求参数进行注解驱动验证。- 对于查询参数,可以使用@Min@Max@DecimalMin@DecimalMax等注解进行数值范围的校验。
  1. 权限验证
    • 使用Spring Security等安全框架进行用户身份验证和授权。通过配置安全规则,可以确保只有具有相应权限的用户才能访问特定的API。- 在控制器方法上使用@PreAuthorize@Secured等注解来定义方法级别的安全约束。
  1. 请求方法验证
    • 使用@RequestMapping@GetMapping@PostMapping等注解确保请求方法(GET、POST、PUT、DELETE等)与控制器方法定义相匹配。
  1. 跨域请求验证
    • 对于跨域请求,可以使用@CrossOrigin注解或者在配置类中设置全局的跨域处理规则,以允许或限制跨域请求。
  1. 自定义验证
    • 可以在控制器方法中添加自定义验证逻辑,例如检查请求中的某些属性是否符合特定的业务规则。- 使用@ControllerAdvice@ExceptionHandler注解定义全局的异常处理逻辑,以捕获验证失败或其他异常情况,并返回适当的错误响应。
  1. 异常处理
    • 对于验证失败的情况,Spring会抛出MethodArgumentNotValidException异常。可以通过定义全局异常处理器来捕获这类异常,并返回统一的错误响应。
业务逻辑层

在Spring Boot Web开发中,

业务逻辑层

是应用程序的核心,它负责处理来自

表示层

的请求,执行业务规则,并与

数据访问层

进行交互。

业务逻辑层通常是由一系列的@Service注解的类组成,这些类包含了业务逻辑处理的公共方法和私有方法。这些服务类会注入对应的Repository接口,以进行数据访问操作。通过这样的设计,业务逻辑层为表示层提供了一个清晰的API接口,同时隐藏了数据访问的细节,使得业务逻辑更加集中和易于管理。

以下是业务逻辑层的一般处理流程:

  1. 接收请求:表示层控制器会将请求转发给业务逻辑层相应的服务(Service)进行处理。
  2. 参数验证:业务逻辑层接收到请求后,会对传入的参数进行进一步的验证,确保数据的完整性和合法性。这通常涉及到业务规则的校验,如数据格式、范围、依赖关系等。
  3. 业务处理:验证通过后,业务逻辑层会执行具体的业务操作。这可能包括复杂的数据计算、调用其他服务、执行特定的业务流程等。业务逻辑层的核心目的是实现应用程序的核心业务功能。
  4. 数据访问:在执行业务操作的过程中,业务逻辑层通常需要与数据访问层进行交互,以读取或写入数据。它会调用数据访问层的接口,而不直接与数据库打交道。这样的设计可以保证业务逻辑与数据访问的解耦,提高系统的可维护性。
  5. 事务管理:业务逻辑层还需要处理事务管理,确保一组操作的原子性。在Spring Boot中,通常会使用@Transactional注解来声明事务边界,保证业务操作的ACID特性。
  6. 返回结果:业务操作完成后,业务逻辑层会构造相应的响应数据,并将其返回给表示层。如果业务操作失败,业务逻辑层还需要负责处理异常,并将错误信息传递回表示层。
数据访问层

数据访问层比较复杂,可以使用mybatis或Spring Data JPA与数据库进行交互。这里先介绍mybatis,后面在学习spring Data时进一步了解

具体见下面的mybatis章节。

实际开发中,表示层会先接收前端的请求,然后调用业务逻辑层的对应方法;业务逻辑层进行对应处理后,会调用数据访问层,访问和处理相关数据;数据访问层处理后,将处理结果返回给业务逻辑层,业务逻辑层再放回给显示层。

这其实函数的相互的调用和返回,从而将一个冗余的项目根据职责拆分到不同的层中。

这里先做简单的了解,后面会给出相关示例。

MVC 架构★

介绍

MVC(Model-View-Controller)是一种软件设计模式,用于将应用程序的逻辑层和表现层分离。

Spring MVC 的主要组件:

  • 模型(Model):模型封装了应用程序的数据和业务逻辑。在 Spring MVC 中,模型通常是一个简单的 POJO(Plain Old Java Object)。
  • 视图(View):视图负责展示模型数据。在 Spring MVC 中,视图可以是 JSP、HTML、XML 或其他格式。
  • 控制器(Controller):控制器处理用户的请求,并调用模型来处理业务逻辑。然后,它将模型数据传递给视图进行展示。

在Spring Boot中,可以使用Spring MVC框架来实现MVC模式。Spring MVC提供了一组注解和类,用于定义和处理RESTful API的请求映射、请求参数绑定、数据验证、响应处理等。

分层架构与MVC架构的区别与联系:

  • 分层架构是一种宏观的架构风格,它涵盖了整个应用程序的结构;而 MVC 是一种微观的设计模式,它主要关注于用户界面的设计。
  • 在分层架构中,表示层可以采用 MVC 模式来实现,而业务逻辑层、持久层和数据库层则遵循分层架构的其他原则。
  • RESTful Web 服务是实现网络通信的一种方式,分层架构提供了一种组织代码的宏观视角,而 MVC 架构则是在表示层实现代码的一种具体模式。
实现

在Spring Boot中使用Spring MVC框架来实现MVC模式需要以下步骤:

  1. 添加Spring MVC依赖:在您的Spring Boot项目的构建文件(例如pom.xml)中添加Spring MVC的依赖项。可以使用Maven或Gradle构建工具来管理依赖关系。
  2. 创建控制器类:创建一个控制器类,使用@Controller注解进行标记。控制器类负责处理来自客户端的请求,并根据逻辑进行处理。
@Controller
public class MyController {
    // 处理GET请求的示例方法
    @GetMapping("/hello")
    public String sayHello() {
        return "hello";
    }
}
  1. 创建视图模板:为了展示用户界面,您可以创建一个视图模板。在Spring Boot中,默认使用Thymeleaf作为模板引擎,您可以使用Thymeleaf的语法来创建视图模板。
  2. 配置视图解析器:在应用程序的配置文件中,配置视图解析器,以将逻辑视图名称解析为实际的视图模板文件。
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.html
  1. 处理请求映射:在控制器类中,使用@RequestMapping或其他相关注解来定义请求映射。这些注解指定了URL路径和HTTP方法与处理方法的关联。
@Controller
public class MyController {
    @GetMapping("/hello")
    public String sayHello() {
        return "hello";
    }
}
  1. 处理请求参数:在处理方法中,您可以使用@RequestParam注解或其他相关注解来获取请求参数的值,并在方法中进行处理。
@Controller
public class MyController {
    @GetMapping("/hello")
    public String sayHello(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello";
    }
}
  1. 处理响应:处理方法可以返回视图名称、视图模型、JSON数据或其他类型的响应。根据您的业务需求,选择适当的返回类型。

RESTful端点

RESTful端点是指用于处理RESTful Web服务请求的特定URL路径。它们是客户端和服务器之间通信的入口点,通过HTTP方法(如GET、POST、PUT、DELETE等)和URL路径来定义对资源的操作。

在Spring Boot中,可以使用Spring MVC框架来创建RESTful端点。以下是创建RESTful端点的一般步骤:

  1. 创建控制器类:创建一个控制器类,使用@Controller或@RestController注解进行标记。通常,使用@RestController注解更方便,因为它将控制器类中的所有方法默认都标记为@ResponseBody,将返回的对象自动序列化为JSON格式。
@RestController
public class MyController {
    // 处理GET请求的示例方法
    @GetMapping("/users")
    public List<User> getAllUsers() {
        // 从数据库或其他数据源中获取用户列表
        List<User> userList = userService.getAllUsers();
        return userList;
    }

    // 处理POST请求的示例方法
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        // 创建新用户的逻辑
        User createdUser = userService.createUser(user);
        return createdUser;
    }

    // 处理PUT请求的示例方法
    @PutMapping("/users/{id}")
    public User updateUser(@PathVariable("id") Long id, @RequestBody User user) {
        // 更新用户的逻辑
        User updatedUser = userService.updateUser(id, user);
        return updatedUser;
    }

    // 处理DELETE请求的示例方法
    @DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable("id") Long id) {
        // 删除用户的逻辑
        userService.deleteUser(id);
    }
}
  1. 配置请求映射:使用@GetMapping、@PostMapping、@PutMapping、@DeleteMapping等注解来定义请求映射。这些注解指定了RESTful端点的URL路径和HTTP方法。
  2. 处理请求参数:根据需要,您可以使用@RequestParam、@PathVariable等注解来获取请求参数的值,并在方法中进行处理。
  3. 处理响应:根据您的业务需求,可以返回不同类型的响应。如果使用@RestController注解,方法的返回值将自动序列化为JSON格式并作为HTTP响应返回给客户端。

这是一个简单的示例,演示了如何创建基本的RESTful端点。还可以使用其他注解和功能来处理异常、版本控制、分页、过滤等更复杂的场景。

异常处理

程序中不可避免的会遇到异常,异常可能会出现不符合api文档中的响应结果

  • 处理方法

    • 法一:在controller中进行try...catch...(不推荐)- 法二:全局异常处理器
  • 全局异常处理器

    • 定义- 注解@RestContrlorAdvice@ExceptionHandler``````@RestContrllorAdvice=@ContrllorAdvice+@RespponseBody``````@RespponseBody会将结果转换为json格式响应会前端
@RestControllerAdvice  
public class GlobalExceptionHandler {  
  
//    捕获所有异常  
    @ExceptionHandler(Exception.class)  
    public Result ex(Exception ex){  
        ex.printStackTrace();  
        return Result.error("对不起,操作失败,请联系管理员");  
    }  
}


MyBatis

介绍

  • jdbc 缺点:

    • 硬编码,代码量大- 操作繁琐- 资源浪费,性能低

MyBatis是一个强大的持久层框架,它内部封装了对JDBC的操作,让开发者只需要关注SQL本身,而不需要处理繁琐的数据库连接、SQL构造、结果集处理等JDBC代码。

  • 持久层:指的是就是数据访问层(dao),是用来操作数据库的。

以下是使用MyBatis的数据访问层的工作流程:

  1. 配置MyBatis:在Spring Boot项目中,首先需要在application.propertiesapplication.yml文件中配置MyBatis的相关设置,如mapper文件的位置、数据源信息等。
  2. 定义实体类:与JPA类似,需要定义实体类(Entity Classes),这些类映射到数据库中的表。MyBatis可以使用注解或XML配置文件来指定映射关系。
  3. 创建MyBatis的XML映射文件或使用注解:定义Mapper接口,这些接口的方法对应于数据库操作。每个方法通常对应一个SQL语句。然后,创建XML配置文件或使用注解来编写SQL语句和映射结果。MyBatis还支持动态SQL,可以根据条件灵活构建SQL语句。
  4. 事务管理:MyBatis的事务管理可以通过Spring的声明式事务管理来实现。在Spring Boot中,可以使用@Transactional注解来声明事务边界。
  5. 异常处理:MyBatis在执行SQL操作时可能会遇到异常,如SQL执行错误、参数错误等。这些异常可以在Service层或Controller层捕获并转换为业务逻辑层可以理解的异常。

参数占位符

  • #{...}

    • 执行sql时,会将#{...}替换为?,生成预编译sql,会自动设置参数值- 优势:性能更高,更安全- 参数传递都使用
  • ${...}

    • 拼接sql,直接将参数拼接在sql中,存在sql注入问题- 对表名、列表进行动态设置时使用

Spring Boot整合MyBatis进行数据库操作时,可以使用注解或者XML文件来编写SQL语句。

  • 对于简单的SQL操作,推荐使用注解,因为它更加简洁、直观。
  • 对于复杂的SQL操作,尤其是需要使用**动态SQL(可变sql)**的场景,推荐使用XML文件,因为它更加灵活、易于维护。

IDEA推荐安装插件mybatisX,方便xml映射文件操作。

基本操作

  • 目标

    • 使用Mybatis对mysql数据库进行增删改查

准备

创建数据库、表,配置springboot工程

  1. 在Navicat中创建数据库mybatis
  2. 创建student表
CREATE TABLE students (
  id INT PRIMARY KEY AUTO_INCREMENT,  -- 学生ID,主键,自动递增
  name VARCHAR(50) NOT NULL,          -- 学生姓名,不为空
  gender ENUM('男', '女') NOT NULL,  -- 学生性别,枚举类型,不为空
  age INT,                            -- 学生年龄
  class_name VARCHAR(50)            -- 学生所在班级名称
);

INSERT INTO students (name, gender, age, class_name) VALUES ('张三', '男', 18, '高三一班');
INSERT INTO students (name, gender, age, class_name) VALUES ('李四', '男', 17, '高三二班');
INSERT INTO students (name, gender, age, class_name) VALUES ('王五', '女', 18, '高三一班');
INSERT INTO students (name, gender, age, class_name) VALUES ('赵六', '女', 17, '高三二班');
  1. /src/main/java/com.example.demo(你的包名)下创建service,controller,mapper,entity包,用于三层架构 /src/main/resouces下创建mapping文件夹,用于存放xml映射文件

  1. 在pom.xml中引入依赖

...
<dependencies>  
  ...
  <!--    mysql依赖    -->
  <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
  <!--   mybatis依赖     -->
  <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
  <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>3.0.3</version>
            <scope>test</scope>
        </dependency>
</dependencies>
...
  1. 配置MyBatis(数据库连接信息)(在application.yml中)
spring:
  datasource:
#    驱动
    driver-class-name: com.mysql.cj.jdbc.Driver
#    数据库地址
    # url: jdbc:mysql://localhost:3306/mybatis # 可以简写为这个,其中mybatis是数据库名称
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#    用户名
    username: root
#    密码
    password: 123456
    
mybatis:
  configuration:
    # 驼峰命名
    map-underscore-to-camel-case: true
?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
  • 这些是连接数据库时使用的参数。

  • useUnicode=true 指定了使用Unicode字符集。

  • characterEncoding=utf-8 指定了使用UTF-8编码。

  • useSSL=false 指定了不使用SSL加密。

  • serverTimezone=Asia/Shanghai 指定了服务器时区为Asia/Shanghai(中国),这通常用于解决时区相关问题。

  1. 其他配置

配置日志输出到控制台:

#配置mybatis日志,指定输出到控制台(记住log即可)  
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

连接数据库

  1. 在entity包下创建实体类Student.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private int age;
    private char gender;
    private String className;
}

注解操作

在mapper包下创建一个StudentMapper.java文件,用于数据库操作。

使用两个注解:

  • @Mapper注解用于标记MyBatis的映射器接口。当Spring Boot项目启动时,它会自动扫描带有@Mapper注解的接口,并创建它们的全局代理实例。
  • @Repository注解是Spring框架提供的一个注解,用于标记数据访问层的组件。当Spring容器扫描到带有@Repository注解的接口时,它会自动创建其代理实例,并实现基于接口的依赖注入。
查询

在数据层访问层方法前添加

@Select

注解,在注解在传递sql语句进行查询

@Mapper
@Repository
public interface StudentMapper {
    @Select("SELECT * FROM students WHERE gender = '女';")
    public List<Student> findGirl() ;

    @Select("SELECT * FROM students WHERE id = #{id};")
    List<Student> findById(int id);//不加public也可,因为interface中的方法都是公用的
}

在/test包下的测试类测试


@SpringBootTest
class XXXApplicationTests {
    @Autowired
    public StudentMapper studentMapper;

    @Test
    void getGirl() {
        List<Student> students = studentMapper.findGirl();
        for (Student student : students) {
            System.out.println(student);
        }
    }

}
插入

添加

@INSERT

注解,在注解在传递sql语句进行插入

@Insert("INSERT INTO students(name,gender,age,class_name) values (#{name},#{gender},#{age},#{className})" )
    public void insertStudent(Student student);

进行测试

 @Test
    void insertStudent() {
        Student student = new Student();
        student.setName("FL");
        student.setGender('男');
        student.setAge(20);
        student.setClassName("计算机");
        if (studentMapper.insertStudent(student) > 0) {
            System.out.println("插入成功");
        }else {
            System.out.println("插入失败");
        }
    }
  • 主键返回 在Mapper.java添加注解Option就可以返回id了
//    插入  
    @Options(useGeneratedKeys = true,keyProperty = "id")  
删除

@Delete

注解,在注解在传递sql语句进行删除

@Delete("DELETE FROM students WHERE id = #{id};")
    public int deleteById(int id);

进行测试

   @Test
    void deleteStudent() {
        if (studentMapper.deleteById(5) > 0) {
            System.out.println("删除成功");
        }else {
            System.out.println("删除失败");
        }
    }
修改

@Update

注解,在注解在传递sql语句进行修改

@Update("UPDATE students SET class_name = #{className} WHERE id = #{id};")
    public int updateClassName(int id, String className);

测试

@Test
    void updateStudent(){
        if (studentMapper.updateClassName(3,"高三二班") > 0) {
            System.out.println("更新成功");
        }else {
            System.out.println("更新失败");
        }
    }

数据封装

  • 如果实体类属性名和数据库表查询返回的字段名一致,就会自动封装,否则不会自动封装

  • 解决方法:

    • 给字段取别名- 使用注解@Results@Result手动封装- 开启mybatis驼峰命名自动映射
    //法一:给字段取别名  
    @Select("select id, username, password, name, gender, image, job, entrydate, " +  
            "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}")  
    public Emp getById1(Integer id);  
//法二:  使用注解手动封装
  
    @Results({  
            @Result(column= "dept_id", property = "deptId"),  
            @Result(column= "create_time", property = "createTime"),  
            @Result(column= "update_time", property = "updateTime")  
    })  
    @Select("select id, username, password, name, gender, image, job, entrydate, " +  
            "dept_id, create_time, update_time from emp where id = #{id}")  
    public Emp getById2(Integer id);

法三:
在application.yml中设置(推荐)

#开启mybatis驼峰命名自动映射  (记住camel)
mybatis:
  configuration:
    # 驼峰命名
    map-underscore-to-camel-case: true


数据库连接池

介绍

  • 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
  • 程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象 ,允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以 复用)
  • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
  • 数据库连接池的好处:
    1. 资源重用2. 提升系统响应速度3. 避免数据库连接遗漏
  • 标准接口:

    • 官方(sun)提供了数据库连接池标准(javax.sql.DataSource接口)- 功能:获取连接public Connection getConnection() throws SQLException;- 常见产品
      • Hikari (springboot默认)- Druid (智能、准确、误报率低
  • 可以在控制台查看使用的连接池

切换数据库连接池

  1. 在pom.xml中引入依赖
<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>最新版本</version>
        </dependency>
  1. 在application.yml中配置数据库信息
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource # type可以指定数据源类型

mybatis:
  configuration:
    map-underscore-to-camel-case: true

Druid数据源专有配置

常用的Druid数据源专有配置:

  • initialSize:初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时。
  • minIdle:最小连接池数量。
  • maxActive:最大连接池数量。
  • maxWait:获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock为true使用非公平锁。
  • timeBetweenEvictionRunsMillis:间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒。
  • minEvictableIdleTimeMillis:一个连接在池中最小生存的时间,单位是毫秒。
  • validationQuery:用来检测连接是否有效的SQL,要求是一个查询语句,常用SELECT 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
  • testWhileIdle:建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
  • testOnBorrow:申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
  • testOnReturn:归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
  • poolPreparedStatements:是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说Oracle。在MySQL下建议关闭。
  • maxPoolPreparedStatementPerConnectionSize:poolPreparedStatements为true时生效,在maxOpenPreparedStatements和maxPoolPreparedStatementPerConnectionSize中,优先生效maxPoolPreparedStatementPerConnectionSize。
  • filters:属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:stat(统计)、log4j(日志)、wall(防火墙)。
  • connectionProperties:属性类型是字符串,通过别名的方式配置连接属性,比如配置druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000表示合并SQL统计,并且记录慢SQL。

示例:

spring:
  datasource:
    # 前面配置省略
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

XML映射文件

基本操作

示例:

  1. 修改application.yml,配置mybatis映射
mybatis:
  configuration:
    map-underscore-to-camel-case: true
  # 配置mapper xml文件所在路径
  mapper-locations: classpath:mapping/*.xml
  # 配置实体类所在位置
  type-aliases-package: com.fl.boot.entity
  1. 在前面注解操作的基础上,去除StudentMapper.java方法上的所有注解
@Mapper
@Repository
public interface StudentMapper {
    public List<Student> findGirl() ;

    public List<Student> findById(int id);

    public int insertStudent(Student student);

    public int deleteById(int id);

    public int updateClassName(int id, String className);
}
  1. 在mapping包下创建StudentMapper.xml(与StudentMapper.java接口一致)
<?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">
<!--    上面的依赖可以从mybatis中文网复制    -->
<!-- namespace属性为Mapper接口全限定名一致 -->
<mapper namespace="com.fl.boot.mapper.StudentMapper">

</mapper>
  1. 在StudentMapper.xml的 <mapper> 中编写sql
<?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">
<!--    上面的依赖可以从mybatis中文网复制    -->
<!-- namespace属性为Mapper接口全限定名一致 -->
<mapper namespace="com.fl.boot.mapper.StudentMapper">
<!--  id为Mapper接口中对应的方法名,resultType为返回的实体全类名  -->
    <select id="findGirl" resultType="com.fl.boot.entity.Student">
        SELECT * FROM students WHERE gender = '女'
    </select>

    <select id="findById" resultType="com.fl.boot.entity.Student">
        SELECT * FROM students WHERE id = #{id}
    </select>

    <insert id="insertStudent" parameterType="com.fl.boot.entity.Student">
        insert into student(id,sname,classId,birthday,email)
        values (#{id},#{sname},#{classId},#{birthday},#{email});
    </insert>

    <delete id="deleteById" parameterType="int">
        DELETE FROM students WHERE id = #{id}
    </delete>

    <update id="updateClassName"  >
        UPDATE students SET class_name = #{className} WHERE id = #{id};
    </update>

</mapper>

讲解:

  • <mapper>标签的namespace属性用于指定Mapper接口,必须传入全限定名
  • 使用 <insert><delete><update><select>进行增删改查
  • (公有)id属性用于指定Mapper接口中的方法,必须和要指定的方法一致
  • (公有)parameterType:指定输入参数的类型,可以是简单类型、Map、POJO等。注意,参数只能传入一个,如果要传入多个参数,需要在Mapper.java中指定参数

如前面的updateClassName方法,需要修改为:

public int updateClassName(@Param("id") int id, @Param("className") String className);
  • <insert>特性:

    • useGeneratedKeys:设置为true时,MyBatis会使用JDBC的getGeneratedKeys方法来获取由数据库自动生成的主键值(如自增字段)。- keyProperty:指定主键属性,当useGeneratedKeys设置为true时,MyBatis会将获取到的主键值赋给这个属性。- keyColumn:指定主键列名,与keyProperty配合使用。
  • <delete><update>特性(只有两个共有属性)

  • <select>特性

    • resultType:指定查询结果的类型,可以是简单类型、Map、POJO等。- resultMap:用于引用外部定义的<resultMap>元素,用于复杂的结果映射。- fetchSize:指定数据库驱动程序每次批量返回的结果行数。- timeout:设置超时时间,等待数据库返回结果的秒数。

在Test类进行测试:

@SpringBootTest
class XXXApplicationTests {
    @Autowired
    public StudentMapper studentMapper;
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    void getGirl() {
        List<Student> students = studentMapper.findGirl();
        for (Student student : students) {
            System.out.println(student);
        }
    }

    @Test
    void insertStudent() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try{
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            Student student = new Student();
            student.setName("FL");
            student.setGender('男');
            student.setAge(20);
            student.setClassName("计算机");
            if (studentMapper.insertStudent(student) > 0) {
                System.out.println("插入成功");
            }else {
                System.out.println("插入失败");
            }
            sqlSession.commit();
        }
        finally {
            sqlSession.close();
        }
    }

    @Test
    void deleteStudent() {//输出删除失败
        //直接获取操作数目
        if (studentMapper.deleteById(5) > 0) {
            System.out.println("删除成功");
        }else {
            System.out.println("删除失败");
        }
    }

    @Test
    void updateStudent(){
            if (studentMapper.updateClassName(4,"高三一班") > 0) {
                System.out.println("更新成功");
            }else {
                System.out.println("更新失败");
            }
    }

}

补充:复制全类名

  1. 右键需要复制全类名的实体类文件(如Student.java)
  2. 点击复制类名/引用
  3. 点击复制引用
  4. 快捷键:ctrl+alt+shift+c

关系映射

在xml映射文件中,可以用

<resultMap>

定义数据库表记录和Java对象之间的映射关系。

可以使用<association>处理一对一的关系,即一个Java对象的属性对应另一个Java对象。对于一对多的关系,应该使用<collection>元素。

<resultMap id="commodityResultMap" type="com.fl.boot.entity.Commodity">
  <id column="Pid" property="pid" />
  <result column="Tcode" property="tcode" />
  <result column="Scode" property="scode" />
  <result column="Pname" property="pname" />
  <result column="PPrice" property="pPrice" />
  <result column="Stocks" property="stocks" />
  <result column="TextAdv" property="textAdv" />
  <result column="LivePriority" property="livePriority" />
  <result column="Evaluate" property="evaluate" />
  <result column="UpdateTime" property="updateTime" />
</resultMap>
<id>

为数据表的主键,

<result>

为其他结果列,

column

为数据表的列名,

property

为实体类的属性名

在MyBatis的<select>或其他查询语句中,你可以使用resultMap属性来引用这个<resultMap>,而不是直接在SQL语句中指定列名和属性名的对应关系。

动态sql

介绍

前端可能有上面这样的筛选列表,那么发送的请求中,可能没有参数,这时我们需要将所有数据返回给前端,而如果含有一个或多个参数,这时我们就需要返回特定筛选条件的数据。我们可以使用动态SQL处理这样参数会变化的SQL语句。

动态SQL是指根据程序运行时的条件动态生成的SQL语句。MyBatis通过XML映射文件或注解提供了一系列强大的动态SQL功能,允许你根据不同的条件构建不同的SQL语句。这使得MyBatis能够灵活地应对复杂的数据库操作需求。

动态SQL的主要特点是能够在SQL语句中包含条件判断、循环和表达式计算等逻辑,从而实现SQL的动态构建

动态SQL元素

MyBatis提供了多种动态SQL元素:

<where>:用于生成WHERE子句,如果所有条件都不满足,则不会生成WHERE子句,同时还会去除多余的and或or 。

<if>:根据条件判断包含SQL片段,使用test属性进行条件判断/如果条件为true,则拼接SQL 。可以单独使用,适用于简单的条件判断。

<choose>、<when>、<otherwise>:类似于Java中的if-else语句,用于选择性地包含SQL片段。不能单独使用,适用于需要多个条件选择的情况。<when>相当于if语句,<otherwise>相当于else语句。

示例:查询学生,可以指定性别,名字的姓,年龄和班级

  1. 在StudentMapper.java中定义方法
//将int类型改为Integer,char类型改为Character,否则不能为null
public List<Student> findStudent(@Param("name") String name,@Param("gender") Character gender,@Param("age") Integer age,@Param("className") String className);
  1. 修改StudentMapper.xml,添加xml映射
<select id="findStudent" resultType="com.fl.boot.entity.Student">
        SELECT * FROM students
        <where>

            <if test="name != null">
                name LIKE CONCAT('%',#{name},'%')
            </if>
            <if test="gender != null">
                AND gender = #{gender}
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
            <if test="className != null ">
                AND class_name = #{className}
            </if>
        </where>
    </select>

<select id="findStudent" resultType="com.fl.boot.entity.Student">
        SELECT * FROM students
        <where>
            <choose>
                <when test="name != null">
                    name LIKE CONCAT('%',#{name},'%')
                </when>
                <when test="gender != null">
                    AND gender = #{gender}
                </when>
                <when test="age != null">
                    AND age = #{age}
                </when>
                <when test="className != null ">
                    AND class_name = #{className}
                </when>
                 <otherwise>
                    AND 1=1
                </otherwise>
            </choose>

        </where>
    </select>

测试:

@Test
    void findStudent() {
        List<Student> students = studentMapper.findStudent(null, '男', null, "高三一班");
        for (Student student : students) {
            System.out.println(student);
        }
        System.out.println("===============SUCCESS!================");
    }

**<foreach>**:用于遍历集合,生成批量SQL语句,如IN查询。

  • foreach 属性:

    • collection:遍历的集合 item:遍历出来的元素 separator:分隔符 open:遍历开始前拼接的sql片段 close:遍历结束后的sql片段
  1. 定义方法
public List<Student> findStudentByIdList(@Param("ids") List<Integer> idList);
  1. 添加xml映射
<select id="findStudentByIdList" resultType="com.fl.boot.entity.Student">
        SELECT * FROM students
        WHERE id IN
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>
  1. 测试
 @Test
    void findStudentByIdList() {
        List<Integer> idList = List.of(1, 2, 3);
        List<Student> students = studentMapper.findStudentByIdList(idList);
        for (Student student : students) {
            System.out.println(student);
        }
        System.out.println("===============SUCCESS!================");
    }

<set>:用于动态生成UPDATE语句的SET子句,只更新改变的字段。

<update id="updateStudent" parameterType="com.fl.boot.entity.Student">
  UPDATE students
  <set>
    <if test="name != null">
      name = #{name},
    </if>
    <if test="age != null">
      age = #{age},
    </if>
    <if test="gender">
      gender = #{gender},
    </if>
    <if test="className">
      class_name = #{className},
    </if>

  </set>
  WHERE id = #{id}
</update>
<trim>

元素用于自定义字符串的截取规则,可以用来去除或添加前缀、后缀,以及根据需要包含或忽略指定的字符串。它可以用于WHERE子句,也可以用于SET子句或其他任何需要根据条件动态生成SQL的部分。

<trim>提供了更多的控制选项,但因此也更为复杂。<where>则是一种专门为WHERE子句设计的简化元素。

<trim>的控制选项:

  • prefix:指定添加到生成的SQL片段之前的前缀。
  • 示例:<trim prefix="(" suffix=")" suffixOverrides=",">
  • suffix:指定添加到生成的SQL片段之后的后缀。
  • 示例:<trim prefix="(" suffix=")" suffixOverrides=",">
  • prefixOverrides:指定需要被前缀替换掉的字符串。
  • 示例,将所有的逗号都替换为左括号:<trim prefix="(" prefixOverrides="," suffix=")" suffixOverrides=",">
  • suffixOverrides:指定需要被后缀替换掉的字符串。
  • 示例,将所有的逗号都替换为右括号:<trim prefix="(" suffix=")" suffixOverrides=",">

示例:将前面的findStudent的where改为使用trim

<select id="findStudent" resultType="com.fl.boot.entity.Student">
        SELECT * FROM students
        <trim prefix="WHERE" prefixOverrides="AND |OR">

            <if test="name != null">
                name LIKE CONCAT('%',#{name},'%')
            </if>
            <if test="gender != null">
                AND gender = #{gender}
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
            <if test="className != null ">
                AND class_name = #{className}
            </if>
        </trim>
    </select>
<sql><include>

作用:将重复的sql提取成可以重用的sql片段

<sql>

:用于定义可以在其他SQL元素中重用的SQL片段。它通常用于定义那些在多个查询中重复出现的代码,比如表名、列名或者复杂的计算表达式。你甚至可以使用它定义整个SQL语句,不过实际开发中用的很少。

<include>

:通过属性refid,指定包含的sql片段

在下面的例子里,使用<sql>片段替换了搜索列

*

<sql id="selectColumns">
        id,name,age,gender,class_name
    </sql>

<select id="findGirl" resultType="com.fl.boot.entity.Student">
        SELECT <include refid="selectColumns"/>
        FROM students
        WHERE gender = '女'
    </select>

事务管理

介绍

在数据库阶段我们已学习过事务了,我们知道:

事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。确保了一系列数据库操作要么全部成功执行,要么全部失败回滚,从而保证了数据的一致性和完整性。

事务的操作主要有三步:

  1. 开启事务(一组操作开始前,开启事务):start transaction / begin ;
  2. 提交事务(这组操作全部成功后,提交事务):commit ;
  3. 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;

Spring Boot和MyBatis的整合简化了传统的事务管理方式,使得开发者能够更容易地实现事务控制,同时保持代码的简洁性和可维护性。

@Transactional注解

@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。

@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。

@Transactional注解书写位置:

  • 方法

    • 当前方法交给spring进行事务管理
    • 当前类中所有的方法都交由spring进行事务管理
  • 接口

    • 接口下所有的实现类当中所有的方法都交给spring 进行事务管理

常在业务方法上加上 @Transactional 来控制事务

在yml配置文件中开启事务管理日志

#spring事务管理日志  
logging:  
  level:  
    org.springframework.jdbc.support.JdbcTransactionManager: debug

事务管理的步骤

下面是Spring Boot使用MyBatis进行事务管理的步骤:

  1. 开启事务管理:

在Spring Boot应用的主类或者配置类上添加@EnableTransactionManagement注解,开启事务管理功能。

@SpringBootApplication
@EnableTransactionManagement
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 使用@Transactional注解并执行数据库操作:

在需要事务管理的Service层方法上添加@Transactional注解,Spring会自动管理事务的提交和回滚。

在Service层方法中,通过注入的Mapper接口调用数据库操作方法。

@Service
public class StudentService {
    @Autowired
    public StudentMapper studentMapper;
//默认情况下,只有出现RunTimeException才回滚事务,rollbackFor属性用于控制出现指定异常类型,都回滚事务
    @Transactional(rollbackFor = Exception.class)
    public void updateStudent(int id, String name, Integer age, Character gender, String className) {
        studentMapper.updateStudent(id, name, age, gender, className);
        //模拟异常
        int i = 1 / 0; 
    }
}
  1. 处理异常和事务回滚:

如果方法执行过程中发生异常,Spring会检测到并回滚事务,确保数据的一致性。

测试:

@Test
    void updateStudent2(){
        studentService.updateStudent(7,"蔡徐坤",null,null,"高三三班");
    }

注意,如果去掉步骤2的 @Transactional注解,那么即使报错该sql语句也会执行。而添加后开启了事务管理,报错后会回滚。

异常处理

介绍

前面的StudentService.java中,我们使用1 / 0模拟了异常,实际使用mybatis时,会有其他sql特有异常,例如插入重复索引的数据,或删除不存在的数据等

常见异常:

  • SQLException:这是最基本的异常,代表数据库级别的错误。当执行数据库操作时出现任何问题时,MyBatis 可能会抛出这个异常。
  • SQLDataException:当尝试将不兼容的数据类型插入表中,或者尝试对字符串使用非法数值操作时抛出。
  • SQLIntegrityConstraintViolationException:当违反了完整性约束(如主键约束、唯一约束或外键约束)时抛出。
  • SQLInternalError:由数据库抛出的内部错误,通常是数据库服务器的错误。
  • SQLNonTransientConnectionException:当无法打开数据库连接时抛出,并且错误是持久的,不是临时的。
  • SQLSyntaxErrorException:当 SQL 语法错误发生时抛出。
  • SQLTimeoutException:当数据库操作超时时抛出。
  • SQLTransactionRollbackException:当发生事务回滚时抛出。
  • SQLTransientConnectionException:当无法打开数据库连接时抛出,并且错误是临时的。

我们可以在Test类添加try...catch...语句,可是以后业务一旦复杂起来,需要这样处理的方法多了怎么办呢?要去一个一个try-catch吗?多麻烦啊!

所以,我们使用异常拦截器进行全局异常捕获。

步骤

  1. 在src/main/java/com.fl.boot包下创建一个exception包(与三层架构同级),创建一个GlobalExceptionHandler.java文件

  2. 添加注解

    1. 全局异常处理:通过在 @ControllerAdvice 标注的类中定义方法,并使用 @ExceptionHandler 注解指定要处理的异常类型,使用value属性指定要捕获的异常类型。2. @RestContrllorAdvice是 @ControllerAdvice 的特殊化,它还包括了 @ResponseBody 注解(@RestContrllorAdvice=@ContrllorAdvice+@RespponseBody),使得返回值直接作为响应体,无需视图解析。
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public String exceptionHandler(Exception e){
        System.out.println("全局异常捕获>>>:"+e);
        return "全局异常捕获,错误原因>>>"+e.getMessage();
    }
}
  1. 在contoller包下编写一个StudentContoller.java,用于接收请求
@RestController
public class StudentContoller {
     @Autowired
     public StudentService studentService;

     @GetMapping("/updateStudent")
     public String updateStudent(int id, String name, Integer age, Character gender, String className) {
         //调用service层updateStudent方法,因为该方法存在算数异常,会被全局异常处理器捕获并处理
         studentService.updateStudent(id, name, age, gender, className);
         
         return "更新成功";
     }
}
  1. 使用postman发送请求
http://localhost:8080/updateStudent?id=4&name=FL
  1. 观察控制台:

注意,如果直接使用测试类进行测试,该错误不会被全局异常处理器捕获

@Test
void updateStudent2(){
    studentService.updateStudent(7,"蔡徐坤",null,null,"高三三班");
}



Mybatis-Plus

配置

springboot3.2配置如下(其他版本可能不同)

<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.5</version>
            <exclusions>
                <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.3</version>
        </dependency>

创一个application.yml,进行配置:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456

# mybatis-plus配置
mybatis-plus:
  configuration:
    # 下划线转驼峰,默认情况下mybatis-plus是开启的,而mybatis是关闭的
    map-underscore-to-camel-case: true

# 日志配置
logging:
    level:
      包名.mapper: debug

常用注解

MyBatis-Plus 是在 MyBatis 的基础上进行了增强的 ORM 框架,它简化了 CRUD 操作,并提供了一些额外的特性来增强开发体验。

以下是一些常用的 MyBatis-Plus 注解:

在实体类中:

  1. @TableName:用于指定实体类映射的数据库表名。
@TableName("t_user")
public class User {
    // ...
}
  1. @TableId:用于指定实体类的主键字段,支持多种主键类型,如自增、雪花算法等。
@TableId(value = "id", type = IdType.AUTO)
private Long id;
  1. @TableField:用于指定实体类字段与数据库表列的映射关系。
@TableField(value = "username", fill = FieldFill.INSERT)
private String username;
  1. @TableLogic:用于指定实体类的逻辑删除字段,支持自动填充逻辑删除值。
@TableLogic
private Integer deleted;
  1. @EqualsAndHashCode:简化 equals 和 hashCode 方法的实现,因为 MyBatis-Plus 会自动根据实体类的字段生成这两个方法的实现。这样,你就不需要手动编写这些方法的实现,也不需要处理继承关系。
@EqualsAndHashCode(callSuper = false)
public class MyEntity extends AnotherEntity {
    // ...
}

在数据层中:

  1. @Select:用于自定义 SQL 查询语句,通常用于自定义查询、更新、删除等操作。
@Select("SELECT * FROM t_user WHERE id = #{id}")
User selectById(Long id);
  1. @Update:用于自定义 SQL 更新语句。
@Update("UPDATE t_user SET username = #{username} WHERE id = #{id}")
boolean updateUserName(User user);
  1. @Delete:用于自定义 SQL 删除语句。
@Delete("DELETE FROM t_user WHERE id = #{id}")
boolean deleteById(Long id);
  1. @Insert:用于自定义 SQL 插入语句。
@Insert("INSERT INTO t_user(username, password) VALUES(#{username}, #{password})")
boolean insertUser(User user);
  1. @SelectProvider:用于自定义 SQL 查询语句,通过提供者方法动态生成 SQL 语句。
@SelectProvider(type = UserProvider.class, method = "selectUser")
List<User> selectUsers();
  1. @TableField(exist = false):用于指定实体类字段在数据库表中是否存在,如果设置为 false,则该字段不会映射到数据库表中。
  2. @TableField(fill = FieldFill.UPDATE):用于指定实体类字段在数据库表中的自动填充策略,如果设置为 UPDATE,则该字段在更新操作时会自动填充。

基本使用

准备数据库:

CREATE DATABASE shoppingdb;
USE shoppingdb;
CREATE TABLE `t_goods`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `price` bigint(20) NULL DEFAULT NULL,
  `pubdate` date NULL DEFAULT NULL,
  `typeName` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `intro` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `picture` varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `flag` int(11) NULL DEFAULT NULL COMMENT '1上架 2下架',
  `star` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_goods` VALUES (1, '可可可乐', 6600, '2018-11-25', '酒水饮料', '巴厘岛原装进口 百事可乐(Pepsi) blue 蓝色可乐 网红可乐汽水饮料 450ml*4瓶装', '201811/7b001eee-38df-4c66-9a0f-350879007402_js1.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (2, '易拉罐可可可乐', 8800, '2018-11-25', '酒水饮料', '日本原装进口 可口可乐(Coca-Cola)碳酸饮料 500ml*6罐,味道谁喝谁知道!', '201811/f65d85f4-a622-4f6b-bbdf-2e354d7b0737_js2.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (3, '干红', 99900, '2018-11-25', '酒水饮料', '自营张裕(CHANGYU)红酒 张裕干红葡萄酒750ml*6瓶(彩龙版),', '201811/fa61ef77-4adc-4895-962b-fcb084e3e809_js3.jpg', 1, 4);
INSERT INTO `t_goods` VALUES (4, '进口红酒', 99900, '2018-11-25', '酒水饮料', '法国进口红酒 拉菲(LAFITE)传奇波尔多干红葡萄酒 双支礼盒装带酒具 750ml*2瓶', '201811/cb233c79-2f18-4f97-afad-0d2079098345_js4.jpg', 1, 3);
INSERT INTO `t_goods` VALUES (5, '草莓饼干', 8800, '2018-11-25', '饼干糕点', '土斯(Totaste) 葡萄味夹层饼干(含葡萄果粒) 休闲零食蛋糕甜点心 实惠分享装360g', '201811/afdd4cd4-9782-46a5-96ed-0ba3c4036379_bg1.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (6, '蔬菜棒', 10100, '2018-11-25', '饼干糕点', '土斯(Totaste) 混合蔬菜味棒形饼干 酥脆可口 独立包装 休闲零食蛋糕甜点心小吃 128g', '201811/8bdbdb3f-4cb6-4af8-9c6f-183411537726_bg2.jpg', 1, 4);
INSERT INTO `t_goods` VALUES (7, '曲奇', 24400, '2018-11-25', '饼干糕点', '丹麦进口 皇冠(danisa)丹麦曲奇精选礼盒装908g(新老包装随机发货)', '201811/db2a101d-600a-44c5-8d0f-0a3b173f81aa_bg3.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (8, '夹心饼干', 6600, '2018-11-25', '饼干糕点', '马来西亚原装进口 茱蒂丝Julie\'s雷蒙德巧克力榛果夹心饼干180g×2', '201811/3b047c04-6b23-491d-bf9c-5405cf36c308_bg4.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (9, '玉米棒', 1800, '2018-11-25', '休闲零食', '印尼进口 Nabati 丽芝士(Richeese)雅嘉 休闲零食 奶酪味 玉米棒 400g/盒 早餐下午茶', '201811/287f1938-f039-4d24-9942-3c8a456d757b_ls1.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (10, '千层酥', 880, '2018-11-25', '休闲零食', '葡韵手信 澳门特产 休闲零食 传统糕点小吃 千层酥150g 新旧包装随机发货', '201811/7570a0dc-eacb-4b61-9085-966ac322172f_ls2.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (11, '海苔', 990, '2018-11-25', '休闲零食', '泰国原装进口休闲零食 老板仔 超大片烤海苔脆紫菜 经典原味 54g/袋(新老包装随机发货)', '201811/62c5370b-2f32-450f-ba4c-401a004d5270_ls3.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (12, '提子干', 4400, '2018-11-25', '休闲零食', '三只松鼠无核白葡萄干蜜饯果干休闲食品新疆特产提子干120g/袋', '201811/4e807511-5515-4ec8-9e20-41e8f49ece66_ls4.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (13, '青岛啤酒', 11800, '2018-11-25', '酒水饮料', '青岛啤酒(TsingTao) 青岛啤酒经典10度 500ml*24听,好喝又实惠!', '201811/555da004-a18c-4fa9-9f23-094551928831_js5.jpg', 1, 5);
INSERT INTO `t_goods` VALUES (14, '手撕面包', 1880, '2018-11-25', '饼干糕点', '三只松鼠 手撕面包1000g整箱装零食大礼包口袋面包孕妇零食早餐食品生日蛋糕小糕点点心礼盒装', '201811/12a3f75c-8ddd-41d8-862f-93f342e5e41e_bg5.png', 1, 5);
INSERT INTO `t_goods` VALUES (15, '开心果', 3200, '2018-11-25', '休闲零食', '满199减120三只松鼠 开心果100g坚果炒货零食每日坚果 1袋装', '201811/d468a868-a7b8-4ad6-bde1-124e23c66437_ls5.jpg', 1, 5);

配置:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shoppingdb?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    
# mybatis-plus配置
mybatis-plus:
  configuration:
    # 下划线转驼峰,默认情况下mybatis-plus是开启的,而mybatis是关闭的
    map-underscore-to-camel-case: true

# 日志配置
logging:
  level:
    包名.mapper: debug
  1. 创建实体类
import java.util.Date; //注意导入的日期类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper  = false)
@TableName("t_goods")
public class Goods {

    private Long id;

    @TableField(value = "name", condition = SqlCondition.LIKE)
    private String name;

    private BigInteger price;

    private Date pubdate;

    @TableField("typeName")
    private String typeName;

    private String intro;

    private String picture;

    private Integer flag;

    private Integer star;

}
  1. 数据层实现BaseMapper
@Mapper
@Repository
public interface GoodsMapper extends BaseMapper<Goods> {
}

查询

在 MyBatis-Plus 中,

selectList

方法是一个用于查询数据列表的便捷方法。这个方法通常在继承自

BaseMapper

的接口中提供,它会根据你提供的查询条件来返回符合条件的数据列表。
这个方法的使用非常简单,只需要提供一个查询条件构造器作为参数即可。MyBatis-Plus 提供了两种查询条件构造器:

QueryWrapper

LambdaQueryWrapper

。注意,如果你提供null,就会查询所有数据。

selectList

方法返回的是一个

List

类型的对象,包含了符合查询条件的所有实体类对象。你可以直接操作这个列表,或者将其转换为其他类型的集合。

查询条件构造器

QueryWrapper

  • 用途:构建查询条件,用于生成 WHERE 子句。
  • 创建:QueryWrapper<MyEntity> queryWrapper = new QueryWrapper<>();
  • 使用:通过链式方法添加查询条件,如 eqnegt 等。
  • 示例:
@Test
    public void testSelectAll() {
        List<Goods> goods = goodsMapper.selectList(null);
        goods.forEach(System.out::println);
        System.out.println("测试成功");
    }

LambdaQueryWrapper

  • 用途:与 QueryWrapper 类似,但使用 Lambda 表达式,提高代码可读性。
  • 创建:LambdaQueryWrapper<MyEntity> lambdaQuery = new LambdaQueryWrapper<>();
  • 使用:通过链式方法添加查询条件,如 eqnegt 等。
  • 示例:
List<MyEntity> list = goodsMapper.selectList(lambdaQuery.eq(Goods::getId, 1));
QueryWrapper API
  • eq (equals):等于查询条件。
  • ne (not equals):不等于查询条件。
  • gt (greater than):大于查询条件。
  • ge (greater than or equals to):大于或等于查询条件。
  • lt (less than):小于查询条件。
  • le (less than or equals to):小于或等于查询条件。
  • like:模糊查询条件,可以包含通配符 %。
  • likeLeft:左模糊查询条件,通配符 % 在字符串的左侧。
  • likeRight:右模糊查询条件,通配符 % 在字符串的右侧。
  • in:在给定列表中的查询条件。
  • notIn:不在给定列表中的查询条件。
  • orderByAsc:升序排序。
  • orderByDesc:降序排序。
  • groupBy:分组查询。
  • select:指定查询的字段。
  • distinct:去重查询。
  • nested:嵌套查询条件。
    /**
     * 查找price>30000并且star=4的记录或者price<1000的记录,记录只显示id、name、price、star字段
     */
    @Test
    public void testSelectByPriceAndStar() {
        QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "name", "price", "star").and(i -> i.gt("price", 30000).eq("star", 4)).or(i -> i.lt("price", 1000));
        List<Goods> goods = goodsMapper.selectList(queryWrapper);
        goods.forEach(System.out::println);
        System.out.println("测试成功");
    }
分页操作
  • 使用 QueryWrapper API的 limit
@Test
    public void testSelectByPage() {
        QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
        queryWrapper.last("limit 0,2");
        List<Goods> goods = goodsMapper.selectList(queryWrapper);
        goods.forEach(System.out::println);
        System.out.println("测试成功");
    }
  • 使用 Page 类进行分页。
@Test
    public void testSelectByPage2() {
        Page<Goods> page = new Page<>(1, 2);
        //selectPage需要两个参数,一个是Page,另一个是QueryWrapper
        List<Goods> goods = goodsMapper.selectPage(page, null).getRecords();
        goods.forEach(System.out::println);
        System.out.println("测试成功");
    }

如果你需要进行分页查询并获取分页信息,Page 是一个更推荐的选择,因为它提供了分页的功能和便捷的方法来处理分页查询。如果你只需要执行普通的查询,并且不需要获取分页信息,那么使用 QueryWrapper 会更直接和方便。

在实际开发中,通常会结合使用 Page 进行分页和 QueryWrapper构建查询条件。但是不要同时使用

limit

Page

@Test
    public void testSelectByPage2() {
        Page<Goods> page = new Page<>(1, 2);
        QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("typeName", "休闲零食");
        List<Goods> goods = goodsMapper.selectPage(page, queryWrapper).getRecords();
        goods.forEach(System.out::println);
        System.out.println("测试成功");
    }

注意,使用Page分页必须进行配置,否则分页无效:

官方教程

@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig{

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setOptimizeJoin(true);
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        paginationInnerInterceptor.setOverflow(true);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
        return interceptor;
    }

}

插入

MyBatis-Plus 提供了一系列的方法来插入数据,以下是一些常用的插入方法及其代码示例:

  1. **insert()**:
    • 用途:插入一个对象,不会自动填充字段,需要手动填充。- 返回值:insert() 方法通常返回插入的行数,而不是布尔值。- insert() 方法提供了更多的灵活性,因为它不会自动填充任何字段,这允许你在插入数据时完全控制每个字段的值。
MyEntity entity = new MyEntity();
entity.setName("李四");
entity.setAge(25);
entity.setCreateTime(new Date()); // 假设有一个createTime字段
boolean result = myEntityMapper.insert(entity);

修改

**updateById()**:

  • 用途:根据主键更新一个对象。
@Test
    public void testUpdateById() {
        Goods goods = Goods.builder()
                .id(16L)
                .name("鸽鸽的蛋")
                .price(BigInteger.valueOf(1000L))
                .pubdate(new Date(System.currentTimeMillis()))
                .typeName("休闲零食")
                .intro("只因你太美")
                .picture("kun.jpg")
                .flag(1)
                .star(2)
                .build();

        int result = goodsMapper.updateById(goods);
        System.out.println("影响行数:" + result);
        System.out.println("测试成功");
    }

**update()**:

  • 用途:根据条件更新对象。

UpdateWrapper 在 QueryWrapper 的基础上增加了用于更新的方法,如 set 方法。UpdateWrapper 主要用于构建更新操作的 WHERE 子句和更新字段的值,而 QueryWrapper 主要用于构建查询操作的 WHERE 子句。

@Test
    public void testUpdateFlag() {
        UpdateWrapper<Goods> wrapper = new UpdateWrapper<>();
        wrapper.set("flag", 0);
//        设置条件,flag字段为1
//        wrapper.eq("flag", 1);
        int result = goodsMapper.update(null, wrapper);
        System.out.println("影响行数:" + result);
        System.out.println("测试成功");
    }

    @Test
    public void testUpdateFlag2() {
        //        设置条件,flag字段为1
        Goods goods = Goods.builder()
                .flag(1)
                .build();

//        当更新的字段为null时,表示更新所有字段
        int result = goodsMapper.update(goods, null);
        System.out.println("影响行数:" + result);
        System.out.println("测试成功");
    }

@Test
    public void testUpdateTypeName() {
        UpdateWrapper<Goods> wrapper = new UpdateWrapper<>();
        wrapper.set("flag", 1).set("star", 5).eq("typeName", "饼干糕点");
        int result = goodsMapper.update(null, wrapper);
        System.out.println("影响行数:" + result);
        System.out.println("测试成功");
    }

删除

  • deleteById():根据id删除
    @Test
    public void testDeleteById() {
        int result = goodsMapper.deleteById(17);
        System.out.println("影响行数:" + result);
        System.out.println("测试成功");
    }
  • delete():根据条件删除,可以传入一个QueryWrapper设置查询条件
    @Test
    public void testDeleteByPrice() {
        int result = goodsMapper.delete(new QueryWrapper<Goods>().lt("price", 1000));
        System.out.println("影响行数:" + result);
        System.out.println("测试成功");
    }

逻辑删除

在Spring Boot中,逻辑删除是一种数据删除的范式,它并不是真正地从数据库中移除数据,而是通过设置一个标志位(通常是一个布尔值字段)来标记数据为“已删除”状态。这样做的目的是为了能够在必要时恢复数据或者进行审计跟踪,同时也避免了实际删除操作可能带来的性能开销。

逻辑删除通常涉及以下几个步骤:

  1. 数据库设计:在数据库表中添加一个标志字段,如is_deleted,通常是一个布尔类型字段。在mysql中没有布尔类型,布尔值通常用tinyint(1)来实现,这个类型可以存储0或1。MySQL会自动将0视为false,将非0值(通常是1)视为true。
  2. 实体类映射:在实体类中添加一个对应于标志字段的属性,并使用JPA注解来映射该字段。
  3. 数据访问逻辑:在数据访问层中,编写查询方法时考虑逻辑删除标志,确保查询不会返回已标记为删除的数据。
  4. 业务逻辑层:在业务逻辑层中,实现删除操作时,不是执行物理删除,而是更新逻辑删除标志字段的值。
  5. 前端或服务调用:前端或服务在执行删除操作时,应该调用更新逻辑删除标志的接口,而不是执行物理删除。

方式一

  1. 在数据库为t_goods表添加一个isDelete列,类型为tinyint

  1. 为实体类添加isDelete字段
private boolean isDelete;
  1. 添加配置
mybatis-plus:
  configuration:
    # 下划线转驼峰,默认情况下mybatis-plus是开启的,而mybatis是关闭的
    map-underscore-to-camel-case: false
  global-config:
    db-config:
      logic-delete-field: isDelete
      logic-delete-value: 1
      logic-not-delete-value: 0
  1. 测试根据id删除
@Test
public void testDeleteById() {
    int result = goodsMapper.deleteById(15);
    System.out.println("影响行数:" + result);
    System.out.println("测试成功");
}
  1. 查看表记录,发现逻辑删除成功:

方式二

在方式一的基础上,修改配置

mybatis-plus:
  configuration:
    # 下划线转驼峰,默认情况下mybatis-plus是开启的,而mybatis是关闭的
    map-underscore-to-camel-case: false
#  global-config:
#    db-config:
#      logic-delete-field: isDelete
#      logic-delete-value: 1
#      logic-not-delete-value: 0

为实体类逻辑删除的字段添加注解

    @TableField(value = "isDelete")
    @TableLogic(value = "0", delval = "1")
    private boolean isDelete;

测试:

@Test
public void testDeleteById() {
    int result = goodsMapper.deleteById(14);
    System.out.println("影响行数:" + result);
    System.out.println("测试成功");
}

PageHelpper

介绍

PageHelper是一个MyBatis的分页插件,它能够非常简单地实现MyBatis的物理分页。PageHelper与MyBatis和MyBatis-Plus兼容,你可以在不改变原有代码的基础上,通过简单的配置和调用,实现分页功能。

MyBatis-Plus虽然提供了强大的ORM功能和内置的分页插件,但对于一些老项目或者特定需求,可能会更倾向于使用PageHelper。

基本使用

导入依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.4.6</version>
</dependency>

测试

@Test
public void testSelectByPage3() {
    //        第一个参数表示当前页数,第二个参数表示每页显示的记录数,第三个参数表示排序字段
    PageHelper.<Goods>startPage(1, 2,"price desc");

    List<Goods> goods = goodsMapper.selectList(null);
    goods.forEach(System.out::println);
    System.out.println("测试成功");
}
@Override
public PageResult pageQuery(AdminPageQueryDTO adminPageQueryDTO) {
int page = adminPageQueryDTO.getPage();
int pageSize = adminPageQueryDTO.getPageSize();
Page<Admin> pageInfo = PageHelper.<Admin>startPage(page, pageSize);
//        查询分页数据,返回分页结果(只查询id、name、username、phone、status)
QueryWrapper<Admin> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "username", "phone", "status");
List<Admin> list = adminMapper.selectList(null, wrapper);
return new PageResult(pageInfo.getTotal(), list);
}

自定义方法

在实现BaseMapper类的方法中,可以自己实现自定义的方法,然后通过注解或xml进行数据库操作。实现方式与mybatis一致。

实际上mybatis-plus提供的方法已经实现了大部分功能,除了有极其复杂的需求,否则不推荐这种方式。

通过注解

通过xml



Thymeleaf*

介绍

thymeleaf 是一个 Java 模板引擎,用于将模板文件(通常是 HTML)与服务器端数据结合起来,生成动态的 HTML 页面。

实际开发中,通常采用前后端分离的开发模式,thymeleaf 使用并不多。

优势:

  • 服务器端渲染:Thymeleaf 允许服务器端渲染 HTML 页面,这意味着页面是在服务器上动态生成的,然后发送到客户端。这有助于提高页面的加载速度和搜索引擎优化。
  • 数据绑定:Thymeleaf 允许将服务器端数据绑定到模板中的变量,简化了数据传递和显示的过程。
  • 动态内容:通过 Thymeleaf 表达式语言,可以轻松地嵌入动态内容,如根据用户角色显示不同的导航链接。
  • 安全性:由于 Thymeleaf 支持数据绑定和验证,因此可以确保前端显示的数据是安全的,避免了 XSS 攻击等安全问题。
  • 易于维护:Thymeleaf 模板通常比纯前端生成的页面更容易维护,因为它们与后端数据和逻辑紧密集成。
  • 与框架 的结合:可以使用框架简化Thymeleaf 页面的开发,如Bootstrap,jquery,vue等。

常用标签

Thymeleaf 是一个强大的 Java 模板引擎,它提供了一系列的标签来帮助开发者生成动态的 HTML 页面。

实际开发中,页面通常由前端负责。生成动态的 HTML 页面可以在前端使用vue完成,所以这些标签了解一下即可。

以下是一些常用的 Thymeleaf 标签:

表达式标签

  • **${expression}**:用于嵌入表达式,如变量、方法调用、流程控制等。

数据绑定标签

  • **${...}**:用于绑定表达式到页面元素,如文本框、下拉列表等。
  • ***{...}**:用于注释表达式,不会影响页面渲染。

迭代标签

  • **<th:each>**:用于遍历集合或数组,生成列表项。

条件标签

  • **<th:if>**:用于条件渲染,如果条件为 true,则包含其中的内容。
  • **<th:unless>**:与 <th:if> 相反,当条件为 false 时,则包含其中的内容。
  • **<th:switch>**:用于多条件分支,类似于 Java 中的 switch 语句。
  • **<th:case>**:与 <th:switch> 一起使用,定义每个分支的条件和内容。

链接和按钮标签

  • **<a th:href="@{...}">**:用于创建链接,@{...} 用于定义 URL。
  • **<form th:action="@{...}">**:用于创建表单,@{...} 用于定义表单提交的 URL。
  • **<button th:text="${...}">**:用于创建按钮,${...} 用于定义按钮上的文本。

杂项标签

  1. **<th:object>**:用于创建一个对象,可以用来访问属性。
  2. **<th:with>**:用于创建一个临时变量,并设置其值。

使用步骤

  1. 添加依赖: 首先,需要在项目的 pom.xml 文件中添加 Spring Boot Thymeleaf 依赖。如果使用的是 Maven,可以添加以下依赖:
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  1. 配置 Thymeleaf: 如果你需要对 Thymeleaf 进行高级配置(虽然大多数情况下 Spring Boot 会自动配置),可以在 application.properties 或 application.yml 文件中进行配置。
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML
    encoding: UTF-8
    cache: false # 开发时建议设置为 false,生产环境设置为 true
  1. 创建模板文件: 在 src/main/resources/templates 目录下创建 Thymeleaf 模板文件。例如,创建一个名为 login.html 的文件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
  </head>
  <body>
    <h2>登录</h2>
    <h3 th:text="${message}">默认消息</h3>
  </body>
</html>
  1. 编写控制器: 创建一个控制器类,该类将处理请求并返回 Thymeleaf 视图。

传递模型数据: 在处理方法中,可以使用 Model 或 ModelMap 对象来传递数据到视图中。可以通过 addAttribute 方法将数据添加到模型中,然后在 Thymeleaf 模板中使用这些数据。

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyController {

    @GetMapping("/login")
    public String index(Model model) {
        model.addAttribute("message", "欢迎使用 Thymeleaf!");
        return "login";
    }
}
  1. 在浏览器发送请求
http://localhost:8088/login

注意我在application.yml中修改了端口号

server:
  port: 8088

效果:

页面跳转

跳转到指定页面

页面跳转通常指的是当用户请求一个URL时,服务器返回一个页面给用户的操作。在Spring Boot中,你可以通过控制器(Controller)中的方法来处理请求并返回页面。

  • 创建控制器类: 创建一个新的 Java 类,并使用 @Controller 注解来标记它作为一个控制器(注意不能使用 @RestController注解)。

  • 返回视图名称:

    • 在处理方法中,返回一个字符串,该字符串表示要渲染的 Thymeleaf 视图的名称。这个字符串通常遵循 "目录/文件名" 的格式,其中目录是 Thymeleaf 视图所在的包名,文件名是视图文件的实际名称,不包括 .html 后缀。- 返回的视图必须是templates下面的页面,不经过配置,无法直接跳转到public,static等目录下的页面

见前面的示例。

重定向

重定向指的是当用户请求一个URL时,服务器返回一个状态码,告诉浏览器去请求另一个URL。在Spring Boot中,你可以使用Spring MVC的RedirectView对象或"redirect:"前缀字符串进行重定向。

重定向用于将用户从一个URL重定向到另一个URL,适用于表单提交后防止页面刷新导致的重复提交。

方式一:使用 "redirect"前缀字符串,类的注解不能使用@RestController,要用@Controller

RequestMapping("/")
    public String index(Model model) {
        model.addAttribute("message", "欢迎使用 Thymeleaf!");

        return "redirect:/login";//路由重定向
    }

方式二:使用Spring MVC的RedirectView对象,类的注解不能使用@RestController,要用@Controller

@GetMapping("/index")
    public RedirectView redirectSource() {
        // 创建RedirectView对象
        RedirectView redirectView = new RedirectView();

        // 设置要重定向到的URL
        redirectView.setUrl("/");

        // 如果需要,添加参数
        redirectView.addStaticAttribute("message", "This is a redirected message!");

        // 返回RedirectView对象
        return redirectView;
    }

    @GetMapping("/")
    public String redirectTarget() {
        // 处理重定向后的请求并返回视图名称
        return "/index";
    }

方式三(不推荐):使用servlet 提供的API,**类的注解可以使用@RestController,也可以使用@Controller **

@RequestMapping(value="/test" , method = RequestMethod.GET)
    public void test( HttpServletResponse response) throws IOException {
        response.sendRedirect("/login");
    }

请求转发

请求转发是指当一个请求到达服务器后,服务器内部将请求转发给另一个处理器或资源,而客户端(浏览器)不知道这一过程(请求转发不会改变浏览器地址栏中的URL)。在Spring Boot中,你可以使用"forward" 前缀字符串或使用Spring MVC的注解和方法来实现请求转发。

请求转发是在服务器内部转发请求,客户端感知不到,适用于服务器内部资源之间的跳转。

方式一:使用 "forward" 前缀字符串,类的注解不能使用@RestController 要用@Controller

RequestMapping("/")
    public String index(Model model) {
        model.addAttribute("message", "欢迎使用 Thymeleaf!");

        return "forward:/login";//路由重定向
    }

方式二:使用Spring MVC的注解和方法来实现请求转发

@Controller
public class MyController {
    @GetMapping("/forwardSource")
    public ModelAndView forwardSource() {
        // 创建ModelAndView对象
        ModelAndView modelAndView = new ModelAndView();
        // 设置要转发到的视图名称
        modelAndView.setViewName("forward:/forwardTarget");
        // 如果需要,添加模型数据
        modelAndView.addObject("message", "This is a forwarded message!");
        // 返回ModelAndView对象
        return modelAndView;
    }
    @GetMapping("/forwardTarget")
    public String forwardTarget() {
        // 处理转发后的请求并返回视图名称
        return "login";
    }
}

案例

案例1:重定向至404页面

  1. 在template目录下编写404.html、500.html、error.html(与下面类似)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>发生了404错误</h1>
</body>
</html>
  1. 在controller目录下创建BasicErrorController 文件
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
@Slf4j
public class BasicErrorController extends AbstractErrorController {

    public BasicErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }
    @RequestMapping(produces = "text/html")
    public ModelAndView handleError(HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> model = this.getErrorAttributes(request, ErrorAttributeOptions.defaults());
        HttpStatus status = this.getStatus(request);
        response.setStatus(status.value());
        // 默认视图
        String viewName = "error/error";
        if (status == HttpStatus.NOT_FOUND) {
            viewName = "error/404";
        } else if (status == HttpStatus.INTERNAL_SERVER_ERROR) {
            viewName = "error/500";
        }
        // 日志记录错误信息
        logError(status, model);
        return new ModelAndView(viewName, model);
    }
    private void logError(HttpStatus status, Map<String, Object> model) {
        // 实现日志记录逻辑
        log.error("Error Status: {}", status);
    }

}

这样就实现了基本的根据不同的HTTP状态码返回不同的错误视图。



安全性

  • 登录标记(会话技术)

    • 用户登录成功后,每一次请求中,都可以获取该标记
  • 统一拦截

    • 过滤器- 拦截器

会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪方案:

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术

会话跟踪技术方案对比:

  • Cookie:

    • 优点:HTTP协议支持- 缺点:移动端app无法使用;不安全,用户可以自己禁用;不能跨域
  • Session:

    • 优点:存储在服务端,安全- 缺点:服务器集群环境下无法直接使用Session
  • JWT

    • 优点:支持PC端、移动端;解决集群环境下的认证问题;减轻服务器端存储压力- 缺点:需要自己实现

JWT令牌

全称:JSON Web Token (官网)

场景:登录认证。
①登录成功后,生成令牌
②后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理

  • 使用
  1. 在pom.xml中引入依赖
<dependency>  
    <groupId>io.jsonwebtoken</groupId>  
    <artifactId>jjwt</artifactId>  
    <version>0.9.1</version>  
</dependency>
  1. 生成JWT令牌(设置签名算法,自定义内容,和有效期)
@Test  // 当运行不与整个工程有关的测试时,可以先将@SpringBootTest注解注掉,只加载测试类
    public void testGenJwt(){  
//        自定义内容  
        HashMap<String, Object> claims = new HashMap<>();  
        claims.put("id",1);  
        claims.put("name","tom");  
//        定义令牌  
        String jwt = Jwts.builder()  
                .signWith(SignatureAlgorithm.HS256, "fl123456") //签名算法。注意生成token的密钥secret字符串不能过短,否则会引起异常。  
                .addClaims(claims) //设置自定义内容  
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置令牌有效时间  
                .compact(); //将令牌转为字符串  
        System.out.println(jwt);  
  
    }
  1. 基于java代码解析jwt令牌
@Test  
public void testParseJwt(){  
    Claims claims = Jwts.parser()  
            .setSigningKey("fl123456")  //指定秘钥(要与生成的一致)
            .parseClaimsJws("令牌")  
            .getBody();  //获取自定义内容
    System.out.println(claims);  
}

案例:修改登录接口,如果登录成功就生成令牌,否则返回错误信息

@PostMapping("/login")  
    public Result login(@RequestBody Emp emp){  
        log.info("登录账号:{}密码:{}",emp.getUsername(),emp.getPassword());  
        Emp e = empService.login(emp);  
  
//        登录成功,生成令牌,下发令牌  
        if (e != null){  
            HashMap<String, Object> claims = new HashMap<>();  
            claims.put("id",e.getId());  
            claims.put("name",e.getName());  
            claims.put("username",e.getUsername());  
  
            String jwt = JwtUtils.generateJwt(claims);  
            return Result.success(jwt);  
        }  
  
//        登录失败,放回错误信息  
        return Result.error("用户名或密码错误");  
    }

过滤器Filter

介绍

概念:

Filter 过滤器

,是 早期JavaWeb 三大组件(Servlet、Filter、Listener)之一(现在其他两个组件不常用)。
作用:过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。比如:登录校验、统一编码处理、敏感字符处理等。

快速入门

1.定义Filter:定义一个类,实现 Filter 接口(注意导入

javax.servlet.*

),并重写其所有方法。
2.配置Filter:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。

@WebFilter(urlPatterns = "/*") //拦截所有请求  
public class DemoFilter implements Filter {  
    @Override//初始化方法,只调用一次  
    public void init(FilterConfig filterConfig) throws ServletException {  
        System.out.println("init 初始化方法执行了"); 
    }  
  
    @Override//拦截请求后调用,调用多次  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
//        拦截请求,并操作  
        System.out.println("拦截到了请求...");  
        //放行前逻辑
//        放行请求  
        filterChain.doFilter(servletRequest,servletResponse);  
        //放行后逻辑
    }  
  
    @Override//销毁方法,只调用一次  
    public void destroy() {  
    }  
}
  • init方法和destroy方法不常用,用默认实现方法,可以不实现
细节
  • 拦截流程:放行后访问对应资源,资源完成访问后,还会回到Filter,执行放行后的逻辑
  • 拦截路径
@WebFilter(urlPatterns = "/*") //拦截所有请求
@WebFilter(urlPatterns = "/emps/*") //拦截目录请求
@WebFilter(urlPatterns = "/login") //拦截具体请求
  • 过滤器链:

    • 一个web应用可以配置多个过滤器,这多个过滤器构成了一个过滤器链- 注解配置的Filter,优先级按照过滤器类名(字符串)的自然排序
过滤器案例
@Slf4j  
@WebFilter(urlPatterns = "/*")  
public class LoginCheckFilter implements Filter {  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
        HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;  
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;  
        //获取请求的url  
        String url = httpRequest.getRequestURL().toString();  
//        判断请求url中是否含有login  
        if (url.contains("login")){  
            log.info("登录操作,直接放行...");  
            filterChain.doFilter(servletRequest,servletResponse);  
            return;        }  
//        获取请求头中的令牌  
        String jwt = httpRequest.getHeader("token");  
//        判断令牌是否为空  
        if (!StringUtils.hasLength(jwt)){//调用spring的工具类,判断令牌是否为空  
            log.info("请求头token为空,返回未登录的信息");  
            Result error = Result.error("NOT_LOGIN");  
//            手动转换对象(用阿里巴巴fastJSON)  
            String notLogin  = JSONObject.toJSONString(error);  
            httpResponse.getWriter().write(notLogin);  
            return;        }  
//        判断令牌是否合法  
        try {  
            JwtUtils.parseJWT(jwt);  
        } catch (Exception e) {  
            e.printStackTrace();  
            log.info("解析令牌失败,返回未登录错误信息");  
            Result error = Result.error("NOT_LOGIN");  
//            手动转换对象(用阿里巴巴fastJSON)  
            String notLogin  = JSONObject.toJSONString(error);  
            httpResponse.getWriter().write(notLogin);  
            return;        }  
//        放行  
        filterChain.doFilter(servletRequest,servletResponse);  
    }  
}

拦截器Interceptor

介绍
  • 概念:spring框架提供的,用来动态拦截控制器方法的执行
  • 作用:拦截请求,在指定方法调用前后,根据业务需要执行预先设置的代码
快速入门
  1. 定义拦截器,实现HandlerInterceptor接口
@Component //交给ioc容器  
public class LoginCheckInterceptor implements HandlerInterceptor {//将光标放在要实现的接口上,然后ctrl+O可以快捷实现  
    @Override   //目标资源方法运行前运行,如果返回true就放行,否则不放行  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("preHandle...");  
        return true;    }  
  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("postHandle...");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("afterCompletion...");  
    }  
  
}
  1. 注册拦截器
@Configuration  
public class WebConfig implements WebMvcConfigurer {  
    @Autowired  
    private LoginCheckInterceptor loginCheckInterceptor;  
  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");  
    }  
}
细节
  • 拦截路径 拦截器可以根据需求,配置不同拦截路径
@Override  
public void addInterceptors(InterceptorRegistry registry) {  
    registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");  
}
  • 执行流程 如果过滤器和拦截器同时存在:

  • 过滤器和拦截器使用其中一种即可。

  • 拦截器与过滤器的区别

    • 接口规范不同:过滤器实现Filter接口,拦截器实现HandlerInterceptor接口- 拦截范围不同:过滤器会拦截所有资源,而拦截器只会拦截Spring环境中的资源
拦截器实现登录校验
@Override   //目标资源方法运行前运行,如果返回true就放行,否则不放行  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        //获取请求的url  
        String url = request.getRequestURL().toString();  
//        判断请求url中是否含有login  
        if (url.contains("login")){  
            log.info("登录操作,直接放行...");  
            return true;        }  
//        获取请求头中的令牌  
        String jwt = request.getHeader("token");  
//        判断令牌是否为空  
        if (!StringUtils.hasLength(jwt)){//调用spring的工具类,判断令牌是否为空  
            log.info("请求头token为空,返回未登录的信息");  
            Result error = Result.error("NOT_LOGIN");  
//            手动转换对象(用阿里巴巴fastJSON)  
            String notLogin  = JSONObject.toJSONString(error);  
            response.getWriter().write(notLogin);  
            return false;        }  
//        判断令牌是否合法  
        try {  
            JwtUtils.parseJWT(jwt);  
        } catch (Exception e) {  
            e.printStackTrace();  
            log.info("解析令牌失败,返回未登录错误信息");  
            Result error = Result.error("NOT_LOGIN");  
//            手动转换对象(用阿里巴巴fastJSON)  
            String notLogin  = JSONObject.toJSONString(error);  
            response.getWriter().write(notLogin);  
            return false;        }  
//        放行  
        return true;  
    }


AOP

概述

  • AOP(面向切面编程),即面向特定方法编程。

在Spring框架中,AOP是一种编程范式,它允许开发者定义跨多个对象的横切关注点,例如日志、事务管理和安全等。在Spring框架中,AOP是通过代理模式实现的。

快速入门

需求:统计各个业务层方法执行耗时。
实现步骤:

  1. 导入依赖:在pom.xml中导入AOP的依赖
  2. 编写AOP程序:针对于特定方法根据业务需要进行编程

为演示方便,可以自建新项目

springboot-aop-quickstart
        <!-- spring aop支持 -->  
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

核心概念

1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
5. 目标对象:Target,通知所应用的对象

AOP细节

通知类型

Spring中AOP的通知类型:

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After :后置通知(最终通知),此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
@Slf4j  
@Component  
@Aspect  
public class MyAspect {  
    //前置通知  
    @Before("execution(* com.itheima.service.*.*(..))")  
    public void before(JoinPoint joinPoint){  
        log.info("before ...");  
  
    }  
  
    //环绕通知  
    @Around("execution(* com.itheima.service.*.*(..))")  
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {  
        log.info("around before ...");  
  
        //调用目标对象的原始方法执行  
        Object result = proceedingJoinPoint.proceed();  
  
        //原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了  
  
        log.info("around after ...");  
        return result;  
    }  
  
    //后置通知  
    @After("execution(* com.itheima.service.*.*(..))")  
    public void after(JoinPoint joinPoint){  
        log.info("after ...");  
    }  
  
    //返回后通知(程序在正常执行的情况下,会执行的后置通知)  
    @AfterReturning("execution(* com.itheima.service.*.*(..))")  
    public void afterReturning(JoinPoint joinPoint){  
        log.info("afterReturning ...");  
    }  
  
    //异常通知(程序在出现异常的情况下,执行的后置通知)  
    @AfterThrowing("execution(* com.itheima.service.*.*(..))")  
    public void afterThrowing(JoinPoint joinPoint){  
        log.info("afterThrowing ...");  
    }  
}

注意:

@Around

环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,且返回类型必须指定为Object

  • 可以将切入点表达式抽取到一个@Pointcut方法上,其他需要使用直接引用即可
@Pointcut("execution(* com.itheima.service.*.*(..))")  
private void pt(){}  //如果pt方法是public的,则在其他类中也可以使用
//前置通知  
@Before("pt()")  
public void before(JoinPoint joinPoint){  
    log.info("before ...");  
  
}
...//其他同理
通知顺序
  • 当有多个切入点都匹配到了目标方法,目标方法执行时,多个通知方法都会执行

  • 执行顺序与类名排序有关

    • 目标方法前的通知方法:字母排名靠前的先执行- 目标方法后的通知方法:字母排名靠前的后执行 如果我们想控制通知的执行顺序有两种方式:
  1. 修改切面类的类名(这种方式非常繁琐、而且不便管理)
  2. 使用Spring提供的@Order注解 使用@Order注解,控制通知的执行顺序:
@Slf4j  
@Component  
@Aspect  
@Order(2)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)  
public class MyAspect2 {  
    //前置通知  
    @Before("execution(* com.itheima.service.*.*(..))")  
    public void before(){  
        log.info("MyAspect2 -> before ...");  
    }  
​  
    //后置通知   
    @After("execution(* com.itheima.service.*.*(..))")  
    public void after(){  
        log.info("MyAspect2 -> after ...");  
    }  
}
@Slf4j  
@Component  
@Aspect  
@Order(3)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)  
public class MyAspect3 {  
    //前置通知  
    @Before("execution(* com.itheima.service.*.*(..))")  
    public void before(){  
        log.info("MyAspect3 -> before ...");  
    }  
​  
    //后置通知  
    @After("execution(* com.itheima.service.*.*(..))")  
    public void after(){  
        log.info("MyAspect3 ->  after ...");  
    }  
}
@Slf4j  
@Component  
@Aspect  
@Order(1) //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)  
public class MyAspect4 {  
    //前置通知  
    @Before("execution(* com.itheima.service.*.*(..))")  
    public void before(){  
        log.info("MyAspect4 -> before ...");  
    }  
​  
    //后置通知  
    @After("execution(* com.itheima.service.*.*(..))")  
    public void after(){  
        log.info("MyAspect4 -> after ...");  
    }  
}
切入点表达式

切入点表达式:

  • 描述切入点方法的一种表达式
  • 作用:主要用来决定项目中的哪些方法需要加入通知
  • 常见形式:
    1. execution(……):根据方法的签名来匹配2. @annotation(……) :根据注解匹配

execution

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带

?

的表示可以省略的部分

  • 访问修饰符:可省略(比如: public、protected)
  • 包名.类名: 可省略(不建议省略)
  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常) 示例:
@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

可以使用通配符描述切入点

  • * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
  • .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数 切入点表达式的语法规则:
  1. 方法的访问修饰符可以省略
  2. 返回值可以使用*号代替(任意返回值类型)
  3. 包名可以使用*号代替,代表任意包(一层包使用一个*
  4. 使用..配置包名,标识此包以及此包下的所有子包
  5. 类名可以使用*号代替,标识任意类
  6. 方法名可以使用*号代替,表示任意方法
  7. 可以使用 * 配置参数,一个任意类型的参数
  8. 可以使用.. 配置参数,任意个任意类型的参数

注意:
根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))

@annotation

已经学习了execution切入点表达式的语法。那么如果我们要匹配多个无规则的方法,比如:list()和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。而在之前我们是将两个切入点表达式组合在了一起完成的需求,这个是比较繁琐的。

我们可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。

实现步骤:

  1. 编写自定义注解
  2. 在业务类要做为连接点的方法上添加自定义注解

自定义注解:MyLog

@Target(ElementType.METHOD)//指定目标类型
@Retention(RetentionPolicy.RUNTIME)//指定生效时间
public @interface MyLog {
}

业务类:DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Override
    @MyLog //自定义注解(表示:当前方法属于目标方法)
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        //模拟异常
        //int num = 10/0;
        return deptList;
    }

    @Override
    @MyLog  //自定义注解(表示:当前方法属于目标方法)
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }

    @Override
    public void save(Dept dept) {
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.save(dept);
    }

    @Override
    public Dept getById(Integer id) {
        return deptMapper.getById(id);
    }

    @Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.update(dept);
    }
}

切面类

@Slf4j
@Component
@Aspect
public class MyAspect6 {
    //针对list方法、delete方法进行前置通知和后置通知

    //前置通知
    @Before("@annotation(com.itheima.anno.MyLog)")
    public void before(){
        log.info("MyAspect6 -> before ...");
    }

    //后置通知
    @After("@annotation(com.itheima.anno.MyLog)")
    public void after(){
        log.info("MyAspect6 -> after ...");
    }
}
连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型
@Slf4j
@Component
@Aspect
public class MyAspect7 {

    @Pointcut("@annotation(com.itheima.anno.MyLog)")
    private void pt(){}
   
    //前置通知
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
    }
    
    //后置通知
    @Before("pt()")
    public void after(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标类名
        String name = pjp.getTarget().getClass().getName();
        log.info("目标类名:{}",name);

        //目标方法名
        String methodName = pjp.getSignature().getName();
        log.info("目标方法名:{}",methodName);

        //获取方法执行时需要的参数
        Object[] args = pjp.getArgs();
        log.info("目标方法参数:{}", Arrays.toString(args));

        //执行原始方法
        Object returnValue = pjp.proceed();

        return returnValue;
    }
}
标签: spring boot 后端

本文转载自: https://blog.csdn.net/sq91fra/article/details/137463440
版权归原作者 FL不凡 所有, 如有侵权,请联系我们删除。

“springboot3零基础到做项目,这一篇就够了!耗时一周整理,超详细!”的评论:

还没有评论