SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第五天)MyBatis的注解开发
SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第五天)MyBatis的注解开发
昨天我们深入学习了MyBatis多表之间的关联映射,了解掌握了一对一关联映射,一对多关联映射,嵌套查询方式以及嵌套结果方式,掌握了缓存机制的一级缓存,二级缓存等概念,也使用了代码进行复现理解。但是都是基于XML配置文件的方式来实现的,现在我们要学习一下Mybatis提供的更加简便的注解配置方式。
那么今天我们要掌握的是MyBatis的注解开发:
- 掌握基于注解的单表增删改查
- 掌握基于注解的关联查询
一、基于注解的单表增删改查
什么是注解?
Annotation(注解)就是Java提供了一种为程序元素关联任何信息或任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
注解不会影响程序代码的执行,Annotation能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息。需要注意的是,这里存在着一个基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。
什么是MyBatis的注解开发?
MyBatis 支持通过注解来进行数据库操作,这种方式被称为 MyBatis 的注解开发。通过注解可以更直观地在 Java 代码中定义 SQL 语句,省去了繁琐的 XML 配置。
MyBatis的注解开发通常需要使用接口来进行。
①在我们项目的根目录java下,新建一个dao包。
DAO(Data Access Object)层是在软件架构中用于封装与数据库或其他持久化机制的交互的一种设计模式。在典型的三层架构(或多层架构)中,DAO 层通常是位于持久化层的组成部分,它负责执行数据库操作,提供数据的增、删、改、查等基本操作,同时也可以包含一些复杂的查询逻辑它提供了一种抽象层,将应用程序的业务逻辑与底层的数据访问细节分离开来,使代码更加清晰、可维护和可测试。
②dao包中新建一个接口(interface):Bookmapper(如下图)
③BookMapper接口中编写上SQL放在方法的上方。
package dao;import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import pojo.Book;import java.util.List;public interface BookMapper {@Select("select * from book")List<Book> selectBook();@Select("select * from book where id =#{id}")Book getOneBookById(Integer id);@Update("update book set id=#{id},bookName=#{bookName},price=#{price},author=#{author}")Boolean updateALlItem(Book book);
}
我们之前有学过,SQl语句,那么就很简单能够理解了,抽出一小块理解一下:
@Select("select * from book")//相当于sql语句,用于访问数据库List<Book> selectBook();//相当于java里面的方法,返回值是List<Book>
@Select("select * from book")
: 这是一个用于查询的注解,表示执行了一个查询操作。括号中的内容是执行的SQL语句,这里查询语句的意思是查询整个book
表的所有数据。List<Book>
: 这是查询的返回类型,表示查询的结果将会被映射到一个List
集合中,集合中的每个元素都是一个Book
对象。selectBook()
: 这是方法的名称,可以根据需要命名。当调用这个方法时,MyBatis会执行对应的SQL查询,并将结果映射到返回类型指定的集合中。记得使用了注解的话,就得对应写入方法,不然会报错。
这样的注解方式可以直接在接口中定义SQL查询语句,非常方便。MyBatis会在运行时根据这些注解执行对应的数据库操作。
④往mybatis-config.xml中注册注解。
<mappers><mapper resource="mapper/PasswordMSMapper.xml"/><mapper resource="mapper/IdCardMapper.xml"/><mapper resource="mapper/PersonMapper.xml"/><mapper resource="mapper/UserMapper.xml"/><mapper resource="mapper/BookMapper.xml"/><mapper class="dao.BookMapper" /></mappers>
注意和以前的xml不同的是,这里是class,不是resource
<mapper class="dao.BookMapper" />
是 MyBatis 配置文件中的一个元素,用于指定一个 DAO 接口对应的 Mapper 类。这个元素告诉 MyBatis 哪个接口的方法应该与 XML 映射文件中的 SQL 语句进行关联。
在这里,<mapper class="dao.BookMapper" />
的作用是将名为 BookMapper
的 DAO 接口与 XML 映射文件关联起来,使得该接口的方法可以与 XML 文件中的 SQL 语句进行匹配,从而实现数据库的操作。
⑤编写测试类查看效果
@Testvoid findBookById() {//Annotation mode test注解方式查询数据库//1.创建SqlSession实例,用于连接数据库SqlSession session=MyBatisUtil.createSqlSession();//2.调用查询语句,传入参数查询List<Book> books=session.selectList("selectBook");//3.输出结果for(Book book:books){logger.info("ID:"+book.getId()+", 书名:"+book.getBookName()+",价格 :"+book.getPrice()+",作者:"+book.getAuthor());}session.close();//关闭连接,若是增删改这些业务操作需要先提交一下事务session.commit();}
可以发现能够正常的调用sql语句,并且访问数据库获取结果,但是呢红色圈标注的那里,一般我们要做修改成这样:表示调用BookMapper接口中的selectBook方法,这样更符合接口的调用习惯。
//2.调用查询语句,传入参数查询List<Book> books=session.getMapper(BookMapper.class).selectBook();
那么自行补充其他的注解语句,增删改查等,或者可以下载我的资源文件,里面全都写好了
二、掌握基于注解的关联查询
还是我们之前的场景一对一关联映射,一对多关联映射
①一对一关联映射Person和IdCard,先编写俩个类的Mapper接口文件,因为pojo类之前已经生成过了,前面几天中就用过了。
PersonMapper接口如下:
package dao;import org.apache.ibatis.annotations.*;
import pojo.IdCard;
import pojo.Person;@Mapper
public interface PersonMapper {// 根据id查询人员信息,并关联查询身份证信息@Select("SELECT * FROM person WHERE id = #{id}")@Results({@Result(property = "id", column = "id"),@Result(property = "name", column = "name"),@Result(property = "age", column = "age"),@Result(property = "sex", column = "sex"),@Result(property = "cardId", column = "id", javaType = IdCard.class, one = @One(select = "dao.IdCardMapper.getIdCardById"))})//这里就是经典的一对一关联映射了 javaType,One,Person getPersonWithIdCard(Integer id);// 插入人员信息@Insert("INSERT INTO person (name, age, sex, card_id) VALUES (#{name}, #{age}, #{sex}, #{cardId.id})")@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")void insertPerson(Person person);// 更新人员信息@Update("UPDATE person SET name = #{name}, age = #{age}, sex = #{sex}, card_id = #{cardId.id} WHERE id = #{id}")int updatePerson(Person person);// 删除人员信息@Delete("DELETE FROM person WHERE id = #{id}")int deletePerson(Integer id);
}
IdCardMapper接口如下:
package dao;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;import pojo.IdCard;@Mapper
public interface IdCardMapper {@Select("SELECT * FROM idcard WHERE id = #{id}")IdCard getIdCardById(@Param("id") Integer id);//插入语句@Insert("INSERT INTO idcard (code) VALUES (#{code})")@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")void insertIdCard(IdCard idCard);
}
②mybatis-config.xml中写入注解注册
<mappers><mapper resource="mapper/PasswordMSMapper.xml"/><mapper resource="mapper/IdCardMapper.xml"/><mapper resource="mapper/PersonMapper.xml"/><mapper resource="mapper/UserMapper.xml"/><mapper resource="mapper/BookMapper.xml"/><mapper class="dao.BookMapper" /><mapper class="dao.PersonMapper" /><mapper class="dao.IdCardMapper" /></mappers>
记得是使用class元素不是resource了
③编写测试类查看一对一关联映射查询的情况
可以看到生成了俩个sql语句去进行数据库访问,这是因为我们使用了嵌套查询的方式
查看PersonMapper中的设定:
@Result(property = "cardId", column = "id", javaType = IdCard.class, one = @One(select = "dao.IdCardMapper.getIdCardById"))
④稍微解释一下代码:
当使用 MyBatis 进行一对一关联查询时,我们需要告诉 MyBatis 如何将查询结果映射到 Java 对象中。这时我们会使用 @Result
注解来指定映射关系。
property
:表示 Java 对象中的属性,即要映射的属性名。column
:表示数据库中的列名,即查询结果中对应的列。javaType
:表示要映射到的 Java 类型,这里是IdCard.class
,表示要将查询结果映射为IdCard
类型的对象。one
:表示一对一关系,这里使用了@One
注解来指定关联查询的方法。
这段代码的作用是在执行一对一关联查询时,将查询结果中的 id
列映射到 Java 对象的 cardId
属性,并且通过 dao.IdCardMapper.getIdCardById
方法来查询与之关联的证件信息。这样,在查询 Person
信息的同时,也会查询并映射对应的 IdCard
信息。。
接下来是一对多关联映射,一对多的场景就是我们之前的俩个表User和tb_Order
①一对一关联映射Users和TbOrder,先编写俩个类的Mapper接口文件,因为pojo类之前已经生成过了,前面几天中就用过了。
UserMapper接口如下:
package dao;import org.apache.ibatis.annotations.*;
import pojo.Users;
import pojo.TbOrder;import java.util.List;@Mapper
public interface UserMapper {@Select("SELECT * FROM users WHERE uid=#{id}")@Results({@Result(property = "uid", column = "uid"),@Result(property = "uname", column = "uname"),@Result(property = "uage", column = "uage"),@Result(property = "orderList", column = "uid", javaType = List.class, many = @Many(select = "dao.TbOrderMapper.findTheOrderByUserId"))})Users findTheOrderWithUserById(Integer id);
}
这里记得一对多是使用many,然后这里的javaType要改成List.class因为订单可能会有多条记录
TbOrderMapper接口如下:
package dao;import org.apache.ibatis.annotations.Select;
import pojo.TbOrder;import java.util.List;public interface TbOrderMapper {@Select("SELECT * FROM tb_order WHERE userid=#{id}")List<TbOrder> findTheOrderByUserId(Integer id);
}
②mybatis-config.xml中写入注解注册
<mappers><mapper class="dao.BookMapper" /><mapper class="dao.PersonMapper" /><mapper class="dao.IdCardMapper" /><mapper class="dao.UserMapper" /><mapper class="dao.TbOrderMapper" /><mapper resource="mapper/PasswordMSMapper.xml"/><mapper resource="mapper/IdCardMapper.xml"/><mapper resource="mapper/PersonMapper.xml"/><mapper resource="mapper/UserMapper.xml"/><mapper resource="mapper/BookMapper.xml"/></mappers>
这一次要稍作修改,因为mybatis-config.xml文件中的扫描方式是从上往下扫描,所以<mappers>
元素下引入UsersMapper和OrdersMapper接口的位置,必须在引入UserMapper.xml和TbOrderMapper.xml文件位置前面,否则程序将会首先读取到引入的UserMapper.xml和TbOrderMapper.xml文件,程序将有可能会报错。
③编写测试类:
//使用注解方式一对多关联查询订单信息通过id
@Test
void findTheOrderWithUserById(){SqlSession session = MyBatisUtil.createSqlSession();Users users=session.getMapper(dao.UserMapper.class).findTheOrderWithUserById(1);List<TbOrder> tbOrders=users.getOrderList();for (TbOrder order:tbOrders){logger.info("id"+users.getUid()+",姓名:"+users.getUname()+",年龄:"+users.getUage()+",产品名:"+order.getProductname()+",价格:"+order.getPrice()+",库存"+order.getNumber());}session.close();
}
输出结果如下:
成功显示一对多数据查询,那么要注意的是一对一一对多的数据插入等操作,记得先对单表进行操作,再去更新有外键的表,这样才不会报错,出现误读。
以下是一些常用的 MyBatis 注解及其用法:
-
@Select:用于查询操作。
@Select("SELECT * FROM users WHERE id = #{id}") User getUserById(int id);
-
@Insert:用于插入操作。
@Insert("INSERT INTO users (id, username, email) VALUES (#{id}, #{username}, #{email})") int insertUser(User user);
-
@Update:用于更新操作。
@Update("UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}") int updateUser(User user);
-
@Delete:用于删除操作。
@Delete("DELETE FROM users WHERE id = #{id}") int deleteUser(int id);
-
@Result 和 @Results:用于定义结果映射。
@Results({@Result(column = "id", property = "id"),@Result(column = "username", property = "username"),@Result(column = "email", property = "email") }) @Select("SELECT * FROM users") List<User> getAllUsers();
-
@Param:用于传递参数。
@Select("SELECT * FROM users WHERE username = #{username} AND email = #{email}") User getUserByUsernameAndEmail(@Param("username") String username, @Param("email") String email);
-
@Options:用于设置一些选项,如主键返回,主键自增等
@Insert("INSERT INTO users (username, email) VALUES (#{username}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") int insertUser(User user);
-
@ResultMap:引用在 XML 配置文件中定义的 ResultMap。
@ResultMap("UserResultMap") @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(int id);
使用注解开发可以更紧凑地定义数据库操作,但对于复杂的动态 SQL 或多表关联查询,通常 XML 配置更为灵活和可读。在实际项目中,你可以根据场景选择使用注解还是 XML 配置,甚至两者结合使用。
总结
今天是Mybatis学习的第五天,今天学的Mybatis的注解开发。其实需要详细理解一下,然后自己去完成一下注解方式单表的增删改,还有注解方式的多表关联查询,嵌套条件方式和嵌套结果方式。看似简单,实则需要自己动手操作才能彻底掌握,理解清楚多表之间的关系,哪个表必须先处理,然后怎么样去嵌套获取信息等。还有今天了解一些注解关键字,虽然看着熟悉,实际上使用的话,还是会有一些坑要踩的,需要慢慢理解。
想要跟着学习的可以去我的资源里面找对应的文件下载,我的md文件也会发上去,项目文件会上传可以自己跟着学习一下。
PS:还可以自己学着掌握使用Mybatis和Xml联合开发的方式,或者我后面单独放一章出来讲。
作者:Stevedash
发表于:2023年8月27日 14点40分
注:本文内容基于个人学习理解,如有错误或疏漏,欢迎指正。感谢阅读!如果觉得有帮助,请点赞和分享。
相关文章:

SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第五天)MyBatis的注解开发
SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录(第五天)MyBatis的注解开发 昨天我们深入学习了MyBatis多表之间的关联映射,了解掌握了一对一关联映射,一对多关联映射,嵌套查询方…...

VBA技术资料MF48:VBA_在Excel中将列号与字母转换
【分享成果,随喜正能量】除非自己的认知获得了改变和刷新,否则,人们常说的“顺应自己的内心”,顺的不过是一颗旧心,一颗惯性的,充满了各种习性的套路之心。与“顺应自己的内心”恰恰相反,人要用…...

LeetCode-160. 相交链表
这是一道真的非常巧妙的题,题解思路如下: 如果让他们尾端队齐,那么从后面遍历就会很快找到第一个相交的点。但是逆序很麻烦。 于是有一个巧妙的思路诞生了,如果让短的先走完自己的再走长的,长的走完走短的,…...

微信小程序如何实现页面传参和页面传递多个参数
前言 只要你的小程序超过一个页面那么可能会需要涉及到页面参数的传递,下面我总结了 4 种页面方法。 下面时多个参数页面传参的方式 let loveJSON.stringify(this.data.totle);let youJSON.stringify(this.data.totleId)let csdnJSON.stringify(this.data.totleP…...

ChatGPT⼊门到精通(3):ChatGPT 原理
OpenAI在2022年11⽉份发布ChatGPT,强⼤的⽂字对话、创意写作能⼒,全球掀起了⼀ 波AI浪潮。本⽂对ChatGPT的技术原理、厉害之处、可能的落地⽅向等⽅⾯进⾏了全⾯ 的解析,看完后会对ChatGPT有更深⼊的了解。 ⼀、前⾔ 2022年11⽉30⽇&#x…...

nginx配置keepalive长连接
nginx之keepalive详解与其配置_keepalive_timeout_恒者走天下的博客-CSDN博客 为什么要有keepalive? 因为每次建立tcp都要建立三次握手,消耗时间较长,所以为了减少tcp建立连接需要的时间,就可以设置keep_alive长连接。 nginx中keep_alive对…...

Thread.enumerate方法
Thread.enumerate方法的作用是将当前线程所对应的的线程组包含的所有线程放入一个数组 参见源码注释 /*** Copies into the specified array every active thread in the current* threads thread group and its subgroups. This method simply* invokes the {@link java.lan…...

* 号靠近数据类型,和靠近变量名, 号靠近数据类型,和靠近变量名,有什么区别
文章目录 一、int* age 和 int *age,* 号靠近数据类型,和靠近变量名,有什么区别:1. int* age:2. int *age: 二、int& age 和 int &age,& 号靠近数据类型,和靠近变量名,有什么区别&a…...

为了做好农业,拼多多请来顶尖农业专家当独立董事
8月29日,拼多多发布截至6月30日的2023年第二季度业绩报告。财报显示,拼多多集团今年第二季度收入为523亿元,同比增长66%,远超市场预期。 财报发布的同时,拼多多还宣布,其董事会已聘任荷兰瓦赫宁根大学终身…...

Linux服务器安装部署MongoDB数据库 – 【无公网IP远程连接】
文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 前言 MongoDB是一个基于分布式文件存储的数据库。由 C 语言编写,旨在为 WEB 应用提供可扩展的高…...

Python+PIL+qrcode实现二维码自由—普通二维码+彩色二维码+logo二维码+动态二维码(附完整代码)
有时候我们需要自己制作一个二维码,然后进行打印下来,或者说在二维码中提前写上一段话比如搞笑的话,然后印在衣服上,然后穿出去玩!的🤣 那么今天我们分享一下制作二维码的几种方式: 哎&#x…...

【Spring Data JPA】JPA 常用查询函数
文章目录 前言函数查询表格 前言 函数查询的表格参考了官网的 2.7.3 版本的文档,JPA 的这种函数式查询方法改动不大,如果想知道更多的复杂查询,可以参考这篇文章 【Spring Data JPA】基于 JpaRepository 增删改查 官方文档地址 Spring Data…...

Visual Studio 2022的MFC框架——AfxWinMain全局对象和InitInstance函数
我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Visual Studio 2022下开发工具的MFC框架知识。 在看这篇帖子前,请先看我的另一篇帖子《Visual Studio 2022的MFC框架——应用程序向导》。 当程序调用了CWinApp类的构造…...

【网络】多路转接——poll | epoll
🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言:你只管努力,剩下的交给时间! 书接上文五种IO模型 | select。 poll | epoll 🍧poll🧁认识接口🧁简…...

音视频 ffmpeg命令视频录制(Windows)
先安装dshow软件 Screen Capturer Recorder, 项目地址:https://sourceforge.net/projects/screencapturer/files/ 然后查看可用设备名字:ffmpeg -list_devices true -f dshow -i dummy [dshow 0509d6c0] DirectShow video devices (some ma…...

【拾枝杂谈】从游戏开发的角度来谈谈原神4.0更新
君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们,这里是君兮_,结合最近的学习内容和以后自己的目标,今天又开了杂谈这个新坑,分享一下我在学习游戏开发的成长和自己的游戏理解,当然现在还是一枚…...

QT设置mainwindow的窗口title
QT设置mainwindow的窗口title 在QT程序中,通常会有**aaaa-[bbbbbbb]**这种形式的title,对于刚上手qt的程序员同学,可能会简单的以为修改这种title,就是使用setWindowTitle这个接口,其实只对了一半,这种形式…...

SaaS多租户系统架构设计
前言:多租户是SaaS(Software-as-a-Service)下的一个概念,意思为软件即服务,即通过网络提供软件服务。SaaS平台供应商将应用软件统一部署在自己的服务器上,客户可以根据工作的实际需求,通过互联网…...

Java自定义捕获异常
需求分析 ElectricalCustomerVO electricalCustomerVO new ElectricalCustomerVO(); electricalCustomerVO.setElcNumber(chatRecordsLog.getDeviceNumber()); List<ElectricalCustomerVO> electricalCustomerlist electricalCustomerMapper.selectElectricalCustomer…...

力扣--数组类题目27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 示例 1: 输入:nums [3,2,2,3], val 3 输出:2, nums [2,2] 解释:函数应该返回新的长度 2, 并且 n…...

实际并行workers数量不等于postgresql.conf中设置的max_parallel_workers_per_gather数量
1 前言 本文件的源码来自PostgreSQL 14.5,其它版本略有不同 PostgreSQL的并行workers是由compute_parallel_worker函数决定的,compute_parallel_worker是估算扫描所需的并行工作线程数,并不是您在postgresql.conf中设置的max_parallel_work…...

java定位问题工具
一、使用 JDK 自带工具查看 JVM 情况 在我的机器上运行 ls 命令,可以看到 JDK 8 提供了非常多的工具或程序: 接下来,我会与你介绍些常用的监控工具。你也可以先通过下面这张图了解下各种工具的基本作用: 为了测试这些工具&#x…...

【Java】基础入门 (十六)--- 异常
1.异常 1.1 异常概述 异常是指程序在运行过程中出现的非正常的情况,如用户输入错误、除数为零、文件不存在、数组下标越界等。由于异常情况再程序运行过程中是难以避免的,一个良好的应用程序除了满足基本功能要求外,还应具备预见并处理可能发…...

[javaWeb]Socket网络编程
网络编程:写一个应用程序,让这个程序可以使用网络通信。这里就需要调用传输层提供的 api。 Socket套接字 传输层提供协议,主要是两个: UDP和TCP 提供了两套不同的 api,这api也叫做socket api。 UDP和 TCP 特点对比: UDP: 无连…...

<MySon car=“宝马“ :money=“money“></MySon>有没有冒号
为什么car"宝马"没有: 但是 :money"money"就有: <script setup> import {ref} from vue import MySon from /components/MySon.vueconst money ref(100) </script><template><h3>father</h3><My…...

netty(三):NIO——多线程优化
NIO多线程优化 使用Boss线程来处理accepct事件使用Worker线程来处理读写事件,可以创建多个worker线程 package com.review;import lombok.extern.slf4j.Slf4j;import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.*; impor…...

Linux操作系统--linux概述
1.Linux概述 Linux,全称GNU/Linux,是一种免费使用和自由传播的类UNIX操作系统(OS)。简单的说就是一种操作系统。在日常中常见的操作系统有一下三种: 2.linux起源和背景 (1).linux的诞生 linux操作系统是由李纳斯托瓦兹…...

数组中出现次数超过一半的数字
⭐️ 题目描述 🌟 OJ链接:数组中出现次数超过一半的数字 思路: 采用投票计数的方式,我们可以把每个数字都看成一次投票并且计数,那么最后剩下来的就是数组中数字出现次数最多的那一个。比如 { 1,2,3,2,2,2,5,4,2 } &a…...

网络优化工程师,你真的了解吗?
一、5G网络优化工程师到底是什么? 5G,就是我们通常所说的第五代移动通信标准,属于目前最热门的新技术趋势。随着2019年5G技术进入正式的商用阶段,拥有广阔的发展前景,备受瞩目。“5G工程师”这个词是一个概念词&#x…...

git 的常用命令
git是一个版本管理器,是程序员必备工具之一,其主分为三个区: 工作区: 暂存区: 仓库: 通过保持软件版本,分支,合并,等多种版本操作,使软件能在自己想要的版本…...