Spring Boot+Mybatis实现增删改查接口开发+测试(超详细建议收藏)

前言

Java也是测试必知必会的内容,特别是现在类似spring boot 等Java框架更是成为主流。之前实现的图书增删改查是用Python实现的,没看过的请移步:Flask+mysql 实现增删改查接口开发+测试(图文教程附源码),本次给大家带来用Java实现的后端接口版本,并根据之前的项目总结有做一些优化

一、接口回顾

Python版本增删改查成功示例

1.1 图书增加

请求示例:

{"title":"《Flask框架》","author":"pycharm","read_status":1
}

响应示例: 

{"message": "图书添加成功!","status": "success"
}

完整截图:

1.2 图书删除

请求示例:

{"id":"49"
}

响应示例: 

{"message": "图书被删除!","status": "success"
}

完整截图:

1.3 图书修改

请求示例:

{"id": 70,"title": "《默8888888888》","author": "陈玉名","read_status": "1"
}

响应示例: 

{"message": "图书已更新!","status": "success"
}

完整截图:

1.4 图书查询

请求示例:

{"id":"55"
}

响应示例: 

{"data": [{"author": "罗贯中","id": 55,"read_status": true,"title": "《三国演义》"}],"message": "图书查询成功!","status": "success"
}

完整截图:

1.5 分页查询

请求示例:

{"page": 1,"pagesize": 5
}

响应示例: 

{"data": [{"author": "罗贯中","id": 50,"read_status": true,"title": "《三国演义》"},{"author": "罗贯中","id": 51,"read_status": true,"title": "《三国演义》"},{"author": "罗贯中","id": 52,"read_status": true,"title": "《三国演义》"},{"author": "罗贯中","id": 53,"read_status": true,"title": "《三国演义》"},{"author": "罗贯中","id": 54,"read_status": true,"title": "《三国演义》"}],"message": "图书查询成功!","page": 1,"pagesize": 5,"status": "success","total": 22
}

完整截图:

 上面我们对相关接口的请求入参,响应复习了一遍,注意请求结构、字段类型、请求方法、请求地址、接口响应格式、接口响应字段类型

下面我们就要用spring boot分层开发实现以上几个接口,把查询和分页查询合并一个接口,这也是一个优化点

二、技术准备

之前博客有简单使用springboot、Mybatis等的文章,本篇文章再次讲述下相关知识

2.1 spring boot

Spring Boot 是一个用于快速开发应用程序的框架,它建立在 Spring 框架的基础之上,旨在简化配置和部署过程,使开发者能够更专注于业务逻辑的实现。

以下是 Spring Boot 的一些主要特点:

  1. 简化配置: Spring Boot 提供了默认的配置选项,减少了开发者在配置方面的工作量。它使用约定大于配置的原则,根据项目的依赖和类型,自动配置应用程序的不同部分。

  2. 内嵌式 Web 服务器: Spring Boot 集成了多种内嵌式 Web 服务器,如Tomcat、Jetty和Undertow,使得构建和运行 Web 应用变得更加简单。

  3. 自动化依赖管理: Spring Boot 可以管理项目的依赖,自动选择合适的版本,简化了依赖管理的复杂性。

  4. 自动化配置: Spring Boot 根据项目的依赖和需要,自动进行一些常见配置,如数据源、消息队列等,减少了手动配置的步骤。

  5. 开箱即用的功能: Spring Boot 提供了许多常见功能的开箱即用实现,如安全认证、缓存、监控等,减少了开发者编写大量样板代码的工作。

  6. 简化部署: Spring Boot 支持将应用程序打包为可执行的 JAR 或 WAR 文件,以及支持 Docker 容器化部署,使部署变得更加方便。

  7. 微服务支持: Spring Boot 能够与 Spring Cloud 结合使用,为微服务架构提供支持,帮助开发者构建分布式系统。

总之,Spring Boot 旨在简化 Spring 应用程序的开发、测试、部署和运维过程,使开发者能够更快速、更高效地开发出高质量的应用程序。它适用于各种规模的项目,从小型应用到大型企业级系统。

2.2 Mybatis

MyBatis(之前称为iBATIS)是一个开源的持久层框架,用于将 Java 对象映射到关系型数据库中的数据。它提供了一种将 SQL 查询、更新和删除等操作与 Java 对象之间的映射关系的方式,从而使数据库操作更加简化和灵活。

以下是 MyBatis 的一些主要特点:

  1. 灵活的SQL映射: MyBatis 允许开发者编写原生的 SQL 查询,而不需要过多的 ORM(对象关系映射)层。这使开发者能够更精确地控制 SQL 查询的执行,并更好地优化数据库操作。

  2. 参数绑定: MyBatis 提供了参数绑定的功能,可以将 Java 对象中的属性值绑定到 SQL 查询中的参数位置,从而更方便地执行动态 SQL 查询。

  3. 结果映射: MyBatis 可以将查询结果映射为 Java 对象,开发者可以通过配置来指定查询结果与 Java 对象之间的映射关系。

  4. 支持存储过程和高级映射: MyBatis 支持存储过程调用和一些复杂的结果映射,使开发者能够处理更复杂的数据库操作。

  5. 缓存支持: MyBatis 提供了一些级别的缓存支持,可以提高查询的性能。

  6. 插件机制: MyBatis 允许开发者编写插件来扩展框架的功能,例如添加自定义的拦截器、执行器等。

  7. XML配置和注解: MyBatis 支持通过 XML 配置文件或者注解的方式来配置映射关系和查询。

  8. 与各种数据库兼容: MyBatis 支持多种数据库,并且不需要太多的配置就可以适应不同数据库的特性。

总之,MyBatis是一个轻量级的持久层框架,它通过提供简洁的方式来操作数据库,使开发者能够更加灵活和高效地进行数据库操作,而无需深入学习复杂的 ORM 框架。

2.3 Maven

Maven 是一个流行的项目构建和管理工具,用于帮助开发团队管理项目的构建、依赖管理和文档生成等任务。它提供了一种标准化的方式来管理项目的生命周期,从而简化了项目构建、发布和维护过程。

以下是 Maven 的一些主要特点和功能:

  1. 项目结构标准化: Maven 鼓励使用一种特定的项目结构,包括源代码目录、资源文件目录、测试目录等。这有助于团队成员更容易理解和协同开发项目。

  2. 依赖管理: Maven 使用一个称为 "坐标" 的标识符来唯一标识项目的依赖库。通过在项目的配置文件中声明依赖,Maven 可以自动下载和管理所需的库文件。

  3. 自动构建: Maven 使用 POM(项目对象模型)文件来描述项目的构建配置,包括源代码目录、依赖、插件等。开发者只需编写 POM 文件,Maven 就能自动执行构建任务。

  4. 插件体系: Maven 提供了丰富的插件来执行各种任务,例如编译、测试、打包、部署等。开发者可以通过配置插件来定制项目的构建过程。

  5. 生命周期和阶段: Maven 将项目的构建过程划分为生命周期(Lifecycle),每个生命周期又分为不同的阶段(Phase)。例如,项目的生命周期包括 validate、compile、test、package、install、deploy 等阶段。

  6. 仓库管理: Maven 使用中央仓库作为默认的依赖库,开发者可以将自己的构建产物发布到本地仓库或者远程仓库。

  7. 多模块支持: Maven 支持将一个大型项目划分为多个子模块,每个模块可以有自己的 POM 文件和构建配置,从而更好地管理复杂的项目结构。

  8. 文档生成: Maven 可以通过插件自动生成项目的文档,包括代码文档、用户文档等。

总之,Maven 是一个用于项目构建和管理的工具,它提供了一套规范和标准,使开发者能够更高效地进行项目的构建、依赖管理和发布等任务。

2.4 MySQL

MySQL 是一种开源的关系型数据库管理系统(RDBMS),它是一种广泛使用的数据库技术,用于存储和管理结构化数据。MySQL 最初由瑞典公司 MySQL AB 开发,后来被 Sun Microsystems 收购,再后来 Sun Microsystems 被 Oracle Corporation 收购,因此 MySQL 也被称为 Oracle MySQL。

以下是 MySQL 数据库的一些主要特点和功能:

  1. 关系型数据库: MySQL 是一种关系型数据库,数据以表格的形式存储,表格中的数据之间通过键值关系相互连接。

  2. 开源和免费: MySQL 是开源软件,因此可以免费使用和分发。开源性质使得它受到广泛的社区支持和贡献。

  3. 跨平台支持: MySQL 可以在多种操作系统上运行,包括 Windows、Linux、macOS 等。

  4. 性能优化: MySQL 在读写速度上具有优异的性能,特别是对于高并发读取请求和大规模数据操作。

  5. 数据安全性: MySQL 提供了数据的访问控制、权限管理以及数据加密等功能,以确保数据的安全性和保密性。

  6. 事务支持: MySQL 支持事务,可以保证在一系列操作中的原子性、一致性、隔离性和持久性(ACID 属性)。

  7. 存储引擎: MySQL 支持多种存储引擎,如 InnoDB、MyISAM 等,不同的存储引擎提供了不同的性能和特性。

  8. SQL 支持: MySQL 使用结构化查询语言(SQL)作为与数据库进行交互的标准语言。

  9. 复制和集群: MySQL 提供了复制和集群功能,允许在多台服务器之间同步数据,实现高可用性和负载均衡。

  10. 备份和恢复: MySQL 支持数据备份和恢复,使用户能够定期保存数据以应对意外数据丢失。

总之,MySQL 是一款功能强大的开源关系型数据库管理系统,适用于各种规模的应用,从个人项目到大型企业应用都可以使用它来存储和管理数据。

三、工具准备

上面是理论,这一篇我们动手前检查一下环境

3.1 IDEA

首先装IDEA前,确保你本地有安装java环境,本篇才用jdk 17,安装教程有很多此处不再介绍

我用的是2020的pojie版本

3.2 Navicat

确保电脑安装了mysql,或者其他机器上。Navicat安装也简单此处也不再赘述

主要是为了验证数据方便

这里为了和Python版本区分,我另起了一个book表,之前的是books,并把read_status设计为int。在实际工作中也是这样,有多个枚举值的定义为int,后期好拓展

重新设计的表DDL如下:

CREATE TABLE `book` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自动递增id,唯一键',`title` varchar(80) NOT NULL COMMENT '书名',`author` varchar(120) NOT NULL COMMENT '作者',`read_status` int(11) DEFAULT NULL COMMENT '阅读状态,0未读,1已读',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8;

四、项目工程结构

4.1 项目目录结构

4.2 项目目录文件解析

4.2.1 config

下有2个类:

DataSourceConfig.java、MyBatisConfig.java这2个是Mybatis调试过程中禁用了Spring Boot默认的数据源自动配置 学习的文件,暂时不用管。

4.2.2 controller

下有1个类:BookController ,用来处理前端请求的,前端或者postman请求首先会到达这一层,也叫控制层

4.2.3 domain

下有3个类:

BaseInput:封装请求用的,实际项目会用上,此处没时间封装请求

Book:最主要的实体类或者模型类,字段和setter、getter方法都在这里,注意,实际项目会采用@Data注解等去掉setter、getter方法

ResponseData :封装响应类,实际项目也会有

4.2.4 mapper

包下有1个类:BookMapper

持久层,主要与数据库进行交互。也叫DAO层,现在用 Mybatis 逆向工程生成的 mapper 层,其实就是 DAO 层。DAO 层会调用entity(domain)层,DAO 中会定义实际使用到的方法,比如增删改查

4.2.5 service

包下有1个类 BookService
,1个包impl,包下有服务实现类 BookServiceImpl、自定义异常类BookNotFoundException

4.2.6 启动类

和上面 包平级的BookApplication类,是spring boot项目默认生成的启动类

4.2.7 resources下的mapper

有1个xml文件BookMapper.xml和mapper(DAO)层交互,主要写的是SQL语句

 4.2.8 resources 根目录下的配置文件

application.properties文件或者application.yml文件。都可以,但是要注意2种文件的配置格式。这里采用的是yml文件,即application.yml

 4.2.9 依赖文件

pom.xml,存放一些依赖,可以在这里引入依赖,更改依赖

一定要注意目录结构,否则会有各种报错。

关于spring boot各层的详细解析参考我的博文:Spring Boot 各层作用与联系

五、增删改查接口开发

前端请求过来遵循controller->mapper->service->entity我们开始写代码

5.1 图书增加接口

5.1.1 controller.java

@RequestMapping("/api/book")
public class BookController {private static final Logger logger = LoggerFactory.getLogger(BookController.class);private final BookService bookService;@Autowiredpublic BookController(BookService bookService){this.bookService = bookService;}@PostMapping("/add")public ResponseEntity<Map<String, String>> addBook(@RequestBody Book book) {try {//System.out.println("前端传值ReadStatus:"+book.getReadStatus());bookService.insertData(book);Map<String, String> response = new HashMap<>();response.put("message", "图书添加成功!");response.put("status", "success");return ResponseEntity.ok(response);} catch (IllegalArgumentException e) {Map<String, String> response = new HashMap<>();response.put("message", e.getMessage());response.put("status", "fail");return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);}}

代码点评:

这段代码是一个 Spring Boot 控制器(Controller),用于处理关于图书(Book)的 HTTP 请求。下面对其中的主要部分进行解释:

  1. @RequestMapping("/api/book"): 这个注解将类映射到 "/api/book" 路径,表示这个控制器处理与图书相关的 API 请求。

  2. private final BookService bookService;: 这是一个私有的成员变量,类型为 BookService,用于在控制器中调用与图书相关的业务逻辑方法。

  3. @Autowired: 这个注解用于自动装配 BookService 实例,即将 bookService 成员变量与 Spring 容器中的 BookService 对象关联起来。

  4. @PostMapping("/add"): 这个注解表示该方法处理 HTTP POST 请求,路径为 "/add",用于添加图书信息。

  5. public ResponseEntity<Map<String, String>> addBook(@RequestBody Book book): 这是处理添加图书请求的方法。它接收一个 HTTP 请求体中的 JSON 数据,并将其映射为一个 Book 对象。返回类型为 ResponseEntity<Map<String, String>>,即返回一个带有消息和状态信息的响应。

  6. try: 这是一个异常处理的开始,表示尝试执行以下代码块,如果出现异常则跳到 catch 部分进行处理。

  7. bookService.insertData(book);: 调用 bookServiceinsertData 方法来将传入的图书信息插入到数据库中。

  8. Map<String, String> response = new HashMap<>();: 创建一个 HashMap 作为响应体,用于存储返回给客户端的消息和状态信息。

  9. response.put("message", "图书添加成功!");: 将消息和状态信息放入响应体中。

  10. response.put("status", "success");: 将操作状态放入响应体中。

  11. return ResponseEntity.ok(response);: 返回一个 HTTP 响应,状态码为 200(成功),将上述响应体包装在 ResponseEntity 中。

  12. catch (IllegalArgumentException e) { ... }: 这是异常处理的部分,如果在 try 块中出现了 IllegalArgumentException 异常,就会执行这里的代码。

  13. catch 部分中,创建一个包含错误消息和状态的响应体,然后使用 ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response) 返回一个 HTTP 响应,状态码为 400(客户端请求错误),将错误信息包装在 ResponseEntity 中。

总之,这段代码定义了一个用于处理添加图书请求的 Spring Boot 控制器方法。它接收来自客户端的图书数据,将数据传递给业务逻辑层进行处理,然后返回适当的响应消息和状态信息。

5.1.2 BookService.java

void insertData(Book book);

 代码点评:

void insertData(Book book); 是一个方法声明,它位于某个 Java 接口(或抽象类)中,用于定义一个接口方法。这个方法声明的含义如下:

  1. void: 方法返回类型。这里是 void,表示这个方法没有返回值,即不返回任何数据。

  2. insertData: 方法名称。这个方法被称为 insertData,这是一个自定义的方法名,用于表示将一本图书信息插入到某个数据源(如数据库)中。

  3. (Book book): 方法参数。这里的 (Book book) 表示这个方法接受一个名为 book 的参数,参数类型是 Book 类。这个参数将用于传递要插入的图书信息。

综合起来,这个方法声明表示一个用于将图书信息插入到数据源中的接口方法。实际的实现代码应该在某个类中,实现了包含这个方法声明的接口,以便在调用时执行插入操作。在你的代码示例中,这个方法是在 BookService 接口中声明的,具体的实现应该在 BookServiceImpl 类中

5.1.3 BookServiceImpl.java

    @Overridepublic void insertData(Book book) {if (book.getTitle() == null || book.getAuthor() == null || book.getReadStatus() == null) {throw new IllegalArgumentException("title、author和read_status是必传参数!");}// Check for duplicate titleString title = book.getTitle().trim();if (bookMapper.existsByTitle(title)) {throw new IllegalArgumentException("书名(title)重复!");}bookMapper.insertData(book);
}

代码点评: 

这段代码是一个方法的实现,它覆盖(Override)了一个接口或父类中的同名方法。具体解释如下:

  1. @Override: 这是一个注解,表示这个方法是对父类或接口中同名方法的重写。在 Java 中,如果一个类实现了某个接口,或者一个类继承了另一个类,可以通过使用 @Override 注解来表明某个方法是对父类或接口中方法的重写。

  2. public void insertData(Book book) { ... }: 这是一个方法的实现。它覆盖了某个接口或父类中声明的 insertData 方法。方法签名中的参数类型和返回类型必须与被重写的方法一致。

  3. 方法体: 方法体包含了实际的代码逻辑。这段代码的目的是在插入图书信息之前进行一些验证和处理:

    • 首先,检查传入的 book 对象的 titleauthorreadStatus 是否为 null。如果有任何一个为 null,就会抛出 IllegalArgumentException 异常,提示必要的参数不能为空。

    • 然后,对传入的 title 进行修整,去掉前后的空格,以确保数据的准确性。

    • 接着,使用 bookMapper.existsByTitle(title) 方法检查数据库中是否已经存在具有相同标题的图书。如果存在重复的标题,会抛出 IllegalArgumentException 异常,提示标题重复。

    • 最后,如果验证通过,调用 bookMapper.insertData(book) 方法将传入的图书信息插入到数据源中。

总之,这段代码实现了向数据源插入图书信息的操作,并在插入前进行了一些数据验证和处理,以确保数据的完整性和准确性。

5.1.4 BookMapper.java

    void insertData(Book book);

代码点评: 

在 MyBatis 中,Mapper 接口的方法声明定义了对数据库的操作。这个 void insertData(Book book); 就是一个 Mapper 接口中的方法声明,它指定了一个用于向数据库插入图书信息的操作。

具体解释如下:

  1. void insertData(Book book);: 这是一个接口中的方法声明。它告诉 MyBatis 框架在映射配置文件中找到名为 "insertData" 的 SQL 语句,并将参数 book 中的数据插入到数据库中。

    • void: 表示该方法没有返回值。
    • insertData: 方法的名称,与映射配置文件中的 <insert> 元素的 id 属性对应。
    • (Book book): 方法的参数,表示插入操作需要传入一个名为 book 的参数,它的类型是 Book 类(或其子类)。

这样,当你在 MyBatis 映射配置文件中配置了一个名为 "insertData" 的 <insert> 元素,定义了插入操作的 SQL 语句,并在代码中调用了 insertData(book) 方法时,MyBatis 会根据配置文件的定义,执行对应的 SQL 语句,将 book 对象的数据插入到数据库中。

5.1.5 BookMapper.xml

    <!-- 增加 --><insert id="insertData">INSERT INTO book (title, author, read_status)VALUES (#{title}, #{author}, #{readStatus})</insert>

代码点评: 

这段 XML 代码是 MyBatis 映射文件中的一个 SQL 映射,它定义了一个插入操作,用于向数据库中的 "book" 表中插入数据。具体解释如下:

  • <insert id="insertData">: 这是一个插入操作的声明,通过 id 属性指定了该操作的标识名,这样在 Java 代码中就可以通过这个标识名来调用这个操作。

  • INSERT INTO book (title, author, read_status): 这是 SQL 语句的一部分,表示将数据插入到 "book" 表中,指定了插入的目标表和需要插入的字段。

  • VALUES (#{title}, #{author}, #{readStatus}): 这也是 SQL 语句的一部分,用来指定插入的具体数值。#{title}, #{author}, #{readStatus} 是 MyBatis 的占位符语法,表示这里会将 Java 对象中的属性值填充进去。

    • #{title}: 这是占位符,会被实际的 title 属性值替代。
    • #{author}: 同样是占位符,会被实际的 author 属性值替代。
    • #{readStatus}: 这也是占位符,会被实际的 readStatus 属性值替代。

综合起来,这段 XML 代码定义了一个插入操作,会将传入的 titleauthorreadStatus 属性值插入到数据库的 "book" 表中的相应字段中。

5.2 图书删除接口

5.2.1 controller.java

    public ResponseEntity<Map<String, String>> deleteById(@RequestBody Map<String, Object> requestParams) {Map<String, Object> data = (Map<String, Object>) requestParams.get("data");Long id = Long.valueOf(data.get("id").toString());// 构建返回结果boolean isDeleted = bookService.deleteById(id);if (isDeleted) {Map<String, String> successResponse = new HashMap<>();successResponse.put("message", "图书被删除!");successResponse.put("status", "success");return ResponseEntity.ok(successResponse);} else {Map<String, String> failResponse = new HashMap<>();failResponse.put("message", "需要删除的图书不存在!");failResponse.put("status", "fail");return ResponseEntity.status(HttpStatus.NOT_FOUND).body(failResponse);}}

代码点评: 

这段代码是一个 Spring Boot 控制器方法,用于处理删除图书的请求。下面是代码的解释:

  • public ResponseEntity<Map<String, String>> deleteById(@RequestBody Map<String, Object> requestParams) {: 这是一个控制器方法的声明。它使用了 @RequestBody 注解,表示请求体中的数据会被映射为一个 Map 对象,并且返回一个 ResponseEntity 对象,该对象包含了响应数据以及 HTTP 状态码等信息。

  • Map<String, Object> data = (Map<String, Object>) requestParams.get("data");: 这行代码从请求参数的 data 字段中获取了一个嵌套的 Map 对象,这个嵌套的 Map 包含了需要的删除操作数据。

  • Long id = Long.valueOf(data.get("id").toString());: 这行代码从嵌套的 Map 中获取了键为 "id" 的值,并将其转换为 Long 类型,这个 id 表示要删除的图书的唯一标识。

  • boolean isDeleted = bookService.deleteById(id);: 这行代码调用了一个 bookService 的方法来进行实际的删除操作,它会尝试根据传入的 id 来删除对应的图书数据,并返回一个布尔值表示是否删除成功。

  • if (isDeleted) { ... } else { ... }: 这是一个条件语句,用于根据删除操作的结果返回不同的响应。如果删除成功,就返回一个成功的响应,其中包含成功的消息和状态。如果删除失败,就返回一个失败的响应,其中包含失败的消息和状态,并且设置了 HTTP 响应状态码为 404(NOT FOUND)。

综合起来,这段代码实现了一个删除图书的功能,它会从请求体中获取图书的唯一标识 id,然后调用服务层的方法来删除图书,并根据删除操作的结果返回不同的响应。

5.2.2 BookService.java

  boolean deleteById(Long id);boolean existsByTitle(@Param("title") String title);

代码点评: 

当你需要删除一本图书时,你可以调用 boolean deleteById(Long id); 方法。这个方法通常是在一个服务接口或类中定义的。它使用图书的唯一标识符(id)来从数据库中删除对应的图书。方法的工作方式如下:

  • Long id:这个参数表示要删除的图书的唯一标识符。

  • boolean:方法的返回类型表明删除是否成功。如果图书成功删除,方法返回 true,如果删除失败,返回 false

另一个方法是 boolean existsByTitle(@Param("title") String title);。这个方法也通常在一个服务接口或类中定义。它用于检查数据库中是否存在具有给定标题的图书。方法的工作方式如下:

  • @Param("title") String title:这个参数表示要检查的图书的标题。

  • boolean:方法的返回类型表明具有给定标题的图书是否存在于数据库中。如果存在,方法返回 true,如果不存在,返回 false

这两个方法经常结合使用,以执行与在数据库中管理图书相关的各种操作。deleteById 方法负责根据图书的 ID 删除图书,而 existsByTitle 方法有助于确定数据库中是否已经存在具有特定标题的图书。

5.2.3 BookServiceImpl.java

    @Overridepublic boolean deleteById(Long id) {if (id == null || id <= 0) {throw new IllegalArgumentException("无效的图书 ID");}int rowsAffected = bookMapper.deleteById(id);return rowsAffected > 0;}

代码点评: 

这段代码是一个方法实现,用于从数据库中删除一本图书。下面是对这段代码的解释:

  • @Override:这个注解表示这个方法是重写自父类或接口的方法。

  • public boolean deleteById(Long id):这是一个公共方法,它的返回类型是布尔值 boolean。它接受一个类型为 Long 的参数 id,表示要删除的图书的唯一标识符。

  • if (id == null || id <= 0):这是一个条件语句,用于检查传入的图书标识符是否为无效值。如果 idnull 或小于等于零,就会抛出一个 IllegalArgumentException 异常,提示图书 ID 无效。

  • int rowsAffected = bookMapper.deleteById(id);:这一行调用了名为 bookMapper 的对象的 deleteById 方法,传入要删除的图书的 ID。这个方法将在数据库中执行删除操作,并返回受影响的行数(即删除的行数)。

  • return rowsAffected > 0;:根据执行删除操作后受影响的行数,这个表达式返回一个布尔值。如果受影响的行数大于零,表示删除成功,返回 true,否则返回 false。这是为了指示删除操作是否成功完成。

这个方法的作用是接收一个图书的唯一标识符,然后调用底层的数据库访问方法,将对应的图书从数据库中删除。如果删除成功(受影响的行数大于零),方法返回 true,否则返回 false

5.2.4 BookMapper.java

int deleteById(@Param("id") Long id);

代码点评: 

这段代码是一个 MyBatis Mapper 接口中的方法定义,用于在数据库中根据图书的 ID 删除记录。下面是对这段代码的解释:

  • int deleteById(@Param("id") Long id);:这是一个接口方法定义,返回类型是整型 int。它使用了 MyBatis 的 @Param 注解,指定了参数名称和对应的映射关系。方法接受一个类型为 Long 的参数 id,表示要删除的图书的唯一标识符。

  • @Param("id"):这是 MyBatis 的注解,它用于为方法参数指定一个映射名称,这里是 "id"。在 SQL 语句中,可以通过 #{id} 来引用这个参数。

  • int:方法的返回类型是整型 int,表示删除操作受影响的行数。

这个方法的作用是为 MyBatis 映射文件提供一个接口,通过调用这个接口的方法,可以在数据库中执行根据图书 ID 进行删除操作。在映射文件中,可以通过使用 #{id} 来获取传递的图书 ID 参数,并执行相应的删除 SQL 操作。方法的返回值表示删除操作后受影响的行数,可以用于判断删除是否成功。

5.2.5 BookMapper.xml

    <!-- 判断是否存在指定 title 的记录 --><select id="existsByTitle" resultType="boolean" parameterType="java.lang.String">SELECT COUNT(*) > 0FROM bookWHERE title = #{title}</select><!-- 删除 --><delete id="deleteById" parameterType="long">DELETE FROM book WHERE id = #{id}</delete>

代码点评: 

这段代码是 MyBatis 映射文件中的两个 SQL 查询语句,分别用于判断指定标题的记录是否存在以及删除指定 ID 的记录。下面是对这两段代码的解释:

  1. 判断是否存在指定 title 的记录:

    • <select id="existsByTitle" resultType="boolean" parameterType="java.lang.String">:这是一个查询语句的定义,通过 id 属性指定查询的标识符,resultType 属性指定查询结果的返回类型,这里是 boolean
    • parameterType="java.lang.String":这个属性指定了输入参数的数据类型,这里是一个字符串类型的标题。
    • SELECT COUNT(*) > 0 FROM book WHERE title = #{title}:这是查询的 SQL 语句。它使用了 COUNT(*) 来计算满足条件的记录数是否大于 0,如果大于 0,则说明存在指定标题的记录。其中 #{title} 是一个占位符,表示参数传递的标题。
  2. 删除:

    • <delete id="deleteById" parameterType="long">:这是一个删除语句的定义,通过 id 属性指定删除的标识符,parameterType 属性指定了传递给 SQL 语句的参数类型,这里是 long 类型的 ID。
    • DELETE FROM book WHERE id = #{id}:这是删除的 SQL 语句。它会根据传递的 id 参数删除数据库中对应的记录。其中 #{id} 是一个占位符,表示参数传递的图书 ID。

这两个 SQL 查询语句分别用于在数据库中判断某个标题的记录是否存在(返回一个布尔值)以及根据图书 ID 删除记录。这些语句会与 MyBatis 中的方法关联,通过方法调用和参数传递来执行相应的数据库操作。

5.3 图书修改接口

5.3.1 controller.java

    @PostMapping("/update")public ResponseEntity<ResponseData> updateBook(@RequestBody Book book) {try {bookService.updateData(book);ResponseData response = new ResponseData("success", "图书已更新!");return ResponseEntity.ok(response);} catch (IllegalArgumentException e) {ResponseData response = new ResponseData("fail", e.getMessage());return ResponseEntity.badRequest().body(response);} catch (BookNotFoundException e) {ResponseData response = new ResponseData("fail", "需要修改的书籍id不存在!");return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);}}

代码点评: 

这段代码是一个 Spring Boot 控制器中的方法,用于处理图书更新的请求。下面是对这段代码的解释:

  1. @PostMapping("/update"):这是一个注解,表示这个方法会处理 POST 请求,并且路径是 "/update"。当客户端发起一个 POST 请求到 "/update" 路径时,该方法将被调用。

  2. public ResponseEntity<ResponseData> updateBook(@RequestBody Book book):这是方法的签名,它指定了方法的访问修饰符、返回值类型以及方法名。@RequestBody 注解表示从请求体中获取参数,这里是一个 Book 对象,即请求中传递的要更新的图书信息。方法返回一个 ResponseEntity<ResponseData>,表示响应实体,其中包含了响应的数据和状态信息。

  3. try:这是一个异常处理的起始块。在这里,我们尝试执行可能会抛出异常的代码块。

  4. bookService.updateData(book);:这是调用业务逻辑层的 updateData 方法,传递了要更新的图书信息。这个方法会处理图书的更新操作。

  5. ResponseData response = new ResponseData("success", "图书已更新!");:在更新操作成功后,创建一个名为 responseResponseData 对象,其中包含了成功状态和成功的消息。

  6. return ResponseEntity.ok(response);:将上面创建的 response 对象作为响应体,使用 ResponseEntity.ok() 方法构建一个成功响应实体,将它返回给客户端。

  7. catch (IllegalArgumentException e):这是一个异常捕获块,用于捕获可能抛出的 IllegalArgumentException 异常。

  8. ResponseData response = new ResponseData("fail", e.getMessage());:在出现参数错误的情况下,创建一个名为 responseResponseData 对象,其中包含了失败状态和异常消息。

  9. return ResponseEntity.badRequest().body(response);:将上面创建的 response 对象作为响应体,使用 ResponseEntity.badRequest() 方法构建一个错误的响应实体,将它返回给客户端,表示请求参数错误。

  10. catch (BookNotFoundException e):这是另一个异常捕获块,用于捕获可能抛出的 BookNotFoundException 异常。

  11. ResponseData response = new ResponseData("fail", "需要修改的书籍id不存在!");:在找不到需要修改的书籍 ID 的情况下,创建一个名为 responseResponseData 对象,其中包含了失败状态和消息。

  12. return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);:将上面创建的 response 对象作为响应体,使用 ResponseEntity.status(HttpStatus.NOT_FOUND) 方法构建一个带有 HTTP 状态码 404 的响应实体,将它返回给客户端,表示找不到要修改的书籍。

这段代码实现了在更新图书信息时的异常处理和响应生成,根据业务逻辑和参数情况,构建不同的响应实体并返回给客户端。

5.3.2 BookService.java

    void updateData(Book book);

代码点评: 

这段代码是一个服务类中的方法定义,用于更新图书信息。下面是对这段代码的解释:

  1. void:这是方法的返回类型,表示该方法不返回任何值。

  2. updateData:这是方法的名称,用于表示这个方法是用来执行更新操作的。

  3. (Book book):这是方法的参数列表,其中 Book 是一个数据类型,表示要传递的参数是一个名为 bookBook 对象。这个对象包含了需要更新的图书的信息。

所以,这段代码定义了一个名为 updateData 的方法,它接受一个 Book 对象作为参数,该方法的作用是将传递进来的 Book 对象中的图书信息用于更新操作。具体的更新操作逻辑将在方法的实现中进行处理。

5.3.3 BookServiceImpl.java

    @Overridepublic void updateData(Book book) {if (book.getId() == null || book.getTitle() == null || book.getAuthor() == null || book.getReadStatus() == null) {throw new IllegalArgumentException("id、title、author和read_status是必传参数!");}List<Book> bookList = bookMapper.selectAll();List<Integer> idList = bookList.stream().map(Book::getId).collect(Collectors.toList());if (!idList.contains(book.getId())) {throw new IllegalArgumentException("需要修改的书籍id不存在!");}String title = book.getTitle().trim();String author = book.getAuthor().trim();int readStatus = book.getReadStatus();if (title.isEmpty()) {throw new IllegalArgumentException("title不能为空!");}if (author.isEmpty()) {throw new IllegalArgumentException("作者不能为空!");}if (readStatus != 0 && readStatus != 1) {throw new IllegalArgumentException("阅读状态只能为0和1!");}bookMapper.updateData(book.getId().longValue(), title, author, readStatus);}

代码点评: 

这段代码是一个服务实现类中的方法,用于更新图书信息。下面是对这段代码的解释:

该方法首先检查传递进来的 Book 对象中是否有必要的字段信息(idtitleauthorreadStatus)。然后,它获取数据库中的已存在图书列表,检查要修改的图书的 id 是否存在于列表中。接着,它处理图书的标题、作者和阅读状态的信息,进行必要的检查。最后,它调用数据访问层的方法 updateData 进行实际的更新操作,将新的图书信息应用到数据库中。如果任何检查不通过,将抛出异常。

5.3.4 BookMapper.java

    void updateData(@Param("id") Long id, @Param("title") String title, @Param("author") String author, @Param("readStatus") Integer readStatus);

代码点评: 

这段代码是一个MyBatis Mapper接口中的方法定义,用于更新图书信息。这里使用了@Param注解来指定方法参数与SQL语句中的参数对应关系。下面是对这段代码的解释:

这个方法定义了四个参数,分别是idtitleauthorreadStatus。这些参数用于更新图书的相关信息。

  • @Param("id") Long id: 这个参数用于指定要更新的图书的唯一标识符(ID)。在SQL语句中,会使用#{id}来引用这个参数。

  • @Param("title") String title: 这个参数用于指定要更新的图书的标题。在SQL语句中,会使用#{title}来引用这个参数。

  • @Param("author") String author: 这个参数用于指定要更新的图书的作者。在SQL语句中,会使用#{author}来引用这个参数。

  • @Param("readStatus") Integer readStatus: 这个参数用于指定要更新的图书的阅读状态。在SQL语句中,会使用#{readStatus}来引用这个参数。

该方法在调用时会将这些参数传递给MyBatis框架,框架会根据参数的名称将它们映射到对应的SQL语句中。具体的SQL语句是在XML映射文件中定义的,它会使用这些参数来执行更新操作,将新的图书信息应用到数据库中

5.3.5 BookMapper.xml

    <!-- 修改 --><update id="updateData">UPDATE book SETtitle = #{title},author = #{author},read_status = #{readStatus}WHERE id = #{id}</update>

代码点评: 

这段XML代码是一个MyBatis的映射文件中的一个SQL语句定义,用于更新图书信息。下面是对这段代码的解释:

这个<update>元素定义了一个更新操作的SQL语句。它使用了MyBatis的占位符语法(#{})来引用参数,具体含义如下:

  • UPDATE book SET: 这是SQL语句的更新部分,表示要对book表进行更新操作。

  • title = #{title},: 这里使用了占位符语法#{title},表示要将title参数的值赋给数据库中的title列。

  • author = #{author},: 类似地,将author参数的值赋给数据库中的author列。

  • read_status = #{readStatus}: 同样,将readStatus参数的值赋给数据库中的read_status列。

  • WHERE id = #{id}: 这个部分指定了更新的条件,只会对id等于#{id}的记录进行更新。

在使用这个SQL语句时,MyBatis会将实际的参数值替换到占位符#{}中,然后执行这个更新操作。这样就能根据传递的参数更新图书的标题、作者和阅读状态

5.4 图书查询接口

5.4.1 controller.java

    @PostMapping("/query")public ResponseData<List<Book>> queryBookByTitle(@RequestBody Map<String, Object> requestParams) {Map<String, Object> data = (Map<String, Object>) requestParams.get("data");String title = (String) data.get("title");int pagesize = data.get("pagesize") != null && !data.get("pagesize").toString().isEmpty() ? Integer.parseInt(data.get("pagesize").toString()) : 3;int page = data.get("page") != null && !data.get("page").toString().isEmpty() ? Integer.parseInt(data.get("page").toString()) : 1;List<Book> result = bookService.queryBookByTitle(title, pagesize, page);if (result.isEmpty()) {return new ResponseData<>("需要查询的图书不存在!", "fail", result);}return new ResponseData<>("请求成功", "success", result);}

代码点评: 

这段代码是一个控制器(Controller)中的一个方法,用于处理通过标题查询图书的请求。以下是代码的解释:

  1. @PostMapping("/query"): 这是一个用于处理 HTTP POST 请求的注解,指定了请求的路径为 "/query"。

  2. public ResponseData<List<Book>> queryBookByTitle(@RequestBody Map<String, Object> requestParams): 这是一个方法定义,它接受一个 Map 对象作为请求体,并返回一个 ResponseData 类型的对象,表示响应数据。

  3. Map<String, Object> data = (Map<String, Object>) requestParams.get("data");: 从传入的请求体中获取名为 "data" 的字段,它应该是一个包含请求参数的 Map 对象。

  4. String title = (String) data.get("title");: 从获取到的数据中获取 "title" 字段,这是用于查询的图书标题。

  5. int pagesize = ...: 获取 "pagesize" 和 "page" 字段,这些字段指定了分页的大小和页码,默认为 3 和 1。

  6. List<Book> result = bookService.queryBookByTitle(title, pagesize, page);: 使用获取到的标题、分页大小和页码调用业务逻辑层的方法来查询图书数据。

  7. if (result.isEmpty()) { ... }: 检查查询结果是否为空。如果为空,创建一个新的 ResponseData 对象,其中包含 "需要查询的图书不存在!" 的消息和 "fail" 的状态。

  8. return new ResponseData<>("请求成功", "success", result);: 如果查询结果不为空,创建一个新的 ResponseData 对象,其中包含 "请求成功" 的消息和 "success" 的状态,同时将查询结果作为数据。

综合起来,这个方法通过标题查询图书,如果查询结果为空,则返回一个包含错误消息和 "fail" 状态的响应对象;如果查询结果不为空,则返回一个包含成功消息和 "success" 状态以及查询结果数据的响应对象

5.4.2 BookService.java

    List<Book> queryBookByTitle(String title, Integer pagesize, Integer page);

代码点评: 

这段代码是服务层(Service Layer)接口中的一个方法声明,用于根据书名、每页条数和页码来查询图书信息。下面是对这段代码的解释:

这个方法声明了一个名为 queryBookByTitle 的方法,它接受三个参数:

  • title:表示要查询的书名。这是一个字符串类型的参数,用于指定要查询的书名关键字。

  • pagesize:表示每页显示的条数。这是一个整数类型的参数,用于指定每页要显示的图书条目数。

  • page:表示要查询的页码。这是一个整数类型的参数,用于指定要查询的页码编号。

方法声明的返回类型是一个 List<Book>,这表示该方法将会返回一个由 Book 对象组成的列表,这些 Book 对象是根据查询条件获取到的图书信息。

这个方法的作用是在服务层中根据给定的书名、每页条数和页码来执行图书查询操作,然后返回查询结果。通常,该方法会调用底层的数据库查询方法,获取满足条件的图书数据,并将结果转化为 Book 对象列表返回给调用方

5.4.3 BookServiceImpl.java

    @Overridepublic List<Book> queryBookByTitle(String title, Integer pagesize, Integer page) {int offset = pagesize * (page - 1);RowBounds rowBounds = new RowBounds(offset, pagesize);return bookMapper.queryBookByTitle(title, rowBounds);}

代码点评: 

这段代码是服务层(Service Layer)中的方法实现,用于根据书名、每页条数和页码来查询图书信息。下面是对这段代码的解释:

这个方法实现了先前声明的 queryBookByTitle 方法。它接受三个参数:

  • title:要查询的书名关键字。
  • pagesize:每页显示的图书条目数。
  • page:要查询的页码。

在方法中,首先计算出数据的偏移量(offset),这个偏移量是通过页码和每页条数计算得到的,用于确定从数据库中哪条记录开始获取数据。

然后,创建了一个 RowBounds 对象,这个对象用于在数据库查询中指定偏移量和限制的条目数,实现分页查询。

最后,调用了 bookMapper.queryBookByTitle 方法,将书名关键字和分页信息传递给数据库查询方法。查询结果会根据传入的分页信息获取相应的图书数据,并将查询结果以 List<Book> 的形式返回给调用方。

总之,这个方法的作用是在服务层中使用 MyBatis 的分页功能,根据给定的书名关键字、每页条数和页码,执行图书查询操作,并返回查询结果

5.4.4 BookMapper.java

    List<Book> queryBookByTitle(@Param("title") String title, RowBounds rowBounds);List<Book> selectAll();

代码点评: 

这两个方法是 MyBatis 的 Mapper 接口中定义的查询方法,用于在数据库中进行数据查询操作。下面分别解释这两个方法:

  1. List<Book> queryBookByTitle(@Param("title") String title, RowBounds rowBounds);

    这个方法用于根据书名关键字和分页信息来查询图书列表。下面是对方法的各部分进行解释:

    • @Param("title") String title:这个注解是用于为方法参数指定名称的 MyBatis 注解。在查询语句中,可以通过该名称引用这个参数。在这个方法中,它用于指定查询图书时要匹配的书名关键字。
    • RowBounds rowBounds:这是 MyBatis 提供的一个分页信息对象,它包含偏移量和限制条数。通过传递这个对象,可以在查询中实现分页功能。
  2. List<Book> selectAll();

    这个方法用于查询所有图书的信息,返回一个包含所有图书对象的列表。它没有参数,因为它是一个简单的查询操作,不需要额外的查询条件。

总之,这两个方法分别用于根据书名关键字和分页信息查询图书列表,以及查询所有图书的信息。它们都是在 MyBatis 的 Mapper 接口中定义的,实际的查询操作由 MyBatis 框架负责执行

5.4.5 BookMapper.xml

<!-- 查询 --><select id="queryBookByTitle" resultType="com.example.book.domain.Book">SELECT id,title,author,read_status readStatus FROM book<where><if test="title != null and title != ''">AND title LIKE CONCAT('%', #{title}, '%')</if></where></select><!-- 查询所有图书 --><select id="selectAll" resultType="com.example.book.domain.Book">SELECT id, title, author, read_status as readStatusFROM book

代码点评: 

这两段代码是 MyBatis 的映射文件中的查询语句,用于从数据库中获取图书信息。以下是对每个查询语句的解释:

         1、查询图书根据书名关键字:

  • <select> 标签:定义一个查询语句。
  • id="queryBookByTitle":为查询语句指定一个唯一的标识符。
  • resultType="com.example.book.domain.Book":指定查询结果的类型,即图书对象的类型。
  • SELECT id, title, author, read_status readStatus FROM book:实际的 SQL 查询语句,从数据库中选择图书的 id、title、author 和 read_status 字段。
  • <where> 标签:这个标签用于生成 SQL 的 WHERE 子句,根据条件动态生成。
  • <if test="title != null and title != ''">:条件判断语句,判断是否传入了 title 参数并且不为空。
  • AND title LIKE CONCAT('%', #{title}, '%'):如果满足条件,会在 SQL 查询语句中添加类似 AND title LIKE '%关键字%' 的条件,进行模糊匹配书名关键字。                                   2、查询所有图书 
  • <select> 标签:同样定义一个查询语句。
    • id="selectAll":为查询语句指定唯一标识符。
    • resultType="com.example.book.domain.Book":指定查询结果的类型,即图书对象的类型。
    • SELECT id, title, author, read_status as readStatus FROM book:从数据库中选择图书的 id、title、author 和 read_status 字段。
  • 这两个查询语句定义了如何从数据库中获取图书信息,通过 MyBatis 框架进行执行。第一个查询语句允许根据书名关键字进行模糊查询,第二个查询语句获取所有图书的信息

六、增删改查接口测试

CRUD,增删改查接口已经开发完毕,下面我们开始测试。注意,这里只列了部分场景。入参也做了校验,这里就不列截图了

6.1 图书增加测试

6.1.1  添加图书测试

请求示例:

{"title":"《Spring高级编程》","author":"李专家","read_status":1
}

响应示例: 

{"message": "图书添加成功!","status": "success"
}

完整截图:

数据库验证:

6.1.2  添加重复图书测试

再次点击,报错:书名重复

6.1.3  图书添加接口测试结论

 新增接口测试通过

6.2 图书删除测试

6.2.1 通过id删除图书测试

准备删除id为 67的书籍

请求示例:

{"data": {"id": "67"},"extra": {}
}

响应示例: 

{"message": "图书被删除!","status": "success"
}

完整截图:

数据库验证:

 6.2.2 通过id删除不存在图书测试

再次点击,报错:图书不存在

 6.2.3 图书删除接口测试结论 

 删除接口测试通过

6.3 图书修改测试

6.3.1 通过修改图书标题测试

修改id 为68的书籍 

请求示例:

{"id": 68,"title": "《我是测试开发》","author": "李同学","read_status": "0"
}

响应示例: 

data是拓展字段,这里返回null没影响。前端页面暂时不用

{"message": "success","status": "图书已更新!","data": null
}

完整截图:

数据库验证:

6.3.2 通过修改图书作者测试

再次修改作业 天下霸唱,点击修改返回成功

 查看数据库,修改成功

6.3.3 图书修改接口测试结论

修改接口测试通过

6.4 图书查询测试

6.4.1 不传书名,页数大小10,第1页测试

请求示例:

{"data": {"title": "","pagesize": "10","page": "1"},"extra": {}
}

响应示例:

{"message": "请求成功","status": "success","data": [{"id": 68,"title": "《我是测试》","author": "李同学","read_status": 0},{"id": 69,"title": "《三国演义20》","author": "罗贯中","read_status": 1},{"id": 70,"title": "《默》","author": "李晓明","read_status": 0},{"id": 71,"title": "《三国演义8》","author": "罗贯中8","read_status": 1},{"id": 72,"title": "《Java高级编程》","author": "李专家","read_status": 1},{"id": 73,"title": "《Spring高级编程》","author": "李专家","read_status": 1}]
}

完整截图:

数据库验证:

6.4.2 关键字过滤查询测试

查询过滤:

{"data": {"title": "编程","pagesize": "10","page": "1"},"extra": {}
}

结果:

{"message": "请求成功","status": "success","data": [{"id": 72,"title": "《Java高级编程》","author": "李专家","read_status": 1},{"id": 73,"title": "《Spring高级编程》","author": "李专家","read_status": 1}]
}

 查询成功

6.4.3 所有值不传分页默认传3和1测试

分页查询:默认传3和1,title不传查所有

{"data": {"title": "","pagesize": "","page": ""},"extra": {}
}

响应:

{"message": "请求成功","status": "success","data": [{"id": 68,"title": "《我是测试》","author": "李同学","read_status": 0},{"id": 69,"title": "《三国演义20》","author": "罗贯中","read_status": 1},{"id": 70,"title": "《默》","author": "李晓明","read_status": 0}]
}

 对照数据库

 6.4.4 图书不存在时查询测试

查询不存在的图书时:

 请求:

{"data": {"title": "粑粑","pagesize": "","page": ""},"extra": {}
}

响应:

{"message": "需要查询的图书不存在!","status": "fail","data": []
}

 截图:

 数据库里没有这个标题相关的书籍:

标题不存在图书测试通过 

 6.4.5 图书查询接口测试结论

查询接口测试通过

七、总结

本次包括接口开发、测试、博文撰写断断续续写了几天,总体上比之前flask实现的要好一些。目前这种CRUD基本上可以在项目上使用了。优化点是可以加上更多的校验和更多的封装

八、期望

作为一名测试专家。Java是必须掌握的语言,而掌握java必须得有几个框架的项目实战,这样才能做好测试。拥有和开发同频对话能力,增加测试自信心,当然也是能力的体现。后续会根据项目分享更多的白盒测试、单元测试文章,共同进步,不说了,老婆叫我包饺子了。。。

九、源码

本来要放在git上的,这一次先全部贴出来。在公司电脑操作涉及安全,我还是先保住工作哈哈哈

9.1 DataSourceConfig.java

//这个文件是禁用了Spring Boot默认的数据源自动配置 ,
// 导致报错"Consider defining a bean of type 'javax.sql.DataSource' in your configuration"
//加了这个文件也能处理这个报错让程序启动
//package com.example.book.config;//import org.springframework.boot.jdbc.DataSourceBuilder;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import javax.sql.DataSource;
//
//@Configuration
//public class DataSourceConfig {
//    @Bean
//    public DataSource dataSource() {
//        return DataSourceBuilder.create()
//                .url("jdbc:mysql://XX.XX.XXX.24:13300/z_liqiju_test?tinyInt1isBit=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true\n")
//                .username("XXX")
//                .password("XXX")
//                .driverClassName("com.mysql.cj.jdbc.Driver")
//                .build();
//    }
//}

9.2 MyBatisConfig.java

//package com.example.book.config;
//这个文件是解决报错Consider defining a bean of type 'org.apache.ibatis.session.SqlSessionFactory' in your configuration.
//这个config包的2个文件是我在启动类那里加了1行@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
//禁用了Spring Boot默认的数据源自动配置 ,导致报错"Consider defining a bean of type 'javax.sql.DataSource' in your configuration"。坑死个人
//import org.apache.ibatis.session.SqlSessionFactory;
//import org.mybatis.spring.SqlSessionFactoryBean;
//import org.mybatis.spring.annotation.MapperScan;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
//import org.springframework.core.io.support.ResourcePatternResolver;
//import javax.sql.DataSource;
//
//@Configuration
//@MapperScan("com.example.book.mapper") // Mapper接口所在的包路径
//public class MyBatisConfig {
//    private final DataSource dataSource;
//
//    public MyBatisConfig(DataSource dataSource) {
//        this.dataSource = dataSource;
//    }
//
//    @Bean
//    public SqlSessionFactory sqlSessionFactory() throws Exception {
//        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//        factoryBean.setDataSource(dataSource);
//
//        // 指定MyBatis的Mapper XML文件所在的路径
//        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
//
//        return factoryBean.getObject();
//    }
//}

9.3 controller.java

package com.example.book.controller;import com.example.book.domain.Book;
import com.example.book.domain.ResponseData;
import com.example.book.service.BookService;
import com.example.book.service.impl.BookNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/api/book")
public class BookController {private static final Logger logger = LoggerFactory.getLogger(BookController.class);private final BookService bookService;@Autowiredpublic BookController(BookService bookService){this.bookService = bookService;}@PostMapping("/add")public ResponseEntity<Map<String, String>> addBook(@RequestBody Book book) {try {//System.out.println("前端传值ReadStatus:"+book.getReadStatus());bookService.insertData(book);Map<String, String> response = new HashMap<>();response.put("message", "图书添加成功!");response.put("status", "success");return ResponseEntity.ok(response);} catch (IllegalArgumentException e) {Map<String, String> response = new HashMap<>();response.put("message", e.getMessage());response.put("status", "fail");return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);}}@PostMapping("/delete")public ResponseEntity<Map<String, String>> deleteById(@RequestBody Map<String, Object> requestParams) {Map<String, Object> data = (Map<String, Object>) requestParams.get("data");Long id = Long.valueOf(data.get("id").toString());// 构建返回结果boolean isDeleted = bookService.deleteById(id);if (isDeleted) {Map<String, String> successResponse = new HashMap<>();successResponse.put("message", "图书被删除!");successResponse.put("status", "success");return ResponseEntity.ok(successResponse);} else {Map<String, String> failResponse = new HashMap<>();failResponse.put("message", "需要删除的图书不存在!");failResponse.put("status", "fail");return ResponseEntity.status(HttpStatus.NOT_FOUND).body(failResponse);}}//    @PostMapping("/update")
//    public void updateBook(@PathVariable Long id, @RequestBody Book book) {
//        bookService.updateData(id, book.getTitle(), book.getAuthor(), book.getReadStatus());
//    }@PostMapping("/update")public ResponseEntity<ResponseData> updateBook(@RequestBody Book book) {try {bookService.updateData(book);ResponseData response = new ResponseData("success", "图书已更新!");return ResponseEntity.ok(response);} catch (IllegalArgumentException e) {ResponseData response = new ResponseData("fail", e.getMessage());return ResponseEntity.badRequest().body(response);} catch (BookNotFoundException e) {ResponseData response = new ResponseData("fail", "需要修改的书籍id不存在!");return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);}}@PostMapping("/query")public ResponseData<List<Book>> queryBookByTitle(@RequestBody Map<String, Object> requestParams) {Map<String, Object> data = (Map<String, Object>) requestParams.get("data");String title = (String) data.get("title");int pagesize = data.get("pagesize") != null && !data.get("pagesize").toString().isEmpty() ? Integer.parseInt(data.get("pagesize").toString()) : 3;int page = data.get("page") != null && !data.get("page").toString().isEmpty() ? Integer.parseInt(data.get("page").toString()) : 1;List<Book> result = bookService.queryBookByTitle(title, pagesize, page);if (result.isEmpty()) {return new ResponseData<>("需要查询的图书不存在!", "fail", result);}return new ResponseData<>("请求成功", "success", result);}
}

9.4 BaseInput.java

//package com.example.book.domain;
//
//import io.swagger.annotations.ApiModelProperty;
//import lombok.Data;
//
//import java.util.Map;
//封装请求
//@Data
//public class BaseInput<T> {
//
//    @ApiModelProperty(value = "实际请求参数数据")
//    private T data;
//
//    @ApiModelProperty(value = "扩展数据,字典类型key-value结构")
//    private Map<String,Object> extra;
//}

9.5 Book.java

package com.example.book.domain;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;//@ApiModelProperty干嘛的?
//@ApiModelProperty 是 Swagger 注解之一,用于在 API 文档中描述接口的各个字段(属性)的用途、数据类型、示例值等信息,以便生成更详细的 API 文档。
//Swagger 是一个用于生成和展示 API 文档的工具,可以帮助开发人员更好地了解和使用 API。
//具体来说,@ApiModelProperty 注解可以用于类的属性上,用于描述 API 接口的请求或响应对象的字段。这些描述将会被 Swagger 生成的 API 文档所使用。主要用途包括:
//描述字段用途: 通过 @ApiModelProperty 注解,你可以为每个字段添加描述,解释该字段的用途、作用等,使文档更加易读和易理解。
//指定数据类型: 你可以通过 dataType 参数来指定字段的数据类型,例如字符串、整数、浮点数等。
//提供示例值: 使用 example 参数可以为字段提供示例值,帮助使用者更好地理解字段的期望值。
//控制是否必填: 通过 required 参数,你可以指定字段是否为必填项。
//其他属性: 还可以设置一些其他属性,如是否允许空值、是否隐藏该字段等。
@TableName(value ="books")
@Data
public class Book implements Serializable {/*** 自动递增id,唯一键*/@TableId(type = IdType.AUTO)private Integer id;@ApiModelProperty(value = "书名")private String title;@ApiModelProperty(value = "作者")private String author;@ApiModelProperty(value = "阅读状态")@JsonProperty("read_status") // 指定 JSON 字段名,使得前端传参和read_status对应readStatusprivate Integer readStatus;@TableField(exist = false)private static final long serialVersionUID = 1L;//构造函数public Book(Integer id, String title, String author, Integer readStatus) {this.id = id;this.title = title;this.author = author;this.readStatus = readStatus;}}

9.6 ResponseData.java

package com.example.book.domain;import lombok.Data;//ResponseData是一个用于封装响应数据的泛型类。
//它用于统一返回给前端的响应格式,包含三个属性:message、status和data。
//属性说明:
//message:表示响应的消息或描述信息,通常用于描述请求的处理结果或返回的状态信息。
//status:表示响应的状态,通常用于标识请求处理的状态,例如"success"表示成功,"error"表示失败等。
//data:表示响应的数据,它是一个泛型参数,可以用于存储任意类型的数据,比如查询结果、实体对象等。
//例如,在查询数据库时,可以将查询结果存储在data中返回给前端;在处理表单提交时,可以将表单提交的数据存储在data中返回。
//构造函数:
//ResponseData(String message, String status, T data):该构造函数用于创建ResponseData对象,
//并初始化message、status和data属性的值。
//@Data是一个Lombok注解,它可以自动为类生成一些常用的方法,如toString()、equals()、hashCode()、getter和setter方法。使用@Data注解可以简化Java类的编写,减少样板代码。
//具体来说,@Data注解为类的所有非静态字段生成以下方法:
//toString(): 生成一个默认的toString()方法,用于将对象转换为字符串表示。该方法会按照字段的名称和值生成字符串。
//equals(): 生成一个默认的equals()方法,用于比较对象是否相等。该方法会比较对象的所有字段是否相等。
//hashCode(): 生成一个默认的hashCode()方法,用于计算对象的哈希码。该方法基于对象的所有字段计算哈希码。
//getter和setter: 为所有非静态字段生成getter和setter方法,用于获取和设置字段的值。
//使用@Data注解的类通常称为"数据类",它主要用于封装数据,而不包含业务逻辑。在编写POJO(Plain Old Java Object)类时,使用@Data注解可以简化代码,提高代码的可读性和可维护性。
@Data
public class ResponseData<T> {private String message;private String status;private T data;public ResponseData(String message, String status, T data) {this.message = message;this.status = status;this.data = data;}public ResponseData(String message, String status) {this.message = message;this.status = status;}
}

9.7 BookService.java

package com.example.book.service;import com.example.book.domain.Book;import java.util.List;/****/
public interface BookService {void insertData(Book book);boolean deleteById(Long id);
;void updateData(Book book);List<Book> queryBookByTitle(String title, Integer pagesize, Integer page);}

9.8 BookServiceImpl.java

package com.example.book.service.impl;import com.example.book.domain.Book;
import com.example.book.mapper.BookMapper;
import com.example.book.service.BookService;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;//创建了BooksServiceImpl类,并将其标记为@Service,以便Spring能够识别并注册它作为bean。
//同时,也要确保在BooksServiceImpl的构造函数中使用了@Autowired来注入BooksMapper。
//在MyBatis中,分页应该使用RowBounds或者使用PageHelper来实现
@Service
public class BookServiceImpl implements BookService  {private final BookMapper bookMapper;@Autowiredpublic BookServiceImpl(BookMapper booksMapper) {this.bookMapper = booksMapper;}@Overridepublic void insertData(Book book) {if (book.getTitle() == null || book.getAuthor() == null || book.getReadStatus() == null) {throw new IllegalArgumentException("title、author和read_status是必传参数!");}// Check for duplicate titleString title = book.getTitle().trim();if (bookMapper.existsByTitle(title)) {throw new IllegalArgumentException("书名(title)重复!");}bookMapper.insertData(book);
}@Overridepublic boolean deleteById(Long id) {if (id == null || id <= 0) {throw new IllegalArgumentException("无效的图书 ID");}int rowsAffected = bookMapper.deleteById(id);return rowsAffected > 0;}//    @Override
//    public void updateData(Long id, String title, String author, Integer readStatus) {
//        bookMapper.updateData(id, title, author, readStatus);
//    }@Overridepublic void updateData(Book book) {if (book.getId() == null || book.getTitle() == null || book.getAuthor() == null || book.getReadStatus() == null) {throw new IllegalArgumentException("id、title、author和read_status是必传参数!");}List<Book> bookList = bookMapper.selectAll();List<Integer> idList = bookList.stream().map(Book::getId).collect(Collectors.toList());if (!idList.contains(book.getId())) {throw new IllegalArgumentException("需要修改的书籍id不存在!");}String title = book.getTitle().trim();String author = book.getAuthor().trim();int readStatus = book.getReadStatus();if (title.isEmpty()) {throw new IllegalArgumentException("title不能为空!");}if (author.isEmpty()) {throw new IllegalArgumentException("作者不能为空!");}if (readStatus != 0 && readStatus != 1) {throw new IllegalArgumentException("阅读状态只能为0和1!");}bookMapper.updateData(book.getId().longValue(), title, author, readStatus);}@Overridepublic List<Book> queryBookByTitle(String title, Integer pagesize, Integer page) {int offset = pagesize * (page - 1);RowBounds rowBounds = new RowBounds(offset, pagesize);return bookMapper.queryBookByTitle(title, rowBounds);}
}

9.9 BookNotFoundException.java

package com.example.book.service.impl;public class BookNotFoundException extends RuntimeException {public BookNotFoundException(String message) {super(message);}
}

9.10 BookMapper.java

package com.example.book.mapper;import com.example.book.domain.Book;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.RowBounds;import java.util.List;/*** @Entity com.example.book.domain.Books*/
@Mapper
public interface BookMapper extends BaseMapper<Book> {void insertData(Book book);boolean existsByTitle(@Param("title") String title);int deleteById(@Param("id") Long id);void updateData(@Param("id") Long id, @Param("title") String title, @Param("author") String author, @Param("readStatus") Integer readStatus);List<Book> queryBookByTitle(@Param("title") String title, RowBounds rowBounds);List<Book> selectAll();
}

9.11 BookApplication.java

package com.example.book;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.example.book.mapper")
public class BookApplication {public static void main(String[] args) {SpringApplication.run(BookApplication.class, args);}}

9.12 BookMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.book.mapper.BookMapper"><resultMap id="BaseResultMap" type="com.example.book.domain.Book"><id property="id" column="id" jdbcType="INTEGER"/><result property="title" column="title" jdbcType="VARCHAR"/><result property="author" column="author" jdbcType="VARCHAR"/><result property="readStatus" column="read_status" jdbcType="INTEGER"/></resultMap><sql id="Base_Column_List">id,title,author,read_status</sql><!-- 增加 --><insert id="insertData">INSERT INTO book (title, author, read_status)VALUES (#{title}, #{author}, #{readStatus})</insert><!-- 判断是否存在指定 title 的记录 --><select id="existsByTitle" resultType="boolean" parameterType="java.lang.String">SELECT COUNT(*) > 0FROM bookWHERE title = #{title}</select><!-- 删除 --><delete id="deleteById" parameterType="long">DELETE FROM book WHERE id = #{id}</delete><!-- 修改 --><update id="updateData">UPDATE book SETtitle = #{title},author = #{author},read_status = #{readStatus}WHERE id = #{id}</update><!-- 查询 --><select id="queryBookByTitle" resultType="com.example.book.domain.Book">SELECT id,title,author,read_status readStatus FROM book<where><if test="title != null and title != ''">AND title LIKE CONCAT('%', #{title}, '%')</if></where></select><!-- 查询所有图书 --><select id="selectAll" resultType="com.example.book.domain.Book">SELECT id, title, author, read_status as readStatusFROM books
</select></mapper>

9.13 application.properties

注意我加了个1在后缀,2个文件选择1个生效即可

spring.datasource.url = jdbc:mysql://XX.XX.XXX.24:13300/z_liqiju_test?tinyInt1isBit=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.username = XXX
spring.datasource.password = XXX
mybatis.mapper-locations = classpath*:mapper/*.xml
server.port = 5002
logging.level.root=info
#logging.level.root=DEBUG
#logging.level.org.mybatis=DEBUG

9.14 application.yml

原来是的项目是5001 端口,这一次改为5002端口启动防止端口冲突

spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://XX.XX.XXX.24:13300/z_liqiju_test?tinyInt1isBit=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=trueusername: XXXpassword: XXXmybatis:mapper-locations: classpath*:mapper/*.xmlserver:port: 5002logging:level:root: DEBUG
#        org:
#          mybatis: INFO

9.15 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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.4</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>book</artifactId><version>0.0.1-SNAPSHOT</version><name>book</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!-- spring-boot-starter-jdbc 驱动 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- spring-boot-starter-web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-annotation</artifactId><version>3.3.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-core</artifactId><version>3.3.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-extension</artifactId><version>3.3.1</version></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>1.6.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.2.5.RELEASE</version><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version><configuration><skipTests>true</skipTests></configuration></plugin></plugins></build></project>

2023年8月12日 -深圳笔

有疑问直接评论区见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/63780.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis学习笔记(八)

文章目录 redis的配置redis的核心配置选项Redis的使用 redis的配置 cat /etc/redis/redis.confredis 安装成功以后,window下的配置文件保存在软件 安装目录下,如果是mac或者linux,则默认安装/etc/redis/redis.conf redis的核心配置选项 绑定ip&#xff1a;访问白名单&#x…

AP51656 电流采样降压恒流驱动IC RGB PWM深度调光 LED电源驱动

产品描述 AP51656是一款连续电感电流导通模式的降压恒流源&#xff0c;用于驱动一颗或多颗串联LED 输入电压范围从 5 V 到 60V&#xff0c;输出电流 可达 1.5A 。根据不同的输入电压和 外部器件&#xff0c; 可以驱动高达数十瓦的 LED。 内置功率开关&#xff0c;采用电流采样…

【Spring Boot】夺名连环问(持续更新ing)

Spring的了解与特性 简单介绍&#xff1a;快速开发Spring项目的脚手架。简化Spring应用的初始搭建以及开发过程。 特性 提供了很多内置的Starter结合自动配置&#xff0c;对主流框架的无配置集成、开箱即用。即不需要自己去引入很多依赖。 并且管理了常用的第三方依赖的版本&…

冒泡排序(超详细!)(C语言)

大家好&#xff01;欢迎来到Mr.kanglong的CSDN博文&#xff0c;今天来讨论一下冒泡排序&#xff0c;在百度百科中&#xff0c;关于为什么叫冒泡排序是这样解释的&#xff1a;因为越小的元素会经由交换慢慢“浮”到数列的顶端&#xff08;升序或降序排列&#xff09;&#xff0c…

软件测试工程师面试如何描述自动化测试是怎么实现的?

软件测试工程师面试的时候&#xff0c;但凡简历中有透露一点点自己会自动化测试的技能点的描述&#xff0c;都会被面试官问&#xff0c;那你结合你的测试项目说说自动化测试是怎么实现的&#xff1f;一到这里&#xff0c;很多网友&#xff0c;包括我的学生&#xff0c;也都一脸…

python小白之matplotlib使用实战项目:随机漫步

文章目录 随机漫步1.1 创建RandomWalk类1.2 选择方向1.3 绘制随机漫步图1.4 模拟多次随机漫步1.5 设置随机漫步图样式1.5.1 给点着色1.5.2 重新绘制起点和终点1.5.3 隐藏坐标轴1.5.4 增加点数1.5.5 调整图片尺寸以适应屏幕 附录&#xff08;项目代码&#xff09;random_walk.py…

【云原生|Docker系列第3篇】Docker镜像的入门实践

欢迎来到Docker入门系列的第三篇博客&#xff01;在前两篇博客中&#xff0c;我们已经了解了什么是Docker以及如何安装和配置它。本篇博客将重点介绍Docker镜像的概念&#xff0c;以及它们之间的关系。我们还将学习如何拉取、创建、管理和分享Docker镜像&#xff0c;这是使用Do…

Leetcode-每日一题【剑指 Offer 28. 对称的二叉树】

题目 请实现一个函数&#xff0c;用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样&#xff0c;那么它是对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称…

[保研/考研机试] 猫狗收容所 C++实现

题目描述&#xff1a; 输入&#xff1a; 第一个是n&#xff0c;它代表操作序列的次数。接下来是n行&#xff0c;每行有两个值m和t&#xff0c;分别代表题目中操作的两个元素。 输出&#xff1a; 按顺序输出收养动物的序列&#xff0c;编号之间以空格间隔。 源代码&#xff…

【总结】Javaweb和Java项目的比较

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理Javaweb中的关键点和需要注意的地方&#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下&#x1f…

【iOS】autoreleasepool

来说一下最近在了解的autoreleasepool吧&#xff0c;我们可能平时书写过许多脑残代码&#xff0c;其有很多的缺陷但是我们可能当时学的比较浅就也不太了解&#xff0c;就像下面这样的&#xff1a; for (int i 0; i < 1000000; i) {NSNumber *num [NSNumber numberWithInt…

C语言 ——指针数组与数组指针

目录 一、二维数组 二、指针数组 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;书写方式 &#xff08;3&#xff09;指针数组模拟二维数组 三、数组指针 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;使用数组指针打印一维数组 &#xff08;3&a…