# JavaWeb——后端 ## Java版本解决方案 **单个Java文件运行:** Edit Configurations - **针对单个运行配置**:每个 Java 运行配置(如主类、测试类等)可以独立设置其运行环境(如 JRE 版本、程序参数、环境变量等)。 - **不影响全局项目**:修改某个运行配置的环境不会影响其他运行配置或项目的全局设置。 **如何调整全局项目的环境** - 打开 `File -> Project Structure -> Project`。 - 在 `Project SDK` 中选择全局的 JDK 版本(如 JDK 17)。 - 在 `Project language level` 中设置全局的语言级别(如 17)。 **Java Compiler** - `File -> Settings -> Build, Execution, Deployment -> Compiler -> Java Compiler` **Maven Runner** - `File -> Settings -> Build, Execution, Deployment -> Build Tools -> Maven -> Runner` **三者之间的关系** - 全局项目环境 是基准,决定项目的默认 JDK 和语言级别。 - Java Compiler 控制编译行为,可以覆盖全局的 `Project language level`。 - Maven Runner 控制 Maven 命令的运行环境,可以覆盖全局的 `Project SDK`。 **Maven 项目**: - 确保 `pom.xml` 中的 `` 和 `` 与 `Project SDK` 和 `Java Compiler` 的配置一致。 - 确保 `Maven Runner` 中的 `JRE` 与 `Project SDK` 一致。 - 如果还是不行,pom文件右键点击maven->reload project ## Maven ![image-20240229132137502](https://pic.bitday.top/i/2025/03/19/u6rxj1-2.png) ![image-20240229133408054](https://pic.bitday.top/i/2025/03/19/u6tcqw-2.png) Maven仓库分为: - 本地仓库:自己计算机上的一个目录(用来存储jar包) - 中央仓库:由Maven团队维护的全球唯一的。仓库地址:https://repo1.maven.org/maven2/ - 远程仓库(私服):一般由公司团队搭建的私有仓库 POM文件导入依赖的时候,先看本地仓库有没有,没有就看私服,再没有就从中央仓库下载。 ### Maven创建/导入项目 **创建Maven项目** ![image-20250307174233390](https://pic.bitday.top/i/2025/03/19/u6u4ni-2.png) 勾选 **Create from archetype**(可选),也可以选择 **maven-archetype-quickstart** 等模版。 点击 Next,填写 GAV 坐标 。 GroupId:标识组织或公司(通常使用域名反写,如 `com.example`) ArtifactId:标识具体项目或模块(如 `my-app`、`spring-boot-starter-web`)。 Version:标识版本号(如 `1.0-SNAPSHOT`、`2.7.3`) **导入Maven项目** **(一)单独的Maven项目** 打开 IDEA,在主界面选择 Open(或者在菜单栏选择 File -> Open)。 在文件选择对话框中,定位到已有项目的根目录(包含 `pom.xml` 的目录)。 选择该目录后,IDEA 会检测到 `pom.xml` 并询问是否导入为 Maven 项目,点击 **OK** 或 **Import** 即可。 IDEA 会自动解析 `pom.xml`,下载依赖并构建项目结构。 **(二)在现有Maven项目中导入独立的Maven项目** 在已经打开的 IDEA 窗口中,使用 **File -> New -> Module from Existing Sources...** 选择待导入项目的根目录(其中包含 `pom.xml`),IDEA 会将其导入为同一个工程下的另一个模块(Module)。 它们 看起来在一个工程里了,但**仍然是两个独立的** Maven 模块。 **(三)两个模块有较强的关联** 1.新建一个上层目录,如下,MyProject1和MyProject2的内容拷贝过去。 ```java ParentProject/ ├── pom.xml <-- 父模块(聚合模块) ├── MyProject1/ <-- 子模块1 │ └── pom.xml └── MyProject2/ <-- 子模块2 └── pom.xml ``` 2.创建父级pom 父模块 `pom.xml` 示例: ```java 4.0.0 com.example ParentProject 1.0-SNAPSHOT pom //必写 MyProject1 //必写 MyProject2 ``` 3.修改子模块 `pom.xml` ,加上: ```java com.example ParentProject 1.0-SNAPSHOT ../pom.xml ``` 如果子模块中无需与父级不同的配置,**可以不写**,就自动继承父级配置;若写了同名配置,则表示你想要**覆盖或合并**父级配置。 4.File -> Open选择父级的pom,会自动导入其下面两个项目。 **(四)通过 Maven 依赖引用(一般导入官方依赖)** 如果你的两个项目之间存在依赖关系(例如,第二个项目需要引用第一个项目打包后的 JAR),可以采用以下方式: 在第一个项目(被依赖项目)执行 `mvn install` - 这会把打包后的产物安装到本地仓库(默认 `~/.m2/repository`)。 在第二个项目的 `pom.xml` 中添加依赖坐标 ```java com.example my-first-project 1.0-SNAPSHOT ``` Maven 重建 ### Maven坐标 什么是坐标? * Maven中的坐标是==资源的唯一标识== , 通过该坐标可以唯一定位资源位置 * 使用坐标来定义项目或引入项目中需要的依赖 ![image-20240302131843540](https://pic.bitday.top/i/2025/03/19/u6ps37-2.png) ### 依赖管理 可以到mvn的中央仓库(https://mvnrepository.com/)中搜索获取依赖的坐标信息 ```java ch.qos.logback logback-classic 1.2.11 junit junit 4.12 test ``` 更改之后可以在界面上看到一个maven刷新按钮,点击一下就开始联网下载依赖了,成功后可以看到 ![image-20240302133241227](https://pic.bitday.top/i/2025/03/19/u6szry-2.png) #### 排除依赖 A依赖B,B依赖C,如果A不想将C依赖进来,可以同时排除C,被排除的资源**无需指定版本**。 ```java com.itheima maven-projectB 1.0-SNAPSHOT junit junit ``` #### 依赖范围 | **scope**值 | **主程序** | **测试程序** | **打包(运行)** | **范例** | | --------------- | ---------- | ------------ | ---------------- | ----------- | | compile(默认) | Y | Y | Y | log4j | | test | - | Y | - | junit | | provided | Y | Y | - | servlet-api | | runtime | - | Y | Y | jdbc驱动 | 注意!!!这里的scope如果是`test`,那么它的作用范围在`src/test/java`下,在`src/main/java`下无法导包! ### Maven生命周期 主要关注以下几个: • clean:移除上一次构建生成的文件 (Target文件夹) • compile:编译 `src/main/java` 中的 Java 源文件至 `target/classes` • test:使用合适的单元测试框架运行测试(junit) • package:将编译后的文件打包,如:jar、war等 • install:将打包后的产物(如 `jar`)安装到本地仓库 #### 单元测试 1. 导入依赖junit ```java junit junit 4.12 test ``` 2. 在src/test/java下创建DemoTest类(*Test) 3. 创建test方法 ```java @Test public void test1(){ System.out.println("hello1"); } @Test public void test2(){ System.out.println("hello2"); } ``` 4. 双击test生命周期 ![image-20240302140156166](https://pic.bitday.top/i/2025/03/19/u6rc3p-2.png) ## HTTP协议 ### 响应状态码 | 状态码分类 | 说明 | | ---------- | ------------------------------------------------------------ | | 1xx | **响应中** --- 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略 | | 2xx | **成功** --- 表示请求已经被成功接收,处理已完成 | | 3xx | **重定向** --- 重定向到其它地方,让客户端再发起一个请求以完成整个处理 | | 4xx | **客户端错误** --- 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 | | 5xx | **服务器端错误** --- 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等 | | 状态码 | 英文描述 | 解释 | | ------- | --------------------------- | ------------------------------------------------------------ | | ==200== | **`OK`** | 客户端请求成功,即**处理成功**,这是我们最想看到的状态码 | | 302 | **`Found`** | 指示所请求的资源已移动到由`Location`响应头给定的 URL,浏览器会自动重新访问到这个页面 | | 304 | **`Not Modified`** | 告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向 | | 400 | **`Bad Request`** | 客户端请求有**语法错误**,不能被服务器所理解 | | 403 | **`Forbidden`** | 服务器收到请求,但是**拒绝提供服务**,比如:没有权限访问相关资源 | | ==404== | **`Not Found`** | **请求资源不存在**,一般是URL输入有误,或者网站资源被删除了 | | 405 | **`Method Not Allowed`** | 请求方式有误,比如应该用GET请求方式的资源,用了POST | | 429 | **`Too Many Requests`** | 指示用户在给定时间内发送了**太多请求**(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用 | | ==500== | **`Internal Server Error`** | **服务器发生不可预期的错误**。服务器出异常了,赶紧看日志去吧 | | 503 | **`Service Unavailable`** | **服务器尚未准备好处理请求**,服务器刚刚启动,还未初始化好 | ## 开发规范 ### REST风格 在前后端进行交互的时候,我们需要基于当前主流的REST风格的API接口进行交互。 什么是REST风格呢? - REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。 **传统URL风格如下:** ```java http://localhost:8080/user/getById?id=1 GET:查询id为1的用户 http://localhost:8080/user/saveUser POST:新增用户 http://localhost:8080/user/updateUser PUT:修改用户 http://localhost:8080/user/deleteUser?id=1 DELETE:删除id为1的用户 ``` 我们看到,原始的传统URL,定义比较复杂,而且将资源的访问行为对外暴露出来了。 **基于REST风格URL如下:** ```java http://localhost:8080/users/1 GET:查询id为1的用户 http://localhost:8080/users POST:新增用户 http://localhost:8080/users PUT:修改用户 http://localhost:8080/users/1 DELETE:删除id为1的用户 ``` 其中总结起来,就一句话:通过URL定位要操作的资源,通过HTTP动词(请求方式)来描述具体的操作。 **REST风格后端代码:** ```java @RestController @RequestMapping("/depts") //定义当前控制器的请求前缀 public class DeptController { // GET: 查询资源 @GetMapping("/{id}") public Dept getDept(@PathVariable Long id) { ... } // POST: 新增资源 @PostMapping public void createDept(@RequestBody Dept dept) { ... } // PUT: 更新资源 @PutMapping public void updateDept(@RequestBody Dept dept) { ... } // DELETE: 删除资源 @DeleteMapping("/{id}") public void deleteDept(@PathVariable Long id) { ... } } ``` ### 开发流程 ![image-20220904125004138](https://pic.bitday.top/i/2025/03/19/u6qh4d-2.png) 1. 查看页面原型明确需求 - 根据页面原型和需求,进行表结构设计、编写接口文档(已提供) 2. 阅读接口文档 3. 思路分析 4. 功能接口开发 - 就是开发后台的业务功能,一个业务功能,我们称为一个接口(Controller 中一个完整的处理请求的方法) 5. 功能接口测试 - 功能开发完毕后,先通过Postman进行功能接口测试,测试通过后,再和前端进行联调测试 6. 前后端联调测试 - 和前端开发人员开发好的前端工程一起测试 ## SpringBoot **Servlet 容器** 是用于管理和运行 Web 应用的环境,它负责加载、实例化和管理 Servlet 组件,处理 HTTP 请求并将请求分发给对应的 Servlet。常见的 Servlet 容器包括 Tomcat、Jetty、Undertow 等。 **SpringBoot的WEB默认内嵌了tomcat服务器,非常方便!!!** 浏览器与 Tomcat 之间通过 HTTP 协议进行通信,而 Tomcat 则充当了中间的桥梁,将请求路由到你的 Java 代码,并最终将处理结果返回给浏览器。 ![image-20240304101816184](https://pic.bitday.top/i/2025/03/19/u6pju8-2.png) 查看springboot版本:查看pom文件 ```java spring-boot-starter-parent org.springframework.boot 2.7.3 ``` 版本为2.7.3 ### 快速启动 1. 新建**spring initializr** project 2. 删除以下文件 ![image-20240302142835694](https://pic.bitday.top/i/2025/03/19/u6qkax-2.png) 新建HelloController类 ```java package edu.whut.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ System.out.println("hello"); return "hello"; } } ``` 然后启动服务器,main程序 ```java package edu.whut; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SprintbootQuickstartApplication { public static void main(String[] args) { SpringApplication.run(SprintbootQuickstartApplication.class, args); } } ``` 然后浏览器访问 localhost:8080/hello。 ### SpringBoot请求 #### 简单参数 - 在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。 ```java @RestController public class RequestController { // http://localhost:8080/simpleParam?name=Tom&age=10 // 第1个请求参数: name=Tom 参数名:name,参数值:Tom // 第2个请求参数: age=10 参数名:age , 参数值:10 //springboot方式 @RequestMapping("/simpleParam") public String simpleParam(String name , Integer age ){//形参名和请求参数名保持一致 System.out.println(name+" : "+age); return "OK"; } } ``` - 如果方法形参名称与请求参数名称不一致,controller方法中的形参还能接收到请求参数值吗? 解决方案:可以使用Spring提供的`@RequestParam`注解完成映射 在方法形参前面加上 `@RequestParam` 然后通过value属性执行请求参数名,从而完成映射。代码如下: ```java @RestController public class RequestController { // http://localhost:8080/simpleParam?name=Tom&age=20 // 请求参数名:name //springboot方式 @RequestMapping("/simpleParam") public String simpleParam(@RequestParam("name") String username , Integer age ){ System.out.println(username+" : "+age); return "OK"; } } ``` #### 实体参数 复杂实体对象指的是,在实体类中有一个或多个属性,也是实体对象类型的。如下: - User类中有一个Address类型的属性(Address是一个实体类) 复杂实体对象的封装,需要遵守如下规则: - **请求参数名与形参对象属性名相同**,按照对象层次结构关系即可接收嵌套实体类属性参数。 ![image-20240303112109981](https://pic.bitday.top/i/2025/03/19/u6ndfm-2.png) 注意:这里User前面不能加`@RequestBody`是因为请求方式是 (表单)或 URL 参数;如果是JSON请求体就必须加。 ```java @RequestMapping("/complexpojo") public String complexpojo(User user){ System.out.println(user); return "OK"; } ``` ```java @Data @NoArgsConstructor @AllArgsConstructor public class User { private String name; private Integer age; private Address address; } ``` ```java @Data @NoArgsConstructor @AllArgsConstructor public class Address { private String province; private String city; } ``` #### 数组参数 数组参数:请求参数名与形参数组**名称相同**且请求参数为**多个**,定义数组类型形参即可接收参数 ```java @RestController public class RequestController { //数组集合参数 @RequestMapping("/arrayParam") public String arrayParam(String[] hobby){ System.out.println(Arrays.toString(hobby)); return "OK"; } } ``` ![image-20240303120212254](https://pic.bitday.top/i/2025/03/19/u6oyev-2.png) #### 路径参数 在现在的开发中,经常还会直接在请求的URL中传递参数。例如: ~~~text http://localhost:8080/user/1 http://localhost:880/user/1/0 ~~~ 上述的这种传递请求参数的形式呢,我们称之为:路径参数。 注意,路径参数使用大括号 `{}` 定义 ```java @RestController public class RequestController { //路径参数 @RequestMapping("/path/{id}/{name}") public String pathParam2(@PathVariable Integer id, @PathVariable String name){ System.out.println(id+ " : " +name); return "OK"; } } ``` #### JSON格式参数 ```java { "backtime": [ "与中标人签订合同后 5日内", "投标截止时间前撤回投标文件并书面通知招标人的,2日内", "开标现场投标文件被拒收,开标结束后,2日内" ], "employees": [ { "firstName": "John", "lastName": "Doe" }, { "firstName": "Anna", "lastName": "Smith" }, { "firstName": "Peter", "lastName": "Jones" } ] } ``` **JSON 格式的核心特征** - 数据为键值对:数据存储在键值对中,键和值用冒号分隔。在你的示例中,每个对象有两个键值对,如 `"firstName": "John"`。 - 使用大括号表示对象:JSON 使用大括号 `{}` 包围对象,对象可以包含多个键值对。 - 使用方括号表示数组:JSON 使用方括号 `[]` 表示数组,数组中可以包含多个值,包括数字、字符串、对象等。在该示例中:`"employees"` 是一个对象数组,数组中的每个元素都是一个对象。 Postman如何发送JSON格式数据: ![image-20240303121028876](https://pic.bitday.top/i/2025/03/19/u6u85u-2.png) 服务端Controller方法如何接收JSON格式数据: - 传递json格式的参数,在Controller中会使用实体类进行封装。 - 封装规则:JSON数据**键名与形参对象属性名**相同,定义POJO类型形参即可接收参数。需要使用 `@RequestBody`标识。 ```java @Data @NoArgsConstructor @AllArgsConstructor public class DataDTO { private List backtime; private List employees; } ``` ```java @Data @NoArgsConstructor @AllArgsConstructor public class Employee { private String firstName; private String lastName; } ``` ```java @RestController public class DataController { @PostMapping("/data") public String receiveData(@RequestBody DataDTO data) { System.out.println("Backtime: " + data.getBacktime()); System.out.println("Employees: " + data.getEmployees()); return "OK"; } } ``` **JSON格式工具包** 用于高效地进行 JSON 与 Java 对象之间的序列化和反序列化操作。 引入依赖: ```java com.alibaba fastjson 1.2.76 ``` 使用: ```java import com.alibaba.fastjson.JSON; public class FastJsonDemo { public static void main(String[] args) { // 创建一个对象 User user = new User("Alice", 30); // 对象转 JSON 字符串 String jsonString = JSON.toJSONString(user); System.out.println("JSON String: " + jsonString); // JSON 字符串转对象 User parsedUser = JSON.parseObject(jsonString, User.class); System.out.println("Parsed User: " + parsedUser); } } // JSON String: {"age":30,"name":"Alice"} // Parsed User: User(name=Alice, age=30) ``` ### SpringBoot响应 **@ResponseBody注解:** - 位置:书写在Controller方法上或类上 - 作用:将方法返回值直接响应给浏览器 - 如果返回值类型是实体对象/集合,将会**转换为JSON格式**后在响应给浏览器 `@RestController` = `@Controller` + `@ResponseBody` **统一响应结果:** 下图返回值分别是字符串、对象、集合。 ![image-20221204174052622](https://pic.bitday.top/i/2025/03/19/u6shpf-2.png) **定义统一返回结果类** - 响应状态码:当前请求是成功,还是失败 - 状态码信息:给页面的提示信息 - 返回的数据:给前端响应的数据(字符串、对象、集合) 定义在一个实体类Result来包含以上信息。代码如下: ```java @Data @NoArgsConstructor @AllArgsConstructor public class Result { private Integer code;//响应码,1 代表成功; 0 代表失败 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); } } ``` ### Spring分层架构 #### 三层架构 Controller层接收请求,调用Service层;Service层先调用Dao层获取数据,然后实现自己的业务逻辑处理部分,最后返回给Controller层;Controller层再响应数据。可理解为递归的过程。 ![image-20221204201342490](https://pic.bitday.top/i/2025/03/19/u6nmne-2.png) 传统模式:对象的创建、管理和依赖关系都由程序员手动编写代码完成,程序内部控制对象的生命周期。 例如: ```java public class A { private B b; public A() { b = new B(); // A 自己创建并管理 B 的实例 } } ``` 假设有类 A 依赖类 B,在传统方式中,类 A 可能在构造方法或方法内部直接调用 `new B()` 来创建 B 的实例。 如果 B 的创建方式发生变化,A 也需要修改代码。这就导致了耦合度较高。 **软件设计原则:高内聚低耦合。** > 高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。 > > 低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。 #### IOC&DI 分层解耦 **外部容器**(例如 Spring 容器)是一个负责管理对象创建、配置和生命周期的软件系统。 - 它扫描项目中的类,根据预先配置或注解,将这些类实例化为 Bean。 - 它维护各个 Bean 之间的依赖关系,并在创建 Bean 时把它们所需的依赖“注入”进去。 **依赖注入(DI)**:类 A 不再自己创建 B,而是声明自己需要一个 B,容器在创建 A 时会自动将 B 的实例提供给 A。 ```java public class A { private B b; // 通过构造器注入依赖 public A(B b) { this.b = b; } } ``` **Bean 对象**:在 Spring 中,被容器管理的对象称为 Bean。通过注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`),可以将一个普通的 Java 类声明为 Bean,容器会负责它的创建、初始化以及生命周期管理。 ![image-20240305104036244](https://pic.bitday.top/i/2025/03/19/u6n46r-2.png) *任务:完成Controller层、Service层、Dao层的代码解耦* 思路: 1. 删除Controller层、Service层中new对象的代码 2. Service层及Dao层的**实现类**,交给IOC容器管理 3. 为Controller及Service注入运行时依赖的对象 - Controller程序中注入依赖的Service层对象 - Service程序中注入依赖的Dao层对象 第1步:删除Controller层、Service层中new对象的代码 ![image-20221204212807207](https://pic.bitday.top/i/2025/03/19/u6tjd9-2.png) 第2步:Service层及Dao层的**实现类**,交给IOC容器管理 使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理 ![image-20221204213328034](https://pic.bitday.top/i/2025/03/19/u6pz2g-2.png) 第3步:为Controller及Service注入运行时依赖的对象 使用Spring提供的注解:`@Autowired` ,就可以实现程序运行时IOC容器自动注入需要的依赖对象 ![image-20221204213859112](https://pic.bitday.top/i/2025/03/19/u6ukxl-2.png) **如果我有多个实现类,eg:EmpServiceA、EmpServiceB,我该如何切换呢?**两种方法 1. 只需在需要使用的实现类上加@Component,注释掉不需要用到的类上的@Component。可以把@Component想象成装入盒子,@Autowired想象成拿出来,因此只需改变放入的物品,而不需改变拿出来的这个动作。 ```java // 只启用 EmpServiceA,其他实现类的 @Component 注解被注释或移除 @Component public class EmpServiceA implements EmpService { // 实现细节... } // EmpServiceB 没有被 Spring 管理 // @Component // public class EmpServiceB implements EmpService { ... } ``` 2. 在@Component上面加上@Primary,表明该类优先生效 ``` // 默认使用 EmpServiceB,其他实现类也在容器中,但未标记为 Primary @Component public class EmpServiceA implements EmpService { // 实现细节... } @Component @Primary // 默认优先注入 public class EmpServiceB implements EmpService { // 实现细节... } ``` Component衍生注解 | 注解 | 说明 | 位置 | | :---------- | -------------------- | -------------------------------------------------- | | @Controller | @Component的衍生注解 | 标注在控制器类上Controller | | @Service | @Component的衍生注解 | 标注在业务类上Service | | @Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少)DAO | | @Component | 声明bean的基础注解 | 不属于以上三类时,用此注解 | 注:@Mapper 注解本身并不是 Spring 框架提供的,是用于 *MyBatis* 数据层的接口标识,但效果类似。 ## SpringBoot原理 ### 容器启动 在 Spring 框架中,“容器启动”指的是 ApplicationContext 初始化过程,主要包括*配置解析*、*加载 Bean 定义*、*实例化*和*初始化 Bean* 以及完成*依赖注入*。具体来说,容器启动的时机包括以下几个关键点: 当你启动一个 Spring 应用时,无论是通过直接运行一个包含 `main` 方法的类,还是部署到一个 Servlet 容器中,Spring 的应用上下文都会被创建和初始化。这个过程包括: - 读取配置:加载配置文件或注解中指定的信息,确定哪些组件由 Spring 管理。 - 注册 Bean 定义:将所有扫描到的 Bean 定义注册到容器中。 - 实例化 Bean:根据 Bean 定义创建实例。默认情况下,所有单例 Bean在启动时被创建(除非配置为懒加载)。 - 依赖注入:解析 Bean 之间的依赖关系,并自动注入相应的依赖。 ### 配置文件 #### 配置优先级 在SpringBoot项目当中,常见的属性配置方式有5种, 3种配置文件,加上2种外部属性的配置(Java系统属性、命令行参数)。优先级(从低到高): - application.yaml(忽略) - application.yml - application.properties - java系统属性(-Dxxx=xxx) - 命令行参数(--xxx=xxx) 在 Spring Boot 项目中,通常使用的是 **application.yml** 或 **application.properties** 文件,这些文件通常放在项目的 **src/main/resources** 目录下。 如果项目已经打包上线了,这个时候我们又如何来设置Java系统属性和命令行参数呢? ```java java -Dserver.port=9000 -jar XXXXX.jar --server.port=10010 ``` 在这个例子中,由于命令行参数的优先级高于 Java 系统属性,最终生效的 `server.port` 是 **10010**。 #### **properties** 位置:`src/main/resources/application.properties` 将配置信息写在application.properties,用注解@Value获取配置文件中的数据 ![image-20230102173905913](https://pic.bitday.top/i/2025/03/19/u6osck-2.png) `@Value("${aliyun.oss.endpoint}")` #### **yml配置文件**(推荐!!!) 位置:`src/main/resources/application.yml` ![image-20230102181215809](https://pic.bitday.top/i/2025/03/19/u6txwe-2.png) 了解下yml配置文件的基本语法: - 大小写敏感 - **数据前边必须有空格**,作为分隔符 - 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会自动将Tab转换为空格) - 缩进的空格数目不重要,只要**相同层级的元素左侧对齐**即可 - `#`表示注释,从这个字符一直到行尾,都会被解析器忽略 对象/map集合 ```java user: name: zhangsan detail: age: 18 password: "123456" ``` 数组/List/Set集合 ```java hobby: - java - game - sport //获取示例 @Value("${hobby}") private List hobby; ``` 以上获取配置文件中的属性值,需要通过@Value注解,有时过于繁琐!!! #### **@ConfigurationProperties** 是用来**将外部配置(如 `application.properties` / `application.yml`)映射到一个 POJO** 上的。 在 **Spring Boot** 中,根据 **驼峰命名转换**规则,自动将 `YAML` 配置文件中的 **键名**(例如 `user-token-name` `user_token_name`)映射到 **Java 类中的属性**(例如 `userTokenName`)。 ```java @Data @Component @ConfigurationProperties(prefix = "aliyun.oss") public class AliOssProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; } ``` Spring提供的简化方式套路: 1. 需要创建一个实现类,且实体类中的**属性名**和配置文件当中**key**的名字必须要**一致** > 比如:配置文件当中叫endpoints,实体类当中的属性也得叫endpoints,另外实体类当中的属性还需要提供 getter / setter方法 **==》@Data** 2. 需要将实体类交给Spring的IOC容器管理,成为IOC容器当中的bean对象 **==>@Component** 3. 在实体类上添加`@ConfigurationProperties`注解,并通过**perfix属性**来指定配置参数项的前缀 ![image-20230103210827003](https://pic.bitday.top/i/2025/03/19/u6taji-2.png) 4. (可选)引入依赖pom.xml (自动生成配置元数据,让 IDE 能识别并补全你在 `application.properties/yml` 中的自定义配置项,提高开发体验) ```java org.springframework.boot spring-boot-configuration-processor ``` ### Bean 的获取和管理 #### 获取Bean **1.自动装配(@Autowired)** ```java @Service public class MyService { @Autowired private MyRepository myRepository; // 自动注入 MyRepository Bean } ``` **2.手动获取(ApplicationContext)** - `@Autowired` 自动将 Spring 创建的 `ApplicationContext` 注入到 `applicationContext` 字段中, - 再通过 `applicationContext.getBean(...)` 拿到其他 Bean Spring 会默认采用类名并将首字母小写作为 Bean 的名称。例如,类名为 `DeptController` 的组件默认名称就是 `deptController`。 ```java @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootWebConfig2ApplicationTests { @Autowired private ApplicationContext applicationContext; // IoC 容器 @Test public void testGetBean() { // 根据 Bean 名称获取 DeptController bean = (DeptController) applicationContext.getBean("deptController"); System.out.println(bean); } } ``` 默认情况下,Spring 在容器启动时会创建所有单例 Bean(饿汉模式);使用 `@Lazy` 注解则可实现延迟加载(懒汉模式) #### bean的作用域 | **作用域** | **说明** | | ---------- | -------------------------------------------------- | | singleton | 容器内同名称的bean**只有一个实例**(单例)(默认) | | prototype | 每次使用该bean时会创建新的实例(非单例) | 在设计单例类时,通常要求它们是**无状态的**,不仅要确保成员变量不可变,还需要确保成员方法不会对共享的、可变的状态进行不受控制的修改,从而实现整体的线程安全。 ```java @Service public class CalculationService { // 不可变的成员变量 private final double factor = 2.0; // 成员方法仅依赖方法参数和不可变成员变量 public double multiply(double value) { return value * factor; } } ``` **更改作用域方法:** 在bean类上加注解`@Scope("prototype")`(或其他作用域标识)即可。 #### 第三方 Bean配置 - 如果要管理的bean对象**来自于第三方**(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。 - 如果需要定义第三方Bean时, 通常会单独定义一个**配置类** ```java @Configuration // 配置类 public class CommonConfig { // 定义第三方 Bean,并交给 IoC 容器管理 @Bean // 返回值默认作为 Bean,Bean 名称默认为方法名 public SAXReader reader(DeptService deptService) { System.out.println(deptService); return new SAXReader(); } } ``` 在应用启动时,Spring 会调用配置类中标注 `@Bean` 的方法,将方法**返回值注册**为容器中的 Bean 对象。 默认情况下,该 Bean 的名称就是**该方法的名字**。本例 Bean 名称默认就是 `"reader"`。 使用: ```java @Service public class XmlProcessingService { // 按类型注入 @Autowired private SAXReader reader; public void parse(String xmlPath) throws DocumentException { Document doc = reader.read(new File(xmlPath)); // ... 处理 Document ... } } ``` ### SpirngBoot原理 #### **起步依赖** Spring Boot 只需要引入一个起步依赖(例如 `springboot-starter-web`)就能满足项目开发需求。这是因为: - **Maven 依赖传递:** 起步依赖内部已经包含了开发所需的常见依赖(如 JSON 解析、Web、WebMVC、Tomcat 等),无需开发者手动引入其它依赖。 - **结论:** 起步依赖的核心原理就是 Maven 的依赖传递机制。 #### **自动配置** Spring Boot 会自动扫描启动类**所在包及其子包中**的所有带有组件注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Mapper` 等)的类并加载到IOC容器中。 ![image-20250326164249514](https://pic.bitday.top/i/2025/03/26/r5wloq-0.png) 自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是: - @SpringBootConfiguration - 声明当前类是一个配置类 - @**ComponentScan** - 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包) - @EnableAutoConfiguration - 内部使用 `@Import` 导入一个 `ImportSelector` 实现类,该实现类在其 `selectImports()` 方法中读取依赖 jar 包中 META-INF 下的配置文件(如 `spring.factories`),获取自动配置类列表。最终,通过 `@Import` 将这些配置类加载到 Spring 的 IoC 容器中。 **自动配置类中的 Bean 加载:** - 自动配置类定义的所有 Bean **不一定**全部加载到容器中。 - 通常会配合使用以 `@Conditional` 开头的条件注解,根据环境或依赖条件决定是否装配对应的 Bean,从而实现有选择的配置。 **如何让第三方bean以及配置类生效?** 如果配置类(如 `CommonConfig`)不在 Spring Boot 启动类的扫描路径内(即不在启动类所在包或其子包下),那么就需要通过 `@Import` 手动导入该配置类。如: ```java com └── example └── SpringBootApplication.java // 启动类 src └── com └── config └── CommonConfig.java // 配置类 ``` 借助 `@Import` 注解,我们可以将外部的普通类、配置类或实现了 `ImportSelector` 的类**显式导入**到 Spring 容器中。 **1.使用@Import导入普通类:** 如果某个普通类(如 `TokenParser`)没有 `@Component` 注解标识,也可以通过 `@Import` 导入它,使其成为 Spring 管理的 Bean。 ```java // TokenParser 类没有 @Component 注解 public class TokenParser { public void parse(){ System.out.println("TokenParser ... parse ..."); } } ``` 在启动类上使用 `@Import` 导入: ```java @Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中 @SpringBootApplication public class SpringbootWebConfig2Application { public static void main(String[] args) { SpringApplication.run(SpringbootWebConfig2Application.class, args); } } ``` **2.使用@Import导入配置类:** 配置类中可以定义多个 Bean,通过 `@Configuration` 和 `@Bean` 注解实现集中管理。 ~~~java @Configuration public class HeaderConfig { @Bean public HeaderParser headerParser(){ return new HeaderParser(); } @Bean public HeaderGenerator headerGenerator(){ return new HeaderGenerator(); } } ~~~ 启动类导入配置类: ~~~java @Import(HeaderConfig.class) //导入配置类 @SpringBootApplication public class SpringbootWebConfig2Application { public static void main(String[] args) { SpringApplication.run(SpringbootWebConfig2Application.class, args); } } ~~~ **3.使用第三方依赖@EnableXxxx 注解** 如果第三方依赖没有提供自动配置支持, 常见方案是第三方依赖提供一个 `@EnableXxxx` 注解,这个注解内部封装了 `@Import`,通过它可以一次性导入多个配置或 Bean。 ~~~java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类 public @interface EnableHeaderConfig { } ~~~ 在应用启动类上添加第三方依赖提供的 `@EnableHeaderConfig` 注解,即可导入相关的配置和 Bean。 ```java @EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解 @SpringBootApplication public class SpringbootWebConfig2Application { public static void main(String[] args) { SpringApplication.run(SpringbootWebConfig2Application.class, args); } } ``` 推荐第三种方式! ## 常见的注解!! 1. `@RequestMapping("/jsonParam")`:可以用于**控制器级别**,也可以用于**方法级别**。 用于方法:HTTP 请求路径为 `/jsonParam` 的请求将调用该方法。 ```java @RequestMapping("/jsonParam") public String jsonParam(@RequestBody User user){ System.out.println(user); return "OK"; } ``` 用于控制器: 所有方法的映射路径都会以这个前缀开始。 ```java @RestController @RequestMapping("/depts") public class DeptController { @GetMapping("/{id}") public Dept getDept(@PathVariable Long id) { // 实现获取部门逻辑 } @PostMapping public void createDept(@RequestBody Dept dept) { // 实现新增部门逻辑 } } ``` 1. `@RequestBody`:这是一个**方法参数级别**的注解,用于告诉Spring框架将请求体的内容解析为指定的**Java对象**。 2. `@RestController`:这是一个类级别的注解,它告诉Spring框架这个类是一个控制器(Controller),并且处理HTTP请求并返回响应数据。与 `@Controller` 注解相比,`@RestController` 注解还会自动将控制器方法返回的数据转换为 JSON 格式,并写入到HTTP响应中,得益于`@ResponseBody` 。 `@RestController = @Controller + @ResponseBody` 4. `@PathVariable` 注解用于将路径参数 `{id}` 的值绑定到方法的参数 `id` 上。当请求的路径是 "/path/123" 时,`@PathVariable` 会将路径中的 "123" 值绑定到方法的参数 `id` 上。 ```java public String pathParam(@PathVariable Integer id) { System.out.println(id); return "OK"; } //参数名与路径名不同 @GetMapping("/{id}") public ResponseEntity getUserById(@PathVariable("id") Long userId) { } ``` 5. `@RequestParam`,如果方法的参数名与请求参数名不同,需要在 `@RequestParam` 注解中指定请求参数的名字。 类似`@PathVariable`,可以指定参数名称。 ```java @RequestMapping("/example") public String exampleMethod(@RequestParam String name, @RequestParam("age") int userAge) { // 在方法内部使用获取到的参数值进行处理 System.out.println("Name: " + name); System.out.println("Age: " + userAge); return "OK"; } ``` 还可以设置默认值 ```java @RequestMapping("/greet") public String greet(@RequestParam(defaultValue = "Guest") String name) { return "Hello, " + name; } ``` 如果既改请求参数名字,又要设置默认值 ```java @RequestMapping("/greet") public String greet(@RequestParam(value = "age", defaultValue = "25") int userAge) { return "Age: " + userAge; } ``` 5. 控制反转与依赖注入: `@Component`、`@Service`、`@Repository` 用于标识 bean 并让容器管理它们,从而实现 IoC。 `@Autowired`、`@Configuration`、`@Bean` 用于实现 DI,通过容器自动装配或配置 bean 的依赖。 7. **数据库相关。** `@Mapper`注解:表示是mybatis中的Mapper接口,程序运行时,框架会自动生成接口的实现类对象(代理对象),**并交给Spring的IOC容器管理** `@Select`注解:代表的就是select查询,用于书写select查询语句 7. `@SpringBootTest`:它会启动 Spring 应用程序上下文,并在测试期间模拟运行整个 Spring Boot 应用程序。这意味着你可以在集成测试中使用 Spring 的各种功能,例如**自动装配、依赖注入、配置加载**等。 9. lombok的相关注解。非常实用的工具库。 - 在pom.xml文件中引入依赖 ```xml org.projectlombok lombok ``` - 在实体类上添加以下注解(加粗为常用) | **注解** | **作用** | | ----------------------- | ------------------------------------------------------------ | | @Getter/@Setter | 为所有的属性提供get/set方法 | | @ToString | 会给类自动生成易阅读的 toString 方法 | | @EqualsAndHashCode | 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 | | **@Data** | **提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)** | | **@NoArgsConstructor** | 为实体类生成无参的构造器方法 | | **@AllArgsConstructor** | 为实体类生成除了static修饰的字段之外带有各参数的构造器方法。 | | **@Slf4j** | 可以log.info("输出日志信息"); | ```java //equals 方法用于比较两个对象的内容是否相同 Address addr1 = new Address("SomeProvince", "SomeCity"); Address addr2 = new Address("SomeProvince", "SomeCity"); System.out.println(addr1.equals(addr2)); // 输出 true ``` 10. `@Test`,Junit测试单元,可在测试类中定义测试函数,一次性执行所有@Test注解下的函数,不用写main方法 11. `@Override`,当一个方法在子类中覆盖(重写)了父类中的同名方法时,为了确保正确性,可以使用 `@Override` 注解来标记这个方法,这样编译器就能够帮助检查是否正确地重写了父类的方法。 12. `@DateTimeFormat`将日期转化为指定的格式。Spring会尝试将接收到的**字符串参数**转换为控制器方法参数的相应类型。 ```java @RestController public class DateController { // 例如:请求 URL 为 /search?begin=2025-03-28 @GetMapping("/search") public String search(@RequestParam("begin") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin) { // 此时 begin 已经是 LocalDate 类型,可以直接使用 return "接收到的日期是: " + begin; } } ``` 12. @RestControllerAdvice= @ControllerAdvice + @ResponseBody。加上这个注解就代表我们定义了一个全局异常处理器,而且处理异常的方法返回值会转换为json后再响应给前端 ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public String handleException(Exception ex) { // 返回错误提示或错误详情 return "系统发生异常:" + ex.getMessage(); } } ``` 12. `@Configuration`和`@Bean`配合使用,可以对第三方bean进行**集中**的配置管理,依赖注入!!`@Bean`用于方法上。加了`@Configuration`,当Spring Boot应用**启动时,它会执行**一系列的自动配置步骤。 12. `@ComponentScan`指定了Spring应该在哪些包下搜索带有`@Component`、`@Service`、`@Repository`、`@Controller`等注解的类,以便将这些类自动注册为Spring容器管理的Bean.`@SpringBootApplication`它是一个便利的注解,组合了`@Configuration`、`@EnableAutoConfiguration`和`@ComponentScan`注解。 ## Mybatis ### 快速创建 ![image-20240307125505211](https://pic.bitday.top/i/2025/03/19/u6pfoj-2.png) 1. 创建springboot工程(Spring Initializr),并导入 mybatis的起步依赖、mysql的驱动包。创建用户表user,并创建对应的实体类User ![image-20240307125820685](https://pic.bitday.top/i/2025/03/19/u6q96d-2.png) 2. 在springboot项目中,可以编写main/resources/application.properties文件,配置数据库连接信息。 ``` #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=1234 ``` 3. 在引导类所在包下,在创建一个包 mapper。在mapper包下创建一个接口 UserMapper ![image-20240307132356616](https://pic.bitday.top/i/2025/03/19/u6qtz4-2.png) @Mapper注解:表示是mybatis中的Mapper接口 ​ -程序运行时:框架会自动生成接口的**实现类对象(代理对象)**,并交给Spring的IOC容器管理 @Select注解:代表的就是select查询,用于书写select查询语句 ```java @Mapper public interface UserMapper { //查询所有用户数据 @Select("select * from user") public List list(); } ``` ### 数据库连接池 数据库连接池是一个容器,负责管理和分配数据库连接(`Connection`)。 - 在程序启动时,连接池会创建一定数量的数据库连接。 - 客户端在执行 SQL 时,从连接池获取连接对象,执行完 SQL 后,将连接归还给连接池,以供其他客户端复用。 - 如果连接对象长时间空闲且超过预设的最大空闲时间,连接池会自动释放该连接。 **优势**:避免频繁创建和销毁连接,提高数据库访问效率。 Druid(德鲁伊) * Druid连接池是阿里巴巴开源的数据库连接池项目 * 功能强大,性能优秀,是Java语言最好的数据库连接池之一 把默认的 Hikari 数据库连接池切换为 Druid 数据库连接池: 1. 在pom.xml文件中引入依赖 ```xml com.alibaba druid-spring-boot-starter 1.2.8 ``` 2. 在application.properties中引入数据库连接配置 ```properties spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.url=jdbc:mysql://localhost:3306/mybatis spring.datasource.druid.username=root spring.datasource.druid.password=1234 ``` ### SQL注入问题 SQL注入:由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。 在Mybatis中提供的参数占位符有两种:${...} 、#{...} - #{...} - 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值 - 使用时机:参数传递,都使用#{…} - ${...} - 拼接SQL。直接将参数拼接在SQL语句中,**存在SQL注入问题** - 使用时机:如果对表名、列表进行动态设置时使用 ### 日志输出 只建议开发环境使用:在Mybatis当中我们可以借助日志,查看到sql语句的执行、执行传递的参数以及执行结果 1. 打开application.properties文件 2. 开启mybatis的日志,并指定输出到控制台 ```java #指定mybatis输出日志的位置, 输出控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl ``` ### 驼峰命名法 在 Java 项目中,数据库表字段名一般使用 **下划线命名法**(snake_case),而 Java 中的变量名使用 **驼峰命名法**(camelCase)。 - [x] **小驼峰命名(lowerCamelCase)**: - 第一个单词的首字母小写,后续单词的首字母大写。 - **例子**:`firstName`, `userName`, `myVariable` **大驼峰命名(UpperCamelCase)**: - 每个单词的首字母都大写,通常用于类名或类型名。 - **例子**:`MyClass`, `EmployeeData`, `OrderDetails` 表中查询的数据封装到实体类中 - 实体类属性名和数据库表查询返回的**字段名一致**,mybatis会自动封装。 - 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。 ![image-20221212103124490](https://pic.bitday.top/i/2025/03/19/u6o894-2.png) 解决方法: 1. 起别名 2. 结果映射 3. **开启驼峰命名** 4. **属性名和表中字段名保持一致** **开启驼峰命名(推荐)**:如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射 > 驼峰命名规则: abc_xyz => abcXyz > > - 表中字段名:abc_xyz > - 类中属性名:abcXyz ```java # 在application.properties中添加: mybatis.configuration.map-underscore-to-camel-case=true ``` ### 增删改 - **增删改通用!:返回值为int时,表示影响的记录数,一般不需要可以设置为void!** **作用于单个字段** ```java @Mapper public interface EmpMapper { //SQL语句中的id值不能写成固定数值,需要变为动态的数值 //解决方案:在delete方法中添加一个参数(用户id),将方法中的参数,传给SQL语句 /** * 根据id删除数据 * @param id 用户id */ @Delete("delete from emp where id = #{id}")//使用#{key}方式获取方法中的参数值 public void delete(Integer id); } ``` ![image-20240312122323753](https://pic.bitday.top/i/2025/03/19/u6mu7z-2.png) 上图参数值分离,有效防止SQL注入 **作用于多个字段** ```java @Mapper public interface EmpMapper { //会自动将生成的主键值,赋值给emp对象的id属性 @Options(useGeneratedKeys = true,keyProperty = "id") @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})") public void insert(Emp emp); } ``` 在 **`@Insert`** 注解中使用 `#{}` 来引用 `Emp` 对象的属性,MyBatis 会自动从 `Emp` 对象中提取相应的字段并绑定到 SQL 语句中的占位符。 `@Options(useGeneratedKeys = true, keyProperty = "id")` 这行配置表示,插入时自动生成的主键会赋值给 `Emp` 对象的 `id` 属性。 ``` // 调用 mapper 执行插入操作 empMapper.insert(emp); // 现在 emp 对象的 id 属性会被自动设置为数据库生成的主键值 System.out.println("Generated ID: " + emp.getId()); ``` ### 查 查询案例: - **姓名:要求支持模糊匹配** - 性别:要求精确匹配 - 入职时间:要求进行范围查询 - 根据最后修改时间进行降序排序 重点在于模糊查询时where name like '%#{name}%' 会报错。 解决方案: 使用MySQL提供的字符串拼接函数:`concat('%' , '关键字' , '%')` **`CONCAT()`** 如果其中任何一个参数为 **`NULL`**,`CONCAT()` 返回 **`NULL`**,`Like NULL`会导致查询不到任何结果! `NULL`和`''`是完全不同的 ```java @Mapper public interface EmpMapper { @Select("select * from emp " + "where name like concat('%',#{name},'%') " + "and gender = #{gender} " + "and entrydate between #{begin} and #{end} " + "order by update_time desc") public List list(String name, Short gender, LocalDate begin, LocalDate end); } ``` ### XML配置文件规范 使用Mybatis的注解方式,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中。 在Mybatis中使用XML映射文件方式开发,需要符合一定的规范: 1. XML映射**文件的名称**与Mapper**接口名称**一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名) 2. XML映射文件的**namespace属性**为Mapper接口**全限定名**一致 3. XML映射文件中sql语句的**id**与Mapper接口中的**方法名**一致,并保持返回类型一致。 ![image-20221212153529732](https://pic.bitday.top/i/2025/03/19/u6su5s-2.png) \ select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc ``` **`id="list"`**:指定查询方法的名称,应该与 Mapper 接口中的方法名称一致。 **`resultType="edu.whut.pojo.Emp"`**:`resultType` 只在 **查询操作** 中需要指定。指定查询结果映射的对象类型,这里是 `Emp` 类。 这里有bug!!! `concat('%',#{name},'%')`这里应该用`` ``标签对name是否为`NULL`或`''`进行判断 ### 动态SQL #### SQL-if,where ``:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。 ~~~xml 要拼接的sql语句 ~~~ ``只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或OR,**加了总比不加好** ```java ``` #### SQL-foreach Mapper 接口 ```java @Mapper public interface EmpMapper { //批量删除 public void deleteByIds(List ids); } ``` XML 映射文件 `` 标签用于遍历集合,常用于动态生成 SQL 语句中的 IN 子句、批量插入、批量更新等操作。 ```java ``` `open="("`:这个属性表示,在*生成的 SQL 语句开始*时添加一个 左括号 `(`。 `close=")"`:这个属性表示,在生成的 SQL 语句结束时添加一个 右括号 `)`。 例:批量删除实现 ```java DELETE FROM emp WHERE id IN #{id} ``` 实现效果类似:`DELETE FROM emp WHERE id IN (1, 2, 3);` ## 案例实战 ### 分页查询 传统员工分页查询分析: ![image-20221215153413290](https://pic.bitday.top/i/2025/03/19/u6p5ae-2.png) **采用分页插件PageHelper:** ![image-20221215170038833](https://pic.bitday.top/i/2025/03/19/u6o1ho-2.png) **在执行empMapper.list()方法时,就是执行:select * from emp 语句,怎么能够实现分页操作呢?** 分页插件帮我们完成了以下操作: 1. 先获取到要执行的SQL语句: ``` select * from emp ``` 2. 为了实现分页,第一步是获取符合条件的总记录数。分页插件将原始 SQL 查询中的 `SELECT *` 改成 `SELECT count(*)` ```mysql select count(*) from emp; ``` 3. 一旦知道了总记录数,分页插件会将 `SELECT *` 的查询语句进行修改,加入 `LIMIT` 关键字,限制返回的记录数。 ```mysql select * from emp limit ?, ? ``` 第一个参数(`?`)是 起始位置,通常是 `(当前页 - 1) * 每页显示的记录数`,即从哪一行开始查询。 第二个参数(`?`)是 每页显示的记录数,即返回多少条数据。 4. 执行分页查询,例如,假设每页显示 10 条记录,你请求第 2 页数据,那么 SQL 语句会变成: ```mysql select * from emp limit 10, 10; ``` **使用方法:** 当使用 **PageHelper** 分页插件时,无需在 Mapper 中手动处理分页。只需在 Mapper 中编写常规的列表查询。 - 在 **Service 层**,调用 Mapper 方法之前,**设置分页参数**。 - 调用 Mapper 查询后,**自动进行分页**,并将结果封装到 `PageBean` 对象中返回。 1、在pom.xml引入依赖 ```java com.github.pagehelper pagehelper-spring-boot-starter 1.4.2 ``` 2、EmpMapper ```java @Mapper public interface EmpMapper { //获取当前页的结果列表 @Select("select * from emp") public List list(); } ``` 3、EmpServiceImpl 当调用 `PageHelper.startPage(page, pageSize)` 时,PageHelper 插件会拦截随后的 SQL 查询,自动修改查询,加入 `LIMIT` 子句来实现分页功能。 ```java @Override public PageBean page(Integer page, Integer pageSize) { // 设置分页参数 PageHelper.startPage(page, pageSize); //page是页号,不是起始索引 // 执行分页查询 List empList = empMapper.list(); // 获取分页结果 Page p = (Page) empList; //封装PageBean PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); return pageBean; } ``` 4、Controller ```java @Slf4j @RestController @RequestMapping("/emps") public class EmpController { @Autowired private EmpService empService; //条件分页查询 @GetMapping public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize) { //记录日志 log.info("分页查询,参数:{},{}", page, pageSize); //调用业务层分页查询功能 PageBean pageBean = empService.page(page, pageSize); //响应 return Result.success(pageBean); } } ``` ### 条件分页查询 思路分析: ![image-20221215180528415](https://pic.bitday.top/i/2025/03/19/u6mjck-2.png) ```java