MyBatis(二)
文章目录
- 一.MyBatis的模式开发
- 1.1 定义数据表和实体类
- 1.2 配置数据源和MyBatis
- 1.3 编写Mapper接口和增加xxxMapper.xml
- 1.4 测试我们功能的是否实现.
- 二. Mybatis的增删查改操作
- 2.1 单表查询
- 2.2 多表查询
- 三.动态SQL的实现
- 3.1 什么是动态SQL
- 3.2 动态SQL的使用
- if标签的使用
- trim标签的使用
- where标签的使用
- set标签的使用
- foreach标签的使用
- 3.3 综合练习
一.MyBatis的模式开发
我们在开始MyBatis模式开发之前,我们首先来了解一下mybaits在整个框架的定位.看下图可知
MyBatis 也是一个ORM框架,ORM(Object Relational Mapping) ,即对象关系映射。在面向对象编程语言中,将关系型数据库中的数据与对象建立起映射关系,进而自动的完成数据与对象的互相转换:
1.将输入数据(即传入对象)+SQL映射成原生SQL
2.将结果集映射为返回对象,即输出对象
也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间
的转换,接下来我们来看 MyBatis 的使⽤吧.
使用过程中,还需要按照后端工程师的思路,来实现查询所有用户的功能.
1.1 定义数据表和实体类
创建数据表
CREATE TABLE `userinfo` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(100) NOT NULL,`password` varchar(32) NOT NULL,`photo` varchar(500) DEFAULT '',`createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`state` int(11) DEFAULT '1',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4
创建实体类
@Data
public class UserEntity {private Integer id;private String username;private String pwd;private String photo;private LocalDateTime createTime;private LocalDateTime updateTime;
}
1.2 配置数据源和MyBatis
在application.properties中配置以下内容
# mysql的配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
driver.class.driver-class-name=com.mysql.jdbc.Driver#配置 MyBatis 中的 XML 路径
# 1.保存路径 2.xml格式
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
1.3 编写Mapper接口和增加xxxMapper.xml
我们现来实现查询所有用户的功能.
定义接口UserMapper接口:
@Mapper注解代表一个Mapper
@Mapper
public interface UserMapper {public List<UserEntity> getAll();}
定义UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">\
<select id="getAll" resultType="com.example.demo.entity.UserEntity">select*from userinfo</select>
</mapper>
针对以上标签进行说明
标签:需要指定namespace 属性,表示命名空间,值为 mapper接口的全限定名,包括全包名.类名。
查询标签:是用来执行数据库的查询操作的:
id:是和Interface (接口)中定义的方法名称一样的,表示对接口的具体实现方法。
resultType:是返回的数据类型,也就是开头我们定义的实体类。
1.4 测试我们功能的是否实现.
我们会引入一个单元测试的概念,我们每实现一个功能没必要,都要去启动项目,然后去验证,这时候我们就可以启动单元测试.
在springboot中启动单元测试也是比较简单的事情,具体的步骤如下:
-
在要测试的类上石健Generate…
-
在测试方法中加上@SpringBootTest注解
直接执行此方法,得出查询结果.
二. Mybatis的增删查改操作
我们在知道了基本的Mybatis的流程,我们来进行基本的增删改查操作
2.1 单表查询
UserMapper接口文件
//根据id查询对象UserEntity getUserById(@Param("id") Integer id);//根据名称查询用户对象UserEntity getUseByUserName(@Param("username")String username);// 登录方法UserEntity login(UserEntity user);// 修改密码int updatePassword(@Param("id") Integer id,@Param("password") String password,@Param("newpassword") String newPassword);//删除操作int delById(@Param("id") Integer id);//增加用户int addUser(UserEntity user);//得到用户idint addUserGetID(UserEntity user);//根据用户模糊查询List<UserEntity> getListByName(@Param("username")String username);
Usermapper.xml文件
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">select * from userinfo where id=#{id}</select><select id="getUseByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=${username}</select><select id="login" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username= '${username}' and password= '${password}'</select><update id="updatePassword">update userinfo set password=#{newpassword}where id = #{id} and password=#{password}</update><delete id="delById">delete from userinfo where id=#{id}</delete><insert id="addUser">insert into userinfo(username,password) values(#{username},#{password})</insert><insert id="addUserGetID" useGeneratedKeys="true" keyProperty="id">insert into userinfo(username,password) values(#{username},#{password})</insert><select id="getListByName" resultMap="BaseMap">select id,username,password as pwd from userinfo where username like concat('%',#{username},'%')</select>
这上面有参数占位符,具体的解释如下;
#预编译处理。
$:字符直接替换。
预编译处理是指:MyBatis在处理f时,会将SQL 中的州替换为?号,使用PreparedStatement的set方法来赋值。
直接替换:是MyBatis 在处理$时,就是把$替换成变量的值。
这里来说明一下预编译处理和字符串直接替换
预编译处理(Prepared Statement)和字符串替换(String Replacement)是两种不同的数据库查询参数传递方式,它们在性能和安全性方面有着明显的区别。
预编译处理(Prepared Statement):
预编译处理是一种参数化查询的方式,在查询语句中使用占位符(通常是问号 “?”)来表示参数。
在执行预编译处理时,数据库会将SQL语句和参数分开处理,首先将SQL语句编译成一个准备好的查询模板,然后再将参数传递到模板中执行。
预编译处理将SQL语句与参数分开,因此能够有效地防止SQL注入攻击,提高了查询性能和安全性。
由于数据库在执行查询前已经对SQL语句进行了编译,所以对于多次执行相同查询但参数不同的情况,预编译处理能够提高性能,因为数据库可以重复使用相同的查询模板。
字符串替换(String Replacement):
字符串替换是一种直接将参数值嵌入到SQL语句中的方式。在查询语句中,直接将参数值拼接到SQL语句中的相应位置。
字符串替换的查询方式容易受到SQL注入攻击,因为恶意用户可以在参数值中插入恶意的SQL代码,破坏数据库或获取敏感数据。
由于字符串替换是每次执行查询都会生成一个新的SQL语句,所以对于相同查询的多次执行,每次都需要重新编译和执行,性能相对较低。
具体来说的话,我们来看下面的图示:
不知道大家注意到没有.上面的字符串替换,为什么会出现SQL注入问题,这里我用一张图给大家去解释
2.2 多表查询
在进行多表联查之前,我们还需要准备一个文章表
CREATE TABLE `articleinfo` (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(100) NOT NULL,`content` text NOT NULL,`createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`uid` int(11) NOT NULL,`rcount` int(11) NOT NULL DEFAULT '1',`state` int(11) DEFAULT '1',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 |
插入了几条数据:
文章表的内容:
用户表的内容:
多表查询的接口类如下:
@Mapper
public interface ArticleMapper {//查询文章详情 一对一,一篇文章最多有一个作者ArticleInfoVO getDetail(@Param("id") Integer id);List<ArticleInfoVO> getArticleByUid(@Param("uid") Integer uid);}
配置文件如下:
<select id="getDetail" resultType="com.example.demo.entity.vo.ArticleInfoVO">select a.*,u.username from articleinfo aleft join userinfo u on u.id=a.uidwhere a.id=#{id}</select><select id="getArticleByUid" resultType="com.example.demo.entity.vo.ArticleInfoVO">select a.* ,u.username from articleinfo aleft join userinfo u on u.id = a.uidwhere a.uid=#{uid}</select>
三.动态SQL的实现
3.1 什么是动态SQL
动态sql 是Mybatis的强大特性之一,能够完成不同条件下不同的sql拼接。
当然官网对其的解释入下:
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
3.2 动态SQL的使用
if标签的使用
if标签用于在SQL语句中根据条件判断是否包含某个SQL片段。语法如下:
<select id="getUsers" resultType="User">SELECT * FROM users<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
在上面的例子中,根据传入的name和age参数的值,如果它们不为空,就会拼接对应的SQL条件,实现动态的查询语句。
trim标签的使用
trim标签用于在SQL语句的头部或尾部去除多余的SQL片段,以避免不必要的SQL语法错误。trim标签还可以根据条件去除WHERE或AND等关键字,以确保动态SQL的正确性。
trim标签中有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀.
prefixOverrides:表示整个语句块要去除掉的前缀.
suffixOverrides:表示整个语句块要去除掉的后缀
<select id="getUsers" resultType="User">SELECT * FROM users<trim prefix="WHERE" prefixOverrides="AND | OR"><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if><if test="gender != null">AND gender = #{gender}</if></trim>
</select>
在上面的例子中,我们使用了trim标签来包裹if标签,prefix属性指定了在条件满足时在WHERE关键字之前添加"WHERE",prefixOverrides属性指定了当条件不满足时去除多余的"AND "或"OR "。
where标签的使用
where标签用于在SQL语句中包含WHERE子句,并根据条件动态拼接查询条件。
<select id="getUsers" resultType="User">SELECT * FROM users<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
set标签的使用
根据传⼊的⽤户对象属性来更新⽤户数据,可以使⽤标签来指定动态内容。
UserMapper 接⼝中修改⽤户⽅法:根据传⼊的⽤户 id 属性,修改其他不为 null 的属性:
<update id="updateUser" parameterType="User">UPDATE users<set><if test="name != null">name = #{name},</if><if test="age != null">age = #{age},</if></set>WHERE id = #{id}
</update>
foreach标签的使用
foreach的属性如下所示:
collection:绑定方法参数中的集合,如List,Set,Map或数组对象item:遍历时的每一个对象
open:语句块开头的字符串close:语句块结束的字符串
separator:每次遍历之间间隔的字符串
foreach标签用于在SQL语句中遍历集合,生成对应的SQL片段。它常用于批量插入或更新操作。
<delete id="delByIdList"><!-- where id in(1,2..)-->delete from articleinfowhere id in<foreach collection="idList" item="aid" open="(" close=")" separator=",">#{aid}</foreach></delete>
这上面生成的语句,入下所示
delete from articleinfo where id in (1, 2, 3)
3.3 综合练习
在我们了解了基本的动态SQL标签之后,我们来一个综合的练习.
假设我们假如我们传入的参数都是非必须的.我们怎么才能完成SQL的拼接.
SQL如下:
select * from articleinfo WHERE id=? and title like concat(‘%’,?,‘%’)
我们传入参数的情况如下:
1.传入id,不传入title
2.传入title,不传入id
3.两个都不传
动态SQL的拼接策略如下:
方案一:
<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">select * from articleinfowhere 1=1 <trim prefixOVerrides="and"><if test="id!=null and id>0">and id=#{id}</if><if test="title!=null and title!=''">and title like concat('%',#{title},'%')</if></trim></select>
trim prefixOVerrides=“and”: 这是MyBatis的动态SQL标签trim的使用。trim标签用于在SQL语句的头部或尾部去除多余的SQL片段,并可以根据条件去除特定的关键字,如这里的and。prefixOverrides属性指定了当条件不满足时去除的前缀关键字,这里是and,表示如果条件不满足,就去除SQL语句中多余的and关键字。
方案二:
<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">select * from articleinfo<trim prefix ="where" suffixOverrides="and"><if test="id!=null and id>0">id=#{id}</if><if test="title!=null and title!=''">and title like concat('%',#{title},'%')</if></trim></select>
中间解释:
trim prefix=“where” suffixOverrides=“and”>: 这是MyBatis的动态SQL标签trim的使用。trim标签用于在SQL语句的头部或尾部去除多余的SQL片段,并可以根据条件去除特定的关键字,如这里的and。prefix属性指定了在条件满足时在WHERE关键字之前添加"where",suffixOverrides属性指定了当条件不满足时去除SQL语句中多余的"and"。
方案三:
<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">select * from articleinfo<where><if test="id!=null and id>0">id=#{id}</if><if test="title!=null and title!=''">and title like concat('%',#{title},'%')</if></where></select>
where标签: 这是MyBatis的动态SQL标签的使用。where标签的作用是将包含在其中的条件片段包裹在WHERE子句中,如果条件满足,WHERE关键字和多余的AND或OR将会被自动添加,如果条件不满足,则WHERE关键字也不会出现在最终的查询语句中
相关文章:

MyBatis(二)
文章目录 一.MyBatis的模式开发1.1 定义数据表和实体类1.2 配置数据源和MyBatis1.3 编写Mapper接口和增加xxxMapper.xml1.4 测试我们功能的是否实现. 二. Mybatis的增删查改操作2.1 单表查询2.2 多表查询三.动态SQL的实现3.1 什么是动态SQL3.2 动态SQL的使用if标签的使用trim标…...

【【51单片机AD转换模块】】
代码是简单的,板子是坏的,电阻是识别不出来的 main.c #include <REGX52.H> #include "delay.h" #include "LCD1602.h" #include "XPT2046.h"unsigned int ADValue;void main(void) {LCD_Init();LCD_ShowString(1,1…...
Longest Divisors Interval(cf)
题意:给定一个正整数n,求正整数的区间[l,r]的最大大小,使得对于区间中的每个i(即l≤i≤r),n是i的倍数。给定两个整数l≤r,区间[l,r]的大小为r−l1(即…...
配置文件、request对象请求方法、Django连接MySQL、Django中的ORM、ORM增删改查字段、ORM增删改查数据
一、配置文件的介绍 1.注册应用的 INSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contrib.sessions,django.contrib.messages,django.contrib.staticfiles,app01.apps.App01Config, ]################中间件###############…...

CTF学习路线指南(附刷题练习网址)
前言: PWN,Reverse:偏重对汇编,逆向的理解; Gypto:偏重对数学,算法的深入学习; Web:偏重对技巧沉淀,快速搜索能力的挑战; Mic:则更为复杂&…...
【Rust 基础篇】Rust默认泛型参数:简化泛型使用
导言 Rust是一种以安全性和高效性著称的系统级编程语言,其设计哲学是在不损失性能的前提下,保障代码的内存安全和线程安全。在Rust中,泛型是一种非常重要的特性,它允许我们编写一种可以在多种数据类型上进行抽象的代码。然而&…...

从源码分析Handler面试问题
Handler 老生常谈的问题了,非常建议看一下Handler 的源码。刚入行的时候,大佬们就说 阅读源码 是进步很快的方式。 Handler的基本原理 Handler 的 重要组成部分 Message 消息MessageQueue 消息队列Lopper 负责处理MessageQueue中的消息 消息是如何添加…...
shell编程 变量作用域
变量 变量赋值不用$,访问值时用$,赋值时两边不留空格,双引号括起来的变量被值替换{}标记变量开始和结束,变量名区分大小写,所有bash变量的值变量不区分类型,统一为字符串 变量类型 环境变量,子进程可以继承父进程环境…...

华为eNSP:isis的配置
一、拓扑图 二、路由器的配置 配置接口IP AR1: <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.1 24 [Huawei-GigabitEthernet0/0/0]qu AR2: <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthe…...
FS.05-SAS-UP-Methodology
FS.05-SAS-UP-Methodology-v9.2.pdf 附录 D 数据处理审核 作为现场数据处理系统和支持流程审核的一部分,受审核方最好在审核日期之前准备一些 SAS 特定的测试数据文件。 本文件提供了建议的方法; 受审核方和审核团队将就每次审核的具体方法达成一致。 …...

Jmeter并发测试
基本步骤 1、新建线程组 测试计划右键——>添加——>线程(用户)——>线程组 2、 添加HTTP请求 线程组右键——>添加——>取样器——>HTTP请求 3、 添加HTTP信息头管理器 线程组右键——>添加——>配置元件——>HTTP信息头…...

【JVM】浅看JVM的运行流程和垃圾回收
1.JVM是什么 JVM( Java Virtual Machine)就是Java虚拟机。 Java的程序都运行在JVM中。 2.JVM的运行流程 JVM的执行流程: 程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过…...

使用低代码开发,需要注意哪些?
低代码平台的历史相对较短,大约始于 2000 年初,源于快速应用程序开发工具。随着低代码平台和工具的日益普及和优势,它不断发展以满足各种领域和角色的需求。 本文将研究各种低代码和无代码应用程序开发方法、业务用例、挑战和未来预测等。 一…...

面试总结-Redis篇章(八)——Redis分布式锁
JAVA 面试总结-Redis分布式锁 模拟抢券场景通过下面方法添加Synchronized锁来防止上述情况,如果上面是单体服务没有问题,但是如果项目是集群部署,会出现下面的问题,因为Synchronized是属于本地的锁端口8080和8081同时访问…...

压力测试-商场项目
1.压力测试 压力测试是给软件不断加压,强制其在极限的情况下运行,观察它可以运行到何种程度,从而发现性能缺陷,是通过搭建与实际环境相似的测试环境,通过测试程序在同一时间内或某一段时间内,向系统发送预…...

IDEA中文UT方法执行报错问题、wps默认保存格式
wps默认保存格式、IDEA中文UT方法执行报错问题 背景 1、wps修改文件后,编码格式从UTF-8-bom变成UTF-8(notepad可以查看); 2、IDEA中文UT执行报错: 解决方案 1、语言设置中不要勾选 “Beta版。。。。” 2、cmd中执…...

Vue如何实现编程式导航声明方法,前进和后退导航
编程式导航声明方法,前进和后退导航 在router中设置路由导航跳转函数 只要发生跳转 导航的声明函数 访问控制系统如何形成 就这三种 导航守卫的案例,写一个Main.Vue 和login .Vue 后台主页 如果想要展示后台主页,就用这种方法 想实现路由跳转…...
torch.load 报错 ModuleNotFoundError 或 AttributeError
Python 3.11.3 (main, Apr 7 2023, 19:25:52) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin Type "help", "copyright", "credits" or "license" for more information.正常情况下,我们会使用 torch.save 保存模型的 …...

前端,js , Error in created hook: TypeError ,有bug了
怎么兄弟,遇到bug了???你开心吗,哈哈哈哈...
百度文心千帆大模型平台:企业级大模型服务的新航标
随着人工智能和大数据的快速发展,大模型平台正越来越受到各大企业和个人开发者的青睐。本文将以百度最新推出的文心千帆大模型平台为例,深入分析其在国家战略布局,经济发展趋势,市场变化动向和技术研发周期等方面的影响和应用。同…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...