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 测试代码遇到的问题及解决(每次环境都太离谱了,各种问题)连接…...

ChatGPT结合知识图谱构建医疗问答应用 (二) - 构建问答流程
一、ChatGPT结合知识图谱 上篇文章对医疗数据集进行了整理,并写入了知识图谱中,本篇文章将结合 ChatGPT 构建基于知识图谱的问答应用。 下面是上篇文章的地址: ChatGPT结合知识图谱构建医疗问答应用 (一) - 构建知识图谱 这里实现问答的流程…...

聊天系统登录后端实现
定义返回的数据格式 # Restful API from flask import jsonifyclass HttpCode(object):# 响应正常ok 200# 没有登陆错误unloginerror 401# 没有权限错误permissionerror 403# 客户端参数错误paramserror 400# 服务器错误servererror 500def _restful_result(code, messa…...

Ajax笔记_01(知识点、包含代码和详细解析)
Ajax_01笔记 前置知识点 在JavaScript中 问题1:将数组转为字符串,以及字符串转为数组的方式。 问题2、将对象转为字符串,以及字符串转为对象的方法。 方法: 问题1: 将数组转为字符串可以使用 join() 方法。例如&…...

Eureka 学习笔记2:EurekaClient
版本 awsVersion ‘1.11.277’ EurekaClient 接口实现了 LookupService 接口,拥有唯一的实现类 DiscoveryClient 类。 LookupService 接口提供以下功能: 获取注册表根据应用名称获取应用根据实例 id 获取实例信息 public interface LookupService<…...

Spring引入并启用log4j日志框架-----Spring框架
<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://ma…...

Redis实现延时队列
缓存队列延时向接口报工,并支持多实例部署。 引入依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-data</artifactId><version>3.17.4</version> </dependency> 注入RedisClient …...

无限遍历,Python实现在多维嵌套字典、列表、元组的JSON中获取数据
目录 背景 思路 新建两个函数A和B,函数 A处理字典数据,被调用后,判断传递的参数,如果参数为字典,则调用自身; 如果是列表或者元组,则调用列表处理函数B; 函数 B处理列表&#x…...

信息学奥赛一本通——1180:分数线划定
文章目录 题目【题目描述】【输入】【输出】【输入样例】【输出样例】【提示】 AC代码 题目 【题目描述】 世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才,A市对所有报名的选手进行了笔试,笔试分数达到面试分数线的选手方可进入…...

SpringApplication对象的构建及spring.factories的加载时机
构建SpringApplication对象源码: 1、调用启动类的main()方法,该方法中调用SpringApplication的run方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, …...

基于传统检测算法hog+svm实现图像多分类
直接上效果图: 代码仓库和视频演示b站视频005期: 到此一游7758258的个人空间-到此一游7758258个人主页-哔哩哔哩视频 代码展示: 数据集在datasets文件夹下 运行01train.py即可训练 训练结束后会保存模型在本地 运行02pyqt.py会有一个可视化…...