Hibernate_day02总结

Hibernate_day02总结

今日内容

l Hibernate持久化对象的状态

l Hibernate的一级缓存

l Hibernate操作持久化对象的方法

l Hibernate 关联关系映射

1.1 上次课内容回顾:

Hibernate框架的概述.

* 什么是Hibernate

* 持久层的ORM框架.

* ORM:对象关系映射.

* 常见的持久层框架

* JPA

* Hibernate

* Mybatis

* JdbcTemplate

* Hibernate流行版本:

* 3.x和4.x

Hibernate的快速入门:

* 下载Hibernate开发环境.

* 了解目录结构.

* 创建项目,引入jar包.

* 创建表和实体

* 创建映射文件.

* 创建核心配置文件.

* 编写测试类.

Hibernate的CRUD的操作:

* save()

* update()

* get/load()

* delete

* createQuery();

* createCriteria()

Hibernate的常见配置:

* 映射文件的配置:

* <class> :类与表的映射.

* name :类的路径

* table :表名

* <id>

* name :类中属性

* column :表中字段

* <property>

* name :类中属性

* column :表中字段

* 核心配置文件的配置:

* hibernate.properties

手动加载映射文件

Configuration cfg = new Configuration();

* hibernate.cfg.xml

Configuration cfg = new Configuration().configure();

Hibernate的核心API:

* Configuration

* addResource();

* addClass();

* SessionFactory:

* 抽取工具类.

* Session:

* get/load的区别?

* get立即发送.load延迟加载.

* get返回的真实对象.load返回代理对象.

* get返回null.load返回ObjectNotFoundException.

* Transaction:

* commit();

* rollback();

* wasCommitted();

* Query:

* Criteria:

Hibernate的持久化类的编写:

* 什么是持久化类:

* 持久化类:Java类与数据库的表建立了映射关系.

* 持久化类编写规则:

* 无参数的构造方法:

* 属性get和set

* 属性尽量使用包装类:

* 提供唯一标识OID.

* 类不要使用final修饰.

* 自然主键和代理主键.

* Hibernate中的主键生成策略:

* increment:

* identity

* sequence

* native

* uuid

* assigned

* foreign

1.2 Hibernate持久化对象的状态

1.2.1 持久化类的三种状态:

持久化类:就是一个Java类与表建立了映射关系.这种Java类就称为是持久化类.

Hibernate为了更好的管理持久化类,将持久化类分成了三种状态.

瞬时态:没有持久化标识OID,对象没有被session管理.

持久态:有持久化标识OID.对象也被session管理.(重点)

脱管态:有持久化标识OID.但对象没有被session管理.

***** 持久化类的持久态的对象可以自动更新数据库.

@Test
/**
* 区分持久化类的三种状态
*/
publicvoid demo1(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 保存一条记录:
User user = new User();// 瞬时态: 没有持久化标识OID,没有被session管理.
user.setUsername("aaa");
user.setPassword("123");
session.save(user); // 持久态: 有持久化标识OID,被session管理.
tx.commit();
session.close();
System.out.println(user.getUsername());// 脱管态: 有持久化标识OID.没有被session管理.
}
@Test
/**
* 证明持久化类的持久态对象可以自动更新数据库
*/
publicvoiddemo2(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 查询一条记录:
User user = (User) session.get(User.class, 1);// 持久态:有标识OID,被session管理.
user.setUsername("ccc");
// session.update(user);
tx.commit();
session.close();
}

1.2.2 Hibernate持久化对象的状态转换:

瞬时态:

* 获得:

* new 对象.

* 转换:

* 瞬时à持久:

* save()/saveOrUpdate();

* 瞬时à脱管:

* User user = new User();

* user.setId(1);

持久态:

* 获得:

* get/load/iterate/find

* 持久à脱管:

* session.close()/session.clear()/session.evict();

* 持久à瞬时:

* delete()

脱管态:

* 获得:

* User user = new User();

* user.setId(1);

* 转换:

* 脱管à持久:

* update/saveOrUpdate/lock

* 脱管à瞬时:

* user.setId(null);

1.3 Hibernate的一级缓存:

1.3.1 什么是缓存:

缓存是计算机领域经常会使用的一个概念.是介于数据源(数据库/文件)与程序之间的.就是内存中的一块空间.查询数据的时候将查询到数据放入到缓存中.当再次获得这个数据的时候,那么直接从缓存中获取.提升性能.

1.3.2 Hibernate中的缓存

Hibernate中提供了两个级别的缓存:

一级缓存:称为session级别缓存.一级缓存的生命周期与session一致.自带的不可卸载的.

二级缓存:称为sessionFactory基本的缓存.可以在多个session中共享数据.默认不开启二级缓存.需要手动配置的.

1.3.3 Hibernate的一级缓存:

在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期

当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图get()、 load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库

@Test
/**
* 证明Hibernate的一级缓存的存在
*/
publicvoid demo1(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
/**
* 先save再get
*/
/*User user = new User();
user.setUsername("bbb");
user.setPassword("123");
Serializable id = session.save(user); // 持久态
User user2 = (User) session.get(User.class, id); // 不发送SQL语句
System.out.println(user2);*/
/**
* 先后执行两次get方法
*/
User user1 = (User) session.get(User.class, 1); // 发送一条SQL语句
User user2 = (User) session.get(User.class, 1); // 不发送SQL语句
System.out.println(user1 );
System.out.println(user2 );
tx.commit();
session.close();
}

1.3.4 Hibernate的内部结构(持久态对象能自动更新数据库原理)

@Test
/**
* 持久态对象能够自动更新数据库(依赖了一级缓存的快照区)
*/
publicvoid demo2(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
User user = (User) session.get(User.class, 1); // 持久态
user.setUsername("fff");
tx.commit();
session.close();
}

1.3.5 Hibernate一级缓存的常用操作

flush:刷出缓存(发送更新语句时机)

@Test
/**
* 一级缓存的管理的方法:flush.刷出缓存
*/
publicvoid demo3(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
User user = (User) session.get(User.class, 1); // 持久态
user.setUsername("eee");
session.flush(); // 刷出缓存
tx.commit();
session.close();
}

clear:清空所有一级缓存中的对象.

@Test
/**
* 一级缓存的管理的方法:clear.清空一级缓存
*/
publicvoid demo4(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
User user1 = (User) session.get(User.class, 1); // 持久态 发送SQL语句
session.clear();
User user2 = (User) session.get(User.class, 1); // 持久态 发送SQL语句
System.out.println(user1);
System.out.println(user2);
tx.commit();
session.close();
}

evict:清空一级缓存中的某个对象.

@Test
/**
* 一级缓存的管理的方法:evict.清空一级缓存中的某个对象
*/
publicvoiddemo5(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
User user1 = (User) session.get(User.class, 1); // 持久态 发送SQL语句
session.evict(user1);
User user2 = (User) session.get(User.class, 1); // 持久态 发送SQL语句
System.out.println(user1);
System.out.println(user2);
tx.commit();
session.close();
}

refresh:将快照区数据重新覆盖了缓存区数据.

@Test
/**
* 一级缓存的管理的方法:refresh.将数据库中数据覆盖掉一级缓存的数据
*/
publicvoiddemo6(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
User user1 = (User) session.get(User.class, 1); // 持久态 发送SQL语句
user1.setUsername("mmm");
session.refresh(user1);
tx.commit();
session.close();
}

1.3.6 Hibernate的一级缓存的刷出时机(了解)

ALWAYS :任何查询操作,事务提交的时候,手动调用flush时候都会刷出.

AUTO :有些查询操作,事务提交的时候,手动调用flush的时候会刷出.(默认)

COMMIT :事务提交或者手动调用flush的时候.

MANUAL :必须手动调用flush刷出

1.4 操作持久化对象的方法

1.4.1 保存的方法:save

save方法:将瞬时态转出持久态对象,而且向一级缓存中存放数据.

1.4.2 查询方法:get/load

get/load方法:可以直接获得持久态对象,而且都可以向一级缓存中存放数据.

1.4.3 修改方法:update

update:可以将脱管态对象转成持久态对象.

在<class>上配置一个select-before-update:在更新之前查询.

1.4.4 保存或更新:saveOrUpdate

saveOrUpate:如果对象是瞬时的采用save方法.如果对象是脱管的执行update.

@Test
/**
* saveOrUpdate方法:
*/
publicvoid demo2(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
/*User user = new User();// 瞬时
user.setUsername("yyy");
user.setPassword("yyy");
session.saveOrUpdate(user);// 执行save
*/
User user = new User();// 瞬时
user.setId(4); // 脱管
user.setUsername("xxx");
user.setPassword("yyy");
session.saveOrUpdate(user); // 执行update
tx.commit();
session.close();
}

***** merge方法和saveOrUpdate大致类似.如果对象是瞬时执行save.如果是脱管执行update.将内存中出现的OID相同的对象进行合并.

1.4.5 删除方法:

将持久态对象转成瞬时.

1.5 Hibernate的关联关系的映射.(*****)

1.5.1 表之间的关系:

一对多 :

* 建表原则:在多的一方创建一个字段,作为外键指向一的一方的主键.

多对多 :

* 建表原则:创建一个中间表,中间表至少两个字段.两个字段分别作为外键指向多对多双方的主键.

一对一

* 建表原则:

* 唯一外键对应:假设一对一是一个一对多的关系.在多的一方创建外键指向一的一方的主键.将外键设置为unique.

* 主键对应:一个表的主键从另一个表的主键获得.

1.5.2 Hibernate完成一对多的关联关系的配置(客户和订单的案例)

步骤一:创建客户和订单的实体:

步骤二:创建客户和订单的映射文件:

Order.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.hibernate.demo2.Order"table="orders">
<id name="oid"column="oid">
<generator class="native"/>
</id>
<property name="address"column="address"length="100"/>
<!--
在多的一方添加一个标签 many-to-one
name :多的一方存放的一的一方的对象的名称.
class :customer对象的全路径
column :外键名称
-->
<many-to-one name="customer" class="cn.itcast.hibernate.demo2.Customer" column="cno"/>
</class>
</hibernate-mapping>
Customer.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.hibernate.demo2.Customer"table="customer">
<id name="cid"column="cid">
<generator class="native"/>
</id>
<property name="cname"column="cname"length="20"/>
<property name="age"column="age"/>
<!-- 一个客户中有多个订单的.配置的是多个订单的集合 -->
<set name="orders">
<!-- key:外键的名称 -->
<key column="cno"/>
<!-- 配置one-to-many -->
<one-to-many class="cn.itcast.hibernate.demo2.Order"/>
</set>
</class>
</hibernate-mapping>

步骤三:保存数据:

@Test
/**
* 插入两个客户和3个订单
*/
publicvoiddemo1(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = new Customer();
customer1.setCname("老马");
customer1.setAge(38);
Customer customer2 = new Customer();
customer2.setCname("凤姐");
customer2.setAge(38);
Order order1 = new Order();
order1.setAddress("北京市昌平区中腾建华大厦");
Order order2 = new Order();
order2.setAddress("北京市昌平区金燕龙办公楼");
Order order3 = new Order();
order3.setAddress("北京市昌平区四拨子");
// 建立客户和订单的关系:
customer1.getOrders().add(order1);
customer1.getOrders().add(order2);
customer2.getOrders().add(order3);
order1.setCustomer(customer1);
order2.setCustomer(customer1);
order3.setCustomer(customer2);
session.save(customer1);
session.save(customer2);
session.save(order1);
session.save(order2);
session.save(order3);
tx.commit();
session.close();
}

1.5.3 Hibernate中的一对多级联操作:

级联保存:

保存某一个对象的同时,级联关联对象.

级联是有方向性.

* 保存客户的时候,级联订单.

@Test
/**
* 级联保存:
* 保存客户,级联订单
* 在Customer.hbm.xml中完成一个配置:<set>上配置cascade="save-update"
*/
publicvoid demo3(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = new Customer(); // 瞬时
customer1.setCname("老马");
customer1.setAge(38);
Order order1 = new Order(); // 瞬时
order1.setAddress("北京市昌平区中腾建华大厦");
// 建立客户和订单的关系:
customer1.getOrders().add(order1);
order1.setCustomer(customer1);
session.save(customer1); // 持久态.
tx.commit();
session.close();
}
* 保存订单的时候,级联客户.
@Test
/**
* 级联保存:
* 保存订单,级联客户
* 在订单Order.hbm.xml中配置<many-to-one>配置cascade="save-update"
*/
publicvoid demo4(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = new Customer(); // 瞬时
customer1.setCname("老马");
customer1.setAge(38);
Order order1 = new Order(); // 瞬时
order1.setAddress("北京市昌平区中腾建华大厦");
// 建立客户和订单的关系:
customer1.getOrders().add(order1);
order1.setCustomer(customer1);
session.save(order1); // 持久态.
tx.commit();
session.close();
}

测试对象的导航:

@Test
/**
* 测试对象的导航.
* 一对多的双方都配置了cascade="save-update"
*/
publicvoid demo5(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer1 = new Customer(); // 瞬时
customer1.setCname("凤姐");
customer1.setAge(38);
Order order1 = new Order(); // 瞬时
order1.setAddress("北京市昌平区中腾建华大厦");
Order order2 = new Order(); // 瞬时
order2.setAddress("北京市昌平区金燕龙");
Order order3 = new Order(); // 瞬时
order3.setAddress("北京市昌平区四拨子");
// 建立客户和订单的关系:
order1.setCustomer(customer1);
customer1.getOrders().add(order2);
customer1.getOrders().add(order3);
// session.save(order1); // 发送几条insert语句. 4条
// session.save(customer1); // 发送几条insert语句. 3条.
session.save(order2); // 发送几条insert语句. 1条
tx.commit();
session.close();
}

级联删除

删除某个对象的时候,将关联的对象一并删除.

级联删除也是有方向性的.

* 删除客户的时候,级联删除订单.

@Test
/**
* 级联删除:
* * 删除客户,级联订单
* * 在Customer.hbm.xml中配置<set>上cascade="delete"
*/
publicvoid demo7(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
/**
* 级联删除,一定要先查询再删除
*/
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
tx.commit();
session.close();
}
* 删除订单的时候,级联删除客户.
@Test
/**
* 级联删除:
* * 删除订单,级联客户
* * 在Order.hbm.xml中配置<many-to-one>上cascade="delete"
*/
publicvoid demo8(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
/**
* 级联删除,一定要先查询再删除
*/
Order order = (Order) session.get(Order.class, 3);
session.delete(order);
tx.commit();
session.close();
}

级联的取值:

* none :没有级联

* save-update :级联保存或更新.

* delete :级联删除

* all :所有但是除了delete-orphan

* delete-orphan :孤儿删除.(孤子删除)

* all-delete-orphan :所有的包含的孤儿删除.

孤儿删除:只能在一对多的情况下使用.认为一对多的一的一方是父方.多的一方子方.

* 孤儿删除指的是删除子的一方没有外键值得那些数据.

孤儿删除:

@Test
/**
* 孤儿删除
*/
publicvoid demo9(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 3);
Order order = (Order) session.get(Order.class, 4);
customer.getOrders().remove(order);
tx.commit();
session.close();
}

1.5.4 Hibernate一对多的inverse的配置:

如果没有配置inverse出现哪些问题?

* 正常的情况下一对多的双方都有能力去维护外键.造成SQL冗余.(发送多余的SQL去维护外键).配置inverse的一方就放弃外键的维护权.

双向维护产生多余SQL

@Test
/**
* 双向维护产生多余的SQL:
* 在一的一方配置外键维护权:inverse="true"
*/
publicvoiddemo10(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 2);
Order order = (Order) session.get(Order.class, 2);
customer.getOrders().add(order);
order.setCustomer(customer);
tx.commit();
session.close();
}

配置cascade和inverse的效果:

@Test
/**
* 测试cascade和inverse.
* 在Customer.hbm.xml中配置cascade="save-update" inverse="true"
*/
publicvoid demo11(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCname("马如花");
customer.setAge(40);
Order order = new Order();
order.setAddress("北京市昌平区朱辛庄");
customer.getOrders().add(order);
session.save(customer);
// 客户 和 订单是否能被保存到数据库,最终效果是如何的?
// cascade:关联对象是否一并保存到数据库.
// inverse:外键是否有值.
tx.commit();
session.close();
}

1.5.5 Hibernate完成多对多的配置:

步骤一:创建实体

步骤二:创建映射文件:

Student.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.hibernate.demo3.Student"table="student">
<id name="sid"column="sid">
<generator class="native"/>
</id>
<property name="sname"column="sname"length="20"/>
<!-- 关联关系的配置 -->
<set name="courses" table="stu_cour">
<!-- key中的column代表的是当前类在中间表的外键的名称 -->
<key column="sno"/>
<many-to-many class="cn.itcast.hibernate.demo3.Course" column="cno"/>
</set>
</class>
</hibernate-mapping>
Course.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.hibernate.demo3.Course"table="course">
<id name="cid"column="cid">
<generator class="native"/>
</id>
<property name="cname"column="cname"length="20"/>
<!-- 关联关系的配置 -->
<set name="students" table="stu_cour">
<key column="cno"/>
<many-to-many class="cn.itcast.hibernate.demo3.Student" column="sno"/>
</set>
</class>
</hibernate-mapping>

1.5.6 基本的保存:

1.5.7 级联操作:

级联保存

@Test
/**
* 插入数据:级联保存:
* * 保存学生级联课程
*/
publicvoid demo2(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 创建两个学生
Student student1 = new Student();
student1.setSname("小马");
// 创建三门课程
Course course1 = new Course();
course1.setCname("C++");
// 设置关系:(双向维护:多对多双向维护,出错. 必须有一方放弃外键维护权.)
student1.getCourses().add(course1);
course1.getStudents().add(student1);
session.save(student1);
tx.commit();
session.close();
}
@Test
/**
* 插入数据:级联保存:
* * 保存课程级联学生
*/
publicvoiddemo3(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 创建两个学生
Student student1 = new Student();
student1.setSname("小马");
// 创建三门课程
Course course1 = new Course();
course1.setCname("C++");
student1.getCourses().add(course1);
course1.getStudents().add(student1);
session.save(course1);
tx.commit();
session.close();
}

级联删除:

@Test
/**
* 级联删除:
* * 删除学生级联课程.
*/
publicvoid demo4(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Student student = (Student) session.get(Student.class, 1);
session.delete(student);
tx.commit();
session.close();
}
@Test
/**
* 级联删除:
* * 删除课程级联学生.
*/
publicvoid demo5(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Course course = (Course) session.get(Course.class, 2);
session.delete(course);
tx.commit();
session.close();
}

1.5.8 学生选课的操作:

@Test
/**
* 让1号学生选择3号课程
*/
publicvoid demo6(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Student student = (Student) session.get(Student.class, 1);
Course course = (Course) session.get(Course.class, 3);
student.getCourses().add(course);
tx.commit();
session.close();
}
@Test
/**
* 让2号学生选择退选2号课改选3号课
*/
publicvoid demo7(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Student student = (Student) session.get(Student.class, 2);
Course course2 = (Course) session.get(Course.class, 2);
Course course3 = (Course) session.get(Course.class, 3);
student.getCourses().remove(course2);
student.getCourses().add(course3);
tx.commit();
session.close();
}

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2017-02-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

一步一步学Linq to sql(三):增删查改

  今天主要是来学习一下,通过Linq如何进行数据库的操作,增加、删除、修改。准备工作,先是建立了一个Asp.Net Mvc 3.0的Web项目,

12220
来自专栏跟着阿笨一起玩NET

[C#] 常用工具类——文件操作类

44030
来自专栏.NET开发者社区

一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](四)

上一篇《一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](三)》,我们完成了:

23190
来自专栏菩提树下的杨过

Sqlite快速上手使用指南

这是网上收集的几篇教程 1. Sqlite简明教程 http://www.sqlite.com.cn/MySqlite/4/32.Html 2. Sqlite入...

25590
来自专栏程序员的SOD蜜

常见.NET功能代码汇总

1,在Web上修改指定文件位置的Web.config 这里需要使用 WebConfigurationManager 类,但必须使用WebConfiguratio...

459100
来自专栏木宛城主

ASP.NET那点不为人知的事(三)

有了以下的知识: ASP.NET那点不为人知的事(一) ASP.NET那点不为人知的事(二) 想必开发一个小型服务器以不是问题了,功能补复杂,能够响应...

21790
来自专栏Golang语言社区

关于JSON.stringify和Unicode编码,需要注意的几点

1JSON.stringify会自动把所要转换内容中的汉字转换为Unicode编码 2浏览器间有差别,个别浏览器会把将要提交表单内容中的Unicode编码自动转...

42380
来自专栏智能大石头

XCode读取Excel数据(适用于任何数据库)

虽然是充血模型,虽然是强类型,XCode同样支持遍历任何数据库结构,并以强类型(相对于DataSet等字典访问)方式读取数据。 要遍历数据库结构是很容易的事情,...

23180
来自专栏逸鹏说道

EF批量操作数据与缓存扩展框架

在原生的EF框架中,针对批量数据操作的接口有限,EF扩展框架弥补了EF在批量操作时的接口,这些批量操作包括:批量修改、批量查询、批量删除和数据缓存,如果您想在E...

48460
来自专栏GuZhenYin

ASP.NET Core文件上传与下载(多种上传方式)

前言 前段时间项目上线,实在太忙,最近终于开始可以研究研究ASP.NET Core了. 打算写个系列,但是还没想好目录,今天先来一篇,后面在整理吧. ASP.N...

64360

扫码关注云+社区

领取腾讯云代金券