Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Hibernate对象导航语言

Hibernate对象导航语言

作者头像
爱撒谎的男孩
发布于 2019-12-31 06:56:18
发布于 2019-12-31 06:56:18
92400
代码可运行
举报
文章被收录于专栏:码猿技术专栏码猿技术专栏
运行总次数:0
代码可运行

文章目录

  1. 1. HQL - 对象导航语言
    1. 1.1. 简介
    2. 1.2. 步骤
    3. 1.3. 准备
    4. 1.4. 实体查询
      1. 1.4.1. 格式
      2. 1.4.2. 拓展
      3. 1.4.3. 实例
    5. 1.5. 部分字段的查询
      1. 1.5.1. 格式
      2. 1.5.2. 实例
    6. 1.6. 多表联合查询
    7. 1.7. 前提
      1. 1.7.1. 常见的联合查询方式
      2. 1.7.2. 对象方式关联查询
        1. 1.7.2.1. 实例
      3. 1.7.3. join方式查询
        1. 1.7.3.1. 左外连查询
          1. 1.7.3.1.1. 格式
          2. 1.7.3.1.2. 实例
        2. 1.7.3.2. 右外连接查询
          1. 1.7.3.2.1. 格式
          2. 1.7.3.2.2. 实例
        3. 1.7.3.3. 迫切左外连接
        4. 1.7.3.4. 迫切右外连接
        5. 1.7.3.5. select子句关联查询
          1. 1.7.3.5.1. 格式
          2. 1.7.3.5.2. 实例
    8. 1.8. 去除重复的数据
    9. 1.9. 聚合函数的查询
      1. 1.9.1. 常见的聚合函数
    10. 1.10. order by子句
    11. 1.11. group by 子句
    12. 1.12. 参考文章

HQL - 对象导航语言

简介

  • HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:
    1. 在查询语句中设定各种查询条件;
    2. 支持投影查询, 即仅检索出对象的部分属性;
    3. 支持分页查询;
    4. 支持连接查询;
    5. 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字;
    6. 提供内置聚集函数, 如 sum(), min() 和 max();
    7. 支持子查询;
    8. 支持动态绑定参数;
    9. 能够调用 用户定义的 SQL 函数或标准的 SQL 函数。

步骤

  1. 获取Session对象
  2. 编写hql语句
  3. 使用session.createQuery(String hql)创建Query对象
  4. 使用session.setXX(index,Object)设置占位符的值
  5. 执行query.list()获取实体对象即可

准备

  • 创建Husband实体类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.tedu.hibernate.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="husband")
public class Husband implements Serializable{
	private static final long serialVersionUID = 7403209578400736239L;
	private Integer id;
	private String name;
	private int age;
	private Wife wife;
	
	@Id
	@GeneratedValue   //主键自增长
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	
	@Column(length=10)
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	@OneToOne(fetch=FetchType.LAZY) 
	@JoinColumn(name="wife_id")   //设置外键名称为wife_id
	public Wife getWife() {
		return wife;
	}
	public void setWife(Wife wife) {
		this.wife = wife;
	}
	
	@Override
	public String toString() {
		return "Husband [id=" + id + ", name=" + name + ", age=" + age
				+ ", wife=" + wife + "]";
	}
}
  • 创建Wife的实体类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="wife")
public class Wife implements Serializable {
	private static final long serialVersionUID = -7203920255946679244L;
	private Integer id;
	private String name;
	private int age;
	@Id
	@GeneratedValue
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	
	@Column(length=10)
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Wife [id=" + id + ", name=" + name + ", age=" + age + "]";
	}
}
  • 创建工具类,用来生成Session
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
	private static Configuration configuration;
	private static SessionFactory sessionFactory;
	/*
	 * 静态语句块中的内容只是在类加载的时候只创建一次,因此这里的大大减少了资源的消耗
	 */
	static {
		// 加载核心配置文件hibernate.cfg.xml
		configuration = new Configuration();
		configuration.configure();
		// 创建SessionFactotry对象
		sessionFactory = configuration.buildSessionFactory();
	}
	//创建session对象,在测试类中可以使用这个静态方法获取session
	public static Session getSession() {
		return sessionFactory.openSession();
	}
}

实体查询

  • 查询结果返回的是一个List<>的集合。其中的泛型为实体类
  • 相当于sql语句中的select * from husband;
  • 使用的hql语句是from Husband where id=?,这里还可以和sql语句一样使用别名来获取其中的值,比如: from Husband h where h.id=?

格式

  • from Husband where id=? ,其中的Husband是实体类的名字,而不是表的名称,后面的属性实体类中的属性名称,而不是表中字段的名称,区分大小写

拓展

  • where子句中只要是sql语句被能够满足的都是可以写的,比如=, , <, >, <=, >=, between, not between, in ,not in, is, like,同时也是可以写算术表达式
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM User user WHERE user.age<20
FROM User user WHERE user.name IS null
FROM User user WHERE user.name LIKE 'Er%'
FROM User user WHERE (user.age % 2 = 1)
FROM User user WHERE (user.age<20) AND (user.name LIKE '%Er')

实例

  • 查询husband这张表,其中对应的实体类是Husband
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
	public void test1() {
		Session session = null;
		Transaction transaction = null;
		try {
			// 创建session
			session = HibernateUtil.getSession();
			// 开始事务
			transaction = session.beginTransaction();
            
			//编写hql语句
			String hql="from Husband where id=?";
            //String hql="from Husband h where h.id=?";
            
			//创建Query
			Query query=session.createQuery(hql);
			//设置占位符的值,这里的用法和PreparedStatement一样的用法
			query.setInteger(0,1);
			List<Husband> husbands=query.list();  //执行查询语句,返回的是list集合
			
			for (Husband husband : husbands) {
				System.out.println(husband);
			}
			
			// 提交事务
			transaction.commit();
		} catch (Exception exception) {
			transaction.rollback(); // 事务回滚
		} finally {
			if (session != null) {
				session.close();
			}
		}
	}

部分字段的查询

  • 实体对象的查询返回的是一个实体对象的List<>集合,我们这里需要查询的是表中的执行字段,而不是全部的字段

格式

  • select 实体类属性名 from 实体类名字 where 条件语句

实例

  • 查询出id=1的所有的husband中的name和age
    • sql语句:select name,age from husband where id=1
    • hql语句: select name,age from Husband h where h.id=?,此时的占位符id的值为1
  • 此时查询返回的结果List是一个Object[],其中的元素是nameage,并且是按照hql的语句的查询顺序存储的
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//编写hql语句,只查询name和age属性字段
String hql="select name,age from Husband where id=?";
//创建Query
Query query=session.createQuery(hql);
//设置占位符的值,这里的用法和PreparedStatement一样的用法
query.setInteger(0,1);

//这里返回的是一个List集合,但是其中的每一个元素都是一个Object数组
List<Object[]> lists=query.list(); 
//遍历List集合
for (Object objects : lists) {
	//遍历数组,[0]的元素是name,[1]的元素是age
	for(int i=0;i<objects.length;i++){
		System.out.println(objects[i]);
	}
}
  • 这里查询的是两个字段,返回的结果List中存放的是Object[]但是如果我们查询的只有一个字段,那么返回的结果List中存放的是Object,这个值是你查询的字段的值

多表联合查询

前提

  • 必须存在关联关系,比如一对一,一对多,多对多

常见的联合查询方式

  1. 对象方式的关联查询
    1. 这个是HQL所特有的,因为这个需要用到对象之间的关系
  2. join方式关联
  3. select子句关联

对象方式关联查询

  • 假设我们需要查询wifeid值为1的husband表中指定的字段,我们除了使用多表联合查询,我们也可以使用关联查询,因为在Husband的实体类中有Wife这个对象
    • hql语句: select name,age from Husband h where h.wife.id=?
实例
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//编写hql语句,where字句中的条件是wife的id
	String hql="select h.name,w.name from Husband h,Wife w where h.wife.id=? ";
	//创建Query
	Query query=session.createQuery(hql);
	//设置占位符的值,这里的用法和PreparedStatement一样的用法
	query.setInteger(0,1);
	
	List<Object> lists=query.list();
	
	//遍历查询结果
	for (Object object : lists) {
		Object[] objects=(Object[])object;
		for (int i = 0; i < objects.length; i++) {
			System.out.println(objects[i]);
		}
	}

join方式查询

左外连查询
  • 这个是等值连接的一种,即使两张表中的某一条数据不存在关联关系,那么也会全部查询出左边的那张表的全部数据
  • sql语句:select * from husband h left join wife w on h.wife_id=w.id,查询丈夫的所有数据并且和其对应妻子的信息,其中husbandwife这两张表是通过wife_id这个外键关联的
  • hql语句: select h.name,h.age,w.name,w.age from Husband h left join h.wife w,这条语句和上面的sql语句是一样的功能
格式
  • select 实体类属性 from 实体类名 [as] 别名 left join 别名.关联对象名 [as] 别名
    • 其中的as可以省略
    • 如果不需要查询关联对象的属性,那么后面的别名可以省略
    • left join后面跟的是实体类的关联对象,比如Husband中的Wife对象h.wife,这里就相当sql中的on h.wife_id=w.id
实例
  • 查询所有丈夫的信息和其对应的妻子的所有信息
  • hql: from Husband h left join h.wife,虽然这里的使用的是实体查询的方式,但是返回的却是Object[],其中的第一个元素是Husband对象,第二个是Wife对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//编写hql语句
String hql="from Husband h left join h.wife";
//创建Query
Query query=session.createQuery(hql);

//执行查询,这里返回的是一个Object数组,其中数组的第一个元素是husband的数据,第二个是wife的数据
List<Object[]> list=query.list();
for (Object[] objects : list) {
	Husband husband=(Husband) objects[0];  //获取Husband对象
	Wife wife=(Wife)objects[1];  //获取Wife对象
}
  • 查询所有的丈夫信息和起对应的妻子的所有信息,这个和上面的例子一样,只不过是另外一种hql语句方式
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//编写hql语句
String hql="select h,w from Husband h left join h.wife w";
//创建Query
Query query=session.createQuery(hql);

List<Object[]> list=query.list();

for (Object[] objects : list) {
	Husband husband=(Husband) objects[0];  //获取Husband对象
	Wife wife=(Wife)objects[1];  //获取Wife对象
}
  • 查询所有丈夫的nameage和其对应的妻子的的nameage信息
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//编写hql语句
String hql="select h.name,h.age,w.name,w.age from Husband h left join h.wife w";
//创建Query
Query query=session.createQuery(hql);

List<Object> list=query.list();

for (Object object : list) {
	Object[] objects=(Object[])object;
	for (int i = 0; i < objects.length; i++) {
		System.out.print(objects[i]+"\t");
	}
	System.out.println();
}
  • 查询丈夫的所有信息
右外连接查询
  • 右外链接查询和左外连接查询的方式是一样的,只是此时如果出现两条记录没有关联关系的话,那么保留的是右边的表中的数据,即是查询右边表的所有数据和其对应的左边表的数据
格式
  • select 实体类属性 from 实体类名 [as] 别名 right join 别名.关联对象名 [as] 别名
    • 其中的as可以省略
    • 如果不需要查询关联对象的属性,那么后面的别名可以省略
    • right join后面跟的是实体类的关联对象,比如Husband中的Wife对象h.wife,这里就相当sql中的on h.wife_id=w.id
实例
  • 查询所有妻子的信息和其对应的丈夫的信息: select h,w from Husband h right join h.wife w
迫切左外连接
迫切右外连接
select子句关联查询
格式
  • select 对象.属性名,.... from 类名
    • 其中的对象实体类中的对象属性,比如Husband类中的Wife对象
实例
  • select h.wife.name,h.wife.age,h.name from Husband h
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//编写hql语句,where字句中的条件是wife的id
String hql="select h.wife.name,h.wife.age,h.name from Husband h";
//创建Query
Query query=session.createQuery(hql);
//设置占位符的值,这里的用法和PreparedStatement一样的用法

List<Object> lists=query.list();

//遍历查询结果
for (Object object : lists) {
	Object[] objects=(Object[])object;
	for (int i = 0; i < objects.length; i++) {
		System.out.println(objects[i]);
	}
}

去除重复的数据

  • sql语句一样,使用distinct即可
  • 比如: select distinct name,age from Husband where id=?

聚合函数的查询

  • hql语句和sql一样,都是可以使用聚集函数查询
  • select count(*) from Husband where id=? 根据id查询出对应的人数

常见的聚合函数

  • count(*): 计算数量
    • select count(*) from Husband where id=?
  • sum() :求和
    • select sum(age) from Husband h where h.wife.id=?
  • AVG(): 求平均值
    • select avg(age) from Husband where age>10
  • MAX(): 求最大值
    • select max(age) from Husband where age>10 and age<90
  • MIN(): 求最小值
    • select min(age) from Husband where age>10 and age<100

order by子句

  • from Husband where id=? order by name desc,age asc 按照姓名将序排列,年龄升序排列

group by 子句

  • hql中也是可以使用group by子句进行分组的,比如select count(*),sum(age),max(age) from Husband h where h.age>? group by h.name
  • 同时也是可以使用having子句进行聚合函数的条件过滤,比如select count(*),sum(age),max(age) from Husband h where h.age>? group by h.name having count(*)>?

参考文章

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
给Ionic写一个cordova(PhoneGap)插件
 给Ionic写一个cordova(PhoneGap)插件 之前由javaWeb转html5开发,由于面临新技术,遂在适应的过程中极为挣扎,不过还好~,这个过程也极为短暂;现如今面临一些较为复杂的需求还会有一丝丝头痛,却没有一开始那么强烈了。。。 在正式写下文之前,我先感谢公司大boss:王总,感谢他让我进入了一个有挑战性的技术公司 并在这个过程中一直鼓励我不断汲取新技术,同时也指正了我在开发中的一些不太好的习惯,十分感谢!   再~,感谢在开发中给予我太多帮助的杜勇以及孙金~,不论是需求讨论还是具体
上帝
2018/05/18
2K0
Cordova@6.4.0以上灵活修改配置
还记得我在此文【技巧】ionic3优雅解决启动前、后黑白屏问题中添加了一个勾子插件cordova-custom-config来修改配置的主题。
IT晴天
2018/08/20
1.4K0
自定义Cordova插件详解
在混合式应用中,我们通过现有的Cordova插件,可以轻松的在 H5 上调用手机native的功能。现有的Cordova插件能满足平时大部分的开发需求,然而,有时候找不到合适的插件、或对找到的插件有不满意的地方,那就要动手去做或改写一个插件,这时候就要了解一些Cordova插件的相关知识。
IT晴天
2018/08/20
2.3K0
【技巧】ionic3优雅解决启动前、后黑白屏问题
启动前黑白屏问题,仅存在于android,是android应用的通病,ionic表示这锅它不背。 具体操作时,当我们点击桌面图标启动APP时,有时会闪一下黑色背景,有时黑色背景时间还比较长。原因是:
IT晴天
2018/08/20
3.7K0
第一个PhoneGap(cordova)应用
PhoneGap是一套能让你使用HTML5轻松调用本地API接口和发布应用到商店的应用开发平台。官方说有低成本,低开发周期,轻量化等优点,这些咱暂时也没法证明,略过不表。但是有一条跨平台,却是很明显的优势。因为它采用HTML5+JavaScript的模式来开发应用。PhoneGap用JavaScript统一封装了几大平台的本地api(Andriod,IOS,WP8/7,WINRT)等等。。这样的话从一个平台移植到另外一个平台只需要把HTML代码跟JS原封不动的拿过去,打包一下就可以了。PhoneGap后来被Adobe收购,然后又贡献给了开源社区,现在由Apache管理,改名cordova。
MJ.Zhou
2022/05/07
4350
第一个PhoneGap(cordova)应用
Cordova-扫描二维码(竖屏) 原
首先想到的是安装cordova plugin add cordova-plugin-barcodescanner插件
tianyawhl
2019/04/04
1.6K0
使用 Cordova 构建应用的流程
Cordova 应用程序有几个组件。 下图展示了 Cordova 应用程序体系结构的高级视图。
acc8226
2022/05/17
4.5K0
使用 Cordova 构建应用的流程
Cordova 创建 Demo插件
MyPluginName.js JavaScript接口,用于插件与混合应用的接口。
acc8226
2022/05/17
7190
ionic莫名其妙的“cordova/platform_metadata”
我的Cordova升级到8已经很久了,而且在此环境下原有的项目跑得好好的,所以别人说有问题需要把Corodva降级到7.1或者7.0时,我不置可否。
IT晴天
2018/08/20
6580
Cordova 实现热更新
文章来源 http://blog.csdn.net/zhuzhiqiang_zhu/article/details/53608398 一、添加插件 说明:在这个步骤里面,以下的命令需要在项目根目录下执行 ● 新建Cordova项目 ○ cordova create CordovaHotCode com.ezample.hotcode ● 添加android平台 ○ cordova platform add android ● 添加iOS平台 ○ cordova platform add ios ● 添加自动更新插件 ○ cordova plugin add cordova-hot-code-push-plugin ● 添加cordova hot code push客户端,用于生成www目录下文件的hash码,更新的时候对比使用。(注意:安装过就不用在安装了) ○ npm install -g cordova-hot-code-push-cli 二、配置
happlyfox
2018/10/31
1.6K0
eclipse遇到的问题
引用不了R文件,可能是导包导错了cannot be resolved or is not a field:首先检查你的XML是否保存了,再检查你的import导入的R文件是你包名+R还是android.R R文件丢失:如果确定配置文件、代码无错的情况下,请将 Project -> Build Automatically 打上勾 R文件不生成的原因都是因为有错误引起的, Android 资源文件夹下的文件不能有大写字符,会导致R.java无法生成 在做图的时候,给图片命名经常会大小写混合命名比较好
六月的雨
2018/05/14
1.1K0
eclipse遇到的问题
R文件丢失:如果确定配置文件、代码无错的情况下,请将 Project -> Build Automatically 打上勾 R文件不生成的原因都是因为有错误引起的, Android 资源文件夹下的文件不能有大写字符,会导致R.java无法生成 在做图的时候,给图片命名经常会大小写混合命名比较好记,这些图片复制到drawable目录后,如果在xml文件里面调用将有可能导致R.java文件无法生成。
六月的雨
2022/01/12
4750
Flutter开发-发布时注意事项
Flutter 应用在 Android 端上启动时会有一段很明显的白屏现象,白屏的时长由设备的性能决定,设备性能越差,白屏时间越长. 然后这个白屏是可以控制的,在Android代码中的style.xml中有这样一段代码:
码客说
2020/06/02
1.2K0
【云+社区年度征文】让移动开发更简单,集成异常上报、运营统计与应用升级
做移动开发最麻烦的就是收集用户在使用过程中的程序的异常崩溃日志,因为这个异常崩溃是无征兆的在毫无防备随时的出现,所以有时候真是丈二金刚(摸不着头脑);这个还是其次要命的是用户端程序的每次迭代和版本的分布又不容易推送和获取。
谭广健
2020/12/19
7380
[转]Android应用安装包apk文件的反编译与重编译、重签名
背景介绍:最近在做Robotium自动化测试,使用到solo.takeScreenshot()函数以在测试过程中截图,但此函数需要被测试APP具有<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />权限。在只有被测试APP的apk文件的情况下,修改apk文件后缀名为zip,解压缩后,修改AndroidManifest.xml文件,删除META-INF文件夹,重压缩为apk文件后,再签名就可以了。 但是!本文舍近求远,借机对apk文件进行反编译与重编译、重签名,来修改源代码中的AndroidManifest.xml文件。本文这么做的目的,就是想熟悉一下反编译、重编译和重签名的过程。
战神伽罗
2019/07/24
1.9K0
[转]Android应用安装包apk文件的反编译与重编译、重签名
Cordova插件开发——滑动手势解锁(iOS篇)
第一次正儿八经的参与Cordova的项目,想写下些文字,以便日后需要的时候能够帮助自己快速回忆起来,同时也希望能够帮到需要的朋友。
100000798482
2018/08/20
2.2K0
Cordova插件开发——滑动手势解锁(iOS篇)
Android权限机制,你真的了解吗?
一、Android的权限机制 Android是目前最流行的智能手机软件平台之一,在智能移动终端如火如荼发展的同时,其安全态势也日益严峻。有调查表明,恶意软件的数量在持续的上升,Google在Android安全机制上面也做了很多工作,并且一直在持续的更新,其Android的安全模型由3个部分组成:Linux安全机制、Android本地库及运行环境安全与Android特有的安全机制,如下图: 本文只涉及到其中的权限机制介绍,其他的部分如果有感兴趣的,我们可以后续一起探讨。 Android的权限管理遵循的是
腾讯移动品质中心TMQ
2018/02/05
6.6K0
Android权限机制,你真的了解吗?
Cordova 初识
Cordova 是使用 HTML,CSS 和 JavaScript构建混合移动应用程序的平台。官方文档给了我们 Cordova 的定义。
acc8226
2022/05/17
1.3K0
Cordova 初识
05. 使用 cordova plugman 编辑和添加插件
$ plugman install --platform <ios|android> --project <directory> --plugin <name|url|path> [--plugins_dir <directory>] [--www <directory>] [--variable <name>=<value> [--variable <name>=<value> ...]]
acc8226
2022/09/22
6000
Cordova 运行 Web 应用
Cordova 非常的流行,因为它可以让 Web 开发人员来创建移动应用, 而且还可以通过 JavaScript 来调用设备硬件 API (GPS、蓝牙等)。
beginor
2020/08/10
1.1K0
推荐阅读
相关推荐
给Ionic写一个cordova(PhoneGap)插件
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验