SpringBoot第25讲:SpringBoot集成MySQL - MyBatis 注解方式
SpringBoot第25讲:SpringBoot集成MySQL - MyBatis 注解方式
本文是SpringBoot第25讲,上文主要介绍了Spring集成MyBatis访问MySQL,采用的是XML配置方式;我们知道除了XML配置方式,MyBatis还支持注解方式。本文主要介绍SpringBoot+MyBatis注解方式。
文章目录
- SpringBoot第25讲:SpringBoot集成MySQL - MyBatis 注解方式
- 1、准备知识
- 2、基本查改删操作
- 2.1、查询操作
- 1、@Results和@Result注解
- 2、@Select和@Param注解
- 3、@ResultMap注解
- 4、表关联查询
- 2.2、插入操作
- 1、@Insert注解
- 2、返回Insert后实体的主键值
- 2.3、更新操作
- 1、@Update 注解
- 2.4、删除操作
- 1、@Delete 注解
- 3、Provider注解
- 4、进一步理解
- 4.1、其它注解
- 4.2、xml方式和注解方式融合
- 4.3、为什么纯注解方式不是最佳选择?
- 5、示例源码
1、准备知识
MyBatis的相关知识体系。
具体可以参考 SpringBoot第24讲:SpringBoot集成MySQL - MyBatis XML方式
在构建知识体系时:我们最重要的目标并不是如何使用注解方式,而是要理解:
- 对于有原有xml方式改为注解方式(一定要有对比),如何写?
- 基本的CRUD怎么用注解写?
- 对于复杂的动态SQL如何写?
- 对于表关联的如何写?
- 为什么xml方式依然是比注解方式使用广泛?
- xml方式和注解方式混合使用?
- 注解方式是如何工作的呢?
2、基本查改删操作
我们从最基本的增删改操作开始,对比xml方式进行理解。
2.1、查询操作
1、@Results和@Result注解
对于xml配置查询时定义的ResultMap, 在注解中如何定义呢?
<resultMap type="springboot.mysql.mybatis.entity.User" id="UserResult1"><id property="id" column="id" /><result property="userName" column="user_name" /><result property="password" column="password" /><result property="email" column="email" /><result property="phoneNumber" column="phone_number" /><result property="description" column="description" /><result property="createTime" column="create_time" /><result property="updateTime" column="update_time" />
</resultMap>
使用注解方式,用@Results注解对应
@Results(id = "UserResult1",value = {@Result(id = true, property = "id", column = "id"),@Result(property = "userName", column = "user_name"),@Result(property = "password", column = "password"),@Result(property = "email", column = "email"),@Result(property = "phoneNumber", column = "phone_number"),@Result(property = "description", column = "description"),@Result(property = "createTime", column = "create_time"),@Result(property = "updateTime", column = "update_time")}
)
2、@Select和@Param注解
对于查询,用@Select注解;对于参数, 使用@Param注解
所以根据用户ID查询用户,使用注解方式写法如下:
String SELECT_USER_SQL = "select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time from tb_user u";@Results(id = "UserResult1",value = {@Result(id = true, property = "id", column = "id"),@Result(property = "userName", column = "user_name"),@Result(property = "password", column = "password"),@Result(property = "email", column = "email"),@Result(property = "phoneNumber", column = "phone_number"),@Result(property = "description", column = "description"),@Result(property = "createTime", column = "create_time"),@Result(property = "updateTime", column = "update_time")}
)
@Select({SELECT_USER_SQL, " where id = #{id}"})
User findById1(@Param("id") Long id);
3、@ResultMap注解
xml配置查询时定义的ResultMap是可以复用的,那么我们上面通过@Results定义在某个方法上的,如何复用呢?
比如查询所有用户返回用户实体@Results是和查询单个用户一致的,那么我们可以通过@ResultMap指定返回值对应关系
@ResultMap("UserResult1")
@Select(SELECT_USER_SQL)
User findAll1();
由此你可以猜到,@ResultMap定义在哪个方法上并没有什么关系,因为它会被优先通过注解解析为数据库字段与Java字段的映射关系。
4、表关联查询
用户和角色存在着一对多的关系,上面的查询只是查询了用户的基本信息,如何关联查询(查询用户同时返回角色信息)呢?
我们看下xml配置方式是如何做到的?
<resultMap type="springboot.mysql.mybatis.entity.User" id="UserResult"><id property="id" column="id" /><result property="userName" column="user_name" /><result property="password" column="password" /><result property="email" column="email" /><result property="phoneNumber" column="phone_number" /><result property="description" column="description" /><result property="createTime" column="create_time" /><result property="updateTime" column="update_time" /><collection property="roles" ofType="springboot.mysql.mybatis.entity.Role"><result property="id" column="id" /><result property="name" column="name" /><result property="roleKey" column="role_key" /><result property="description" column="description" /><result property="createTime" column="create_time" /><result property="updateTime" column="update_time" /></collection>
</resultMap>
使用注解方式, 可以通过@Results+@Many注解
@Results(id = "UserResult",value = {@Result(id = true, property = "id", column = "id"),@Result(property = "userName", column = "user_name"),@Result(property = "password", column = "password"),@Result(property = "email", column = "email"),@Result(property = "phoneNumber", column = "phone_number"),@Result(property = "description", column = "description"),@Result(property = "createTime", column = "create_time"),@Result(property = "updateTime", column = "update_time"),@Result(property = "roles", column = "id", many = @Many(select = "springboot.mysql.mybatis.anno.dao.IRoleDao.findRoleByUserId", fetchType = FetchType.EAGER))}
)
其中findRoleByUserId是通过user表中的id查找Role, 具体方法如下
@Results(id = "RoleResult",value = {@Result(id = true, property = "id", column = "id"),@Result(property = "name", column = "name"),@Result(property = "roleKey", column = "role_key"),@Result(property = "description", column = "description"),@Result(property = "createTime", column = "create_time"),@Result(property = "updateTime", column = "update_time")})@Select("select r.id, r.name, r.role_key, r.description, r.create_time, r.update_time from tb_role r, tb_user_role ur where r.id = ur.user_id and ur.user_id = #{userId}")List<Role> findRoleByUserId(Long userId);
对于一对一的可以使用@One注解。
2.2、插入操作
涉及插入操作的主要注解有:@Insert, @SelectKey等。
1、@Insert注解
对于插入操作,在xml配置可以定义为:
<insert id="save" parameterType="springboot.mysql.mybatis.xml.entity.User" useGeneratedKeys="true" keyProperty="id">insert into tb_user(<if test="userName != null and userName != ''">user_name,</if><if test="password != null and password != ''">password,</if><if test="email != null and email != ''">email,</if><if test="phoneNumber != null and phoneNumber != ''">phone_number,</if><if test="description != null and description != ''">description,</if>create_time,update_time)values(<if test="userName != null and userName != ''">#{userName},</if><if test="password != null and password != ''">#{password},</if><if test="email != null and email != ''">#{email},</if><if test="phoneNumber != null and phoneNumber != ''">#{phoneNumber},</if><if test="description != null and description != ''">#{description},</if>sysdate(),sysdate())
</insert>
特别是,这里通过<if>判断条件更新的情况应该如何在注解中写呢?
可以通过@Insert + <script>
@Insert({"<script> ", "insert into tb_user(\n" +" <if test=\"userName != null and userName != ''\">user_name,</if>\n" +" <if test=\"password != null and password != ''\">password,</if>\n" +" <if test=\"email != null and email != ''\">email,</if>\n" +" <if test=\"phoneNumber != null and phoneNumber != ''\">phone_number,</if>\n" +" <if test=\"description != null and description != ''\">description,</if>\n" +" create_time,\n" +" update_time\n" +" )values(\n" +" <if test=\"userName != null and userName != ''\">#{userName},</if>\n" +" <if test=\"password != null and password != ''\">#{password},</if>\n" +" <if test=\"email != null and email != ''\">#{email},</if>\n" +" <if test=\"phoneNumber != null and phoneNumber != ''\">#{phoneNumber},</if>\n" +" <if test=\"description != null and description != ''\">#{description},</if>\n" +" sysdate(),\n" +" sysdate()\n" +" )", " </script>"})
@Options(useGeneratedKeys = true, keyProperty = "id")
int save(User user);
2、返回Insert后实体的主键值
上述
@Options(useGeneratedKeys = true, keyProperty = "id")表示什么意思呢?
表示,如果数据库提供了自增列生成Key的方式(比如这里的id),并且需要返回自增主键时,可以通过这种方式返回实体。
那么,如果id的自增不使用数据库自增主键时, 在xml中可以使用SelectKey:
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">SELECT LAST_INSERT_ID()
</selectKey>
对应着注解:
@SelectKey(statement = "SELECT LAST_INSERT_ID()", keyColumn = "id", keyProperty = "id", resultType = Long.class, before = false)
before = false, 相当于XML中的order=“AFTRE”,这是MySql数据库的配置。before = true, 相当于XML中的order=“BEFORE”,这是Oracle数据库的配置。
注意事项:不同的数据库statement的值会不同,上面中的值适用于MySql数据库,使用其他类型的数据库时要注意修改。
2.3、更新操作
涉及更新操作的主要注解有:@Update等。
1、@Update 注解
对于xml的更新操作如下:
<update id="update" parameterType="springboot.mysql.mybatis.xml.entity.User">update tb_user<set><if test="userName != null and userName != ''">user_name = #{userName},</if><if test="email != null and email != ''">email = #{email},</if><if test="phoneNumber != null and phoneNumber != ''">phone_number = #{phoneNumber},</if><if test="description != null and description != ''">description = #{description},</if>update_time = sysdate()</set>where id = #{id}
</update><update id="updatePassword" parameterType="springboot.mysql.mybatis.xml.entity.User">update tb_user<set>password = #{password}, update_time = sysdate()</set>where id = #{id}
</update>
对应的注解写法如下:
@Update({"update tb_user set password = #{password}, update_time = sysdate()", " where id = #{id}"})
int updatePassword(User user);@Update({"<script> ", "update tb_user\n" +" <set>\n" +" <if test=\"userName != null and userName != ''\">user_name = #{userName},</if>\n" +" <if test=\"email != null and email != ''\">email = #{email},</if>\n" +" <if test=\"phoneNumber != null and phoneNumber != ''\">phone_number = #{phoneNumber},</if>\n" +" <if test=\"description != null and description != ''\">description = #{description},</if>\n" +" update_time = sysdate()\n" +" </set>\n" +" where id = #{id}", " </script>"})
int update(User user);
2.4、删除操作
涉及删除操作的主要注解有:@Delete等。
1、@Delete 注解
对于xml的删除操作如下:
<delete id="deleteById" parameterType="Long">delete from tb_user where id = #{id}
</delete><delete id="deleteByIds" parameterType="Long">delete from tb_user where id in<foreach collection="array" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
对应的注解写法如下:
@Delete("delete from tb_user where id = #{id}")
int deleteById(Long id);@Delete({"<script> ", "delete from tb_user where id in\n" +"<foreach collection=\"array\" item=\"id\" open=\"(\" separator=\",\" close=\")\">\n" +"#{id}\n" +"</foreach>", " </script>"})
int deleteByIds(Long[] ids);
3、Provider注解
其实你可以发现通过注解方式,对于有一些需要通过动态构建查询条件的操作是非常不方便的。MyBatis的作者们自然就想到了动态构建SQL,动态构建SQL的方式是配合@Provider注解来完成的。
MyBatis提供了4种Provider注解,分别是 @SelectProvider、@InsertProvider、@UpdateProvider 和 @DeleteProvider。
这里以@SelectProvider为例来根据Id查询User:
1、定义包含自定义生成的动态SQL的类,比如UserDaoProvider
/*** @author qiwenjie*/
public class UserDaoProvider {public String findById(final Long id) {SQL sql = new SQL();sql.SELECT("u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time");sql.FROM("tb_user u");sql.WHERE("id = " + id);return sql.toString();}
}
2、通过@SelectProvider注解关联到定义的类和方法
@ResultMap("UserResult")
@SelectProvider(type = UserDaoProvider.class, method = "findById")
User findById2(Long id);
4、进一步理解
让我们通过几个问题,进一步理解MyBatis注解方式。
4.1、其它注解
@CacheNamespace:为给定的命名空间 (比如类) 配置缓存。对应xml中的<cache>。@CacheNamespaceRef:参照另外一个命名空间的缓存来使用。属性:value,应该是一个名空间的字 符串值(也就是类的完全限定名) 。对应xml中的<cacheRef>标签。@ConstructorArgs:收集一组结果传递给一个劫夺对象的构造方法。属性:value,是形式参数 的数组。@Arg:单独的构造方法参数 , 是 ConstructorArgs 集合的一部分。属性: id,column,javaType,typeHandler。id 属性是布尔值, 来标识用于比较的属性,和XML元素相似。对应xml中的<arg>标签。@Case:单独实例的值和它对应的映射。属性: value,type,results。Results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。对应xml中标签<case>。@TypeDiscriminator: 一组实例值被用来决定结果映射的表 现。 属性: column, javaType, jdbcType, typeHandler,cases。cases 属性就是实例的数组。对应xml中标签<discriminator>。@Flush: 在MyBatis 3.3以上版本,可以通过此注解在Mapper接口中调用SqlSession#flushStatements()。
4.2、xml方式和注解方式融合
xml方式和注解方式是可以融合写的, 我们可以将复杂的SQL写在xml中
比如将resultMap定义在xml中
<resultMap type="springboot.mysql.mybatis.xml.entity.User" id="UserResult3"><id property="id" column="id" /><result property="userName" column="user_name" /><result property="password" column="password" /><result property="email" column="email" /><result property="phoneNumber" column="phone_number" /><result property="description" column="description" /><result property="createTime" column="create_time" /><result property="updateTime" column="update_time" /><collection property="roles" ofType="springboot.mysql.mybatis.xml.entity.Role"><result property="id" column="id" /><result property="name" column="name" /><result property="roleKey" column="role_key" /><result property="description" column="description" /><result property="createTime" column="create_time" /><result property="updateTime" column="update_time" /></collection>
</resultMap>
在方法中用 @ResultMap
@ResultMap("UserResult3")
@Select("select u.id, u.password, u.user_name, u.email, u.phone_number, u.description, u.create_time, u.update_time from tb_user u")
User findAll1();
4.3、为什么纯注解方式不是最佳选择?
纯注解方式为何很少大规模呢? 说说我的一些看法
- 1、对于复杂的SQL,特别是按照条件动态生成方式极为不便,即便有
<script>, 代码的阅读体验和维护极为不佳; - 2、对于复杂的SQL,即便有@Provider方式,这种充其量是一个半成品
- 不是所见即所得的写法,需要再定义额外的类和方法
- 动态构建时不便利
- 函数式编程成为主流,lambda方式才是未来
这也是mybatis-plus等工具改进的地方。
5、示例源码
todo
相关文章:
SpringBoot第25讲:SpringBoot集成MySQL - MyBatis 注解方式
SpringBoot第25讲:SpringBoot集成MySQL - MyBatis 注解方式 本文是SpringBoot第25讲,上文主要介绍了Spring集成MyBatis访问MySQL,采用的是XML配置方式;我们知道除了XML配置方式,MyBatis还支持注解方式。本文主要介绍Sp…...
服务器返回 413 Request Entity Too Large
问题 上传一个大于1.5M的文件时,报错:413 Request Entity Too Large 使用的配置 1、用的是docker环境,还有一层代理,代理用的镜像是:jwilder/nginx-proxy 2、docker里是有php和nginx 确认配置 docker里的php和ngi…...
如何一目了然地监控远程 Linux 系统
动动发财的小手,点个赞吧! Glances 是一款免费的开源、现代、跨平台、实时 top 和类似 htop 的系统监控工具,与同类工具相比,它提供了先进的功能,并且可以在不同的模式下运行:作为独立模式、客户端/服务器模…...
9.环境对象和回调函数
9.1环境对象 指的是函数内部特殊的变量this,它代表着当前函数运行时所处的环境 作用: 弄清楚this的指向,可以让我们代码更简洁 ➢函数的调用方式不同,this指代的对象也不同 ➢[谁调用,this 就指代谁] 是判断this指向的…...
51单片机(普中HC6800-EM3 V3.0)实验例程软件分析概览
本专栏将分析普中HC6800-EM3 V3.0 (9.22)\5--实验程序\基础实验例程中的各个例程的代码。 引言:本专栏将对历程中的关键代码进行分析与拓展,再学习一遍51,记录与各位一起进步。 下面是文件列表: E:\USER\000study\000_51单片机\000普中HC6800-EM3 V3.0 (9.22)\5--实…...
ubuntu18.04 安装php7.4-xdebug
文章目录 场景解决 场景 apt install php7.4-xdebug 下载失败, 只好通过编译解决了 解决 https://xdebug.org/wizard 输入php -i的执行结果...
java 定时任务不按照规定时间执行
这里写目录标题 使用异步启动可能出现的问题排查代码中添加的定时任务步骤是否正确排查是否任务阻塞,如果定时任务出现异常阻塞后,将不会在次执行java中多个Scheduled定时器不执行为了让Scheduled效率更高,我们可以通过两种方法将定时任务变成…...
Android复习(Android基础-四大组件)—— Activity
Activity作为四大组件之首,是使用最为频繁的一种组件,中文直接翻译为"活动",不过如果被翻译为"界面"会更好理解。正常情况,除了Window,Dialog和Toast , 我们能见到的界面只有Activity。…...
Linux系统安装部署MongoDB完整教程(图文详解)
前言:本期给大家分享一下目前最新Linux系统安装部署MongoDB完整教程,我的服务器采用的是Centos7,在部署之前我重装了我的服务器,目的是为了干净整洁的给大家演示我是如何一步步的操作的,整体部署还是挺简洁,…...
CSS图片放到<div>里面,自适应宽高全部显示,点击图片跳到新页面预览,点击旋转按钮图片可旋转
有一个需求是图片放到一个固定宽高的<div>里面,不管是横图还是竖图,都要全部显示出来并且保持图片的长宽比例不变形,点击图片可以跳到一个新页面预览,代码如下: <!DOCTYPE html> <html> <head>…...
二阶段web基础与http协议
dns与域名 网络是基于tcp/ip协议进行通信和连接的 应用层-----传输层-----网络层-----数据链路层-----物理层 ip地址,每一台主机都有一个唯一的地址标识(固定的ip地址) 1.区分用户和计算机 2.通信 ip地址的问题在于32位二进制数组成的&…...
SpringBoot+Freemark根据html模板动态导出PDF
SpringBootFreemark根据html模板导出PDF 1、引入maven2、两个工具类2.1 test.html模板2.2 test.html模板中的Freemark语法 3、controller导出pdf 1、引入maven 导出pdf的一些必要jar包 <dependency><groupId>org.projectlombok</groupId><artifactId>…...
XPath数据提取与贴吧爬虫应用示例
XPath数据提取与贴吧爬虫应用示例 XpathXpath概述Xpath Helper插件 XPath语法基本语法查找特定节点选取未知节点选取若干路径 lxml模块使用说明使用示例 百度贴吧爬虫 Xpath Xpath概述 XPath(XML Path Language)是一种用于在XML文档中定位和选择节点的语…...
字符串匹配-KMP算法
KMP算法,字符串匹配算法,给定一个主串S,和一个字串T,返回字串T与之S匹配的数组下标。 在学KMP算法之前,对于两个字符串,主串S,和字串T,我们根据暴力匹配,定义两个指针,i指…...
Java面向对象之UML类图
UML类图 表示 public 类型, - 表示 private 类型,#表示protected类型方法的写法:方法的类型(、-) 方法名(参数名: 参数类型):返回值类型...
【机器学习】西瓜书学习心得及课后习题参考答案—第4章决策树
这一章学起来较为简单,也比较好理解。 4.1基本流程——介绍了决策树的一个基本的流程。叶结点对应于决策结果,其他每个结点则对应于一个属性测试;每个结点包含的样本集合根据属性测试的结果被划分到子结点中;根结点包含样本全集&a…...
2023.8.2
2022河南萌新联赛第(三)场:河南大学\神奇数字.cpp //题意:给定三个正整数a b c,求x满足满足abc同余x的个数。 //这个考虑同余的性质,就是两个数的差去取模为0的数肯定是这两个数的同余数,。因此我们计算三个数两两之…...
windows运行窗口常用快捷键命令
winr打开运行窗口,然后输入快捷命令:(当然utools和win11搜索也挺好用的) cmd : 命令行窗口(命令提示符窗口、cmd窗口)regedit : 注册表mspaint : 画图工具services.msc : 本地服务设置(比如查看mysql服务是否启动成功)devmgmt.ms…...
HDFS的QJM方案
Quorum Journal Manager仲裁日志管理器 介绍主备切换,脑裂问题解决---ZKFailoverController(zkfc)主备切换,脑裂问题解决-- Fencing(隔离)机制主备数据状态同步问题解决 HA集群搭建集群基础环境准备HA集群规…...
安装win版本的neo4j(2023最新版本)
安装win版本的neo4j 写在最前面安装 win版本的neo4j1. 安装JDK2.下载配置环境变量(也可选择直接点击快捷方式,就可以不用配环境了)3. 启动neo4j 测试代码遇到的问题及解决(每次环境都太离谱了,各种问题)连接…...
如何重置idea ai assistant ACP 插件中的 Cursor 账号登录状态?
在使用 JetBrains 系列 IDE(如 IntelliJ IDEA, PyCharm 等)配合 ACP (AI Assistant) 插件时,经常会遇到想要更换 Cursor 账号却“退出无门”的情况。即使卸载插件还是会重新登录之前的账号。 本文分享一个通过手动运行本地 Agent 脚本强制重…...
0 基础入门 Agent:理论知识体系搭建指南
本文档系统梳理 AI Agent 的核心理论知识,帮助理解”为什么这样设计”,为动手构建 Agent 打下认知基础。 1. 从 LLM 到 Agent:为什么需要 Agent 1.1 LLM 的能力与局限 大语言模型(LLM)本质上是一个”文本补全机器”…...
从邮件处理Agent入手:新手搭建AI智能体的真实代价与边界
先说结论邮件处理Agent看似低门槛,但真实部署涉及邮箱协议、错误处理和API成本,新手容易低估复杂度框架选择不是非此即彼,LangChain生态完善但学习曲线陡峭,低代码工具快速出原型但定制受限Agent的核心价值在于自主规划与工具调用…...
电池材料行业数据管理新突破:AI4S驱动的科学数据平台正在重塑电池材料开发范式
电池行业背景描述:电池材料行业是新能源汽车、储能设备等新能源领域的基础产业,近年来随着全球能源转型和电动汽车等新能源应用的快速发展,电池材料行业得到了快速发展。在我国,锂电池作为主要电池类型,其产业链不断完…...
避坑指南:SAP生产订单历史状态配置OPL8的3个关键点与报工数据丢失解决方案
SAP生产订单状态管理深度解析:从OPL8配置到数据追溯实战 1. 生产订单状态管理的核心价值与常见痛点 在制造业ERP实施中,生产订单状态管理就像流水线上的指示灯系统,实时反映每个订单的生命周期位置。但许多实施团队往往将注意力集中在物料需求…...
java进阶知识思维导图
...
【大模型LLM学习】天池Deep Research Agent开发赛
前言 运气不错,这次压线前10,不过和top还是有差距的数据包括问题和答案可以在比赛官网获取,包括初赛的100题和复赛100题。赛题为输入query输出answer,通过搭建Search Agent,分解问题、多跳搜索识别线索,找…...
特征值可视化指南:用Matplotlib动态演示PCA降维全过程
特征值可视化指南:用Matplotlib动态演示PCA降维全过程 在数据科学领域,理解高维数据的结构是一项基础但关键的能力。主成分分析(PCA)作为最常用的降维技术之一,其核心数学原理却常常让初学者望而生畏——特征值、特征向…...
如何为DeepSeek-VL2开发自定义处理器和扩展
如何为DeepSeek-VL2开发自定义处理器和扩展 【免费下载链接】DeepSeek-VL2 DeepSeek-VL2: Mixture-of-Experts Vision-Language Models for Advanced Multimodal Understanding 项目地址: https://gitcode.com/gh_mirrors/de/DeepSeek-VL2 DeepSeek-VL2是一款强大的视觉…...
【无人售货柜・RK+YOLO】篇 6:安卓端落地!RK3576 + 安卓系统,YOLO RKNN 模型实时推理保姆级教程
目录 一、前置说明 & 新手扫盲 新手必守的红线 二、第一步:环境 & 资源准备,新手零坑版 三、第二步:创建安卓项目,配置环境 四、第三步:核心功能实现,全流程代码带注释 模块 1:动…...
