hibernate基础

ORM(Object/Relationship Mapping):对象/关系映射      写SQL语句的缺点:
1.不同的数据库使用的SQL语法不同。比如:PL/SQL(Oracle)与T/SQL(SQL Server)
2.同样的功能在不同的数据库中有不同的实现方式。比如分页SQL。
3.程序过分依赖SQL对程序的移植及扩展、维护等带来很大的麻烦。
为了完全使用面向对象思想开发软件,需要采用一种可行ORM框架技术。
使用ORM框架技术可以使程序员彻底抛弃书写SQL的思想,完全使用面向对象的思想开发软件。Hibernate正是这样一种技术。
1.Hibernate是java领域的一个开源的ORM框架 
2.Hibernate是对JDBC进行了非常轻量级的封装,实质上还是通过JDBC实现对数据库的操作 3.Hibernate在程序中的作用:持久化层,把程序中生成的对象 持久化到数据库的表中
其他主流的ORM框架技术1.MyBatis:前身就是iBatis(Apache)2.Toplink:后被Oracle收购,并重新包装为Oracle AS TopLink3.EJB:本身是JAVAEE的规范(重量级)
开发工具:Eclipse Standard Kepler
插件:Hibernate Tools for Eclipse Plugins
是由JBoss推出的一个EClipse综合开发插件,该插件可以简化ORM框架Hibernate 以及JBoss Searm EJB3等的开发工作。
如何安装Hibernatetools插件:
点击help--install new。。--找到安装包--安装完重启
出现Hibernate文件夹即表示安装成功或者
help—>Install New SoftWare_->粘贴【http://download.jboss.org/jbosstools/neon/stable/updates/】->点击add然后等...->选择Jboss Web and Java EE Development目录下的Hibernate Tools

创建第一个Hibernate例子   
1.导入jar包(hibernate的jar包,mysql的jar包,junit4的jar包)2.创建Hibernate配置文件 3.创建持久化类 4.创建对象-关系映射文件 4.通过Hibernate API 编写访问数据库的代码
使用版本:Hibernate 4.2.4+MySQL 6.0
第一个Hibernate例子-创建Hibernate工程
1)新建Java Project
2)自定义jar包(自定义并导入):windows-Preference-Java -Build Path -User Libraries_:
 New1(hibernate-core(命名hibernate-core) _ Add External JARs)
 New2(Junit4 _ Add External JARs:unit-4.10.jar)
 New3(mysql-jdec _ Add External JARs:mysql-connector-java-5.1.7-bin.jar)
3) 在工程中导入:
project-Properties-Build Path -Libraries:
 Add Library-User Library:
  勾选2)创建的三个包_finish_OK
hibernate.cfg.xml
   <session-factory>
    	<property name="connection.username">root</property>
    	<property name="connection.password">1149754829</property>
    	<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
    	<property name="connection.url">jdbc:mysql://hibernateuseUnicode=true&amp;characterEncoding=UTF-8</property>
     	<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
     	
     	<property name="show_sql">true</property>
		<property name="format_sql">true</property>
		<property name="hbm2ddl.auto">create</property>
    </session-factory>
构造持久类的原则:
javabean的四点要求1.公有的类2.提供公有的不带参数的构造方法3.属性私有4.属性setter/getter封装
创建完数据持久类(实体类)后需要新建对象关系映射文件:
新建对象关系映射文件Student.hbm.xml
New -> other - > Hibernate -> Hibernate XML Mapping file (hbm.xml) 选择需要映射的刚创建的实体类,会将创建的字段和数据库字段进行映射。
创建完对象关系映射文件后
 需要在cfg.xml中配置文件声明:如<mapping resource="Student.hbm.xml" ></mapping>
配置后将在初始化 Hibernate 环境时将装载User.xml 映射信息。
Hibernate5.2版本:
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
<mapping resource="Student.hbm.xml" />
junit有两个jar包:junit和hamcrest-core
Students.hbm.xml 具体如下:
对象关系映射文件Student.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
	<class name="com.szh.model.Student" table="students">
		<id name="id" type="int">
			<column name="id" />
			<generator class="assigned" />
		</id>
		<property name="name" type="java.lang.String">
			<column name="name" />
		</property>
		<property name="gender" type="java.lang.String">
			<column name="gender" />
		</property>
		<property name="birthday" type="java.util.Date">
			<column name="birthday" />
		</property>
		<property name="address" type="java.lang.String">
			<column name="address" />
		</property>
	</class>
</hibernate-mapping>
使用Junit进行测试
要使用到的标签:
@Test:要测试的方法。
@Before:初始化方法(表示在测试前会先执行before方法进行初始化)。
@After:释放资源。
执行次序:@before标签下的方法——>@test方法——>@after释放资源
@Before
  public void init() {
    // 创建配置对象   
    Configuration config = new Configuration().configure();
    config.addClass(Student.class);
    // 创建服务注册对象
    ServiceRegistry serviceRegistery = new StandardServiceRegistryBuilder().applySettings(config.getProperties()).build();
    // 创建会话工厂对象
    sessionFactory = config.buildSessionFactory(serviceRegistery);
    // 创建会话对象
    session = sessionFactory.openSession();
    // 开启事务
    transaction = session.beginTransaction();
    
    
  }

  @Test
  public void testSaveStudent() {
    Student s = new Student(1,"张三丰","男",new Date(),"武当山");
    session.save(s);
  }

  @After
  public void destory() {
    // 提交事务
    transaction.commit();
    // 关闭会话
    session.close();
    // 关闭会话工厂
    sessionFactory.close();
  }
hibernate4.3.x版本中 ServiceRegistryBuilder 已过时--"Deprecated.  Use StandardServiceRegistryBuilder instead",得用StandardServiceRegistryBuilder,也就是它的父类代替。太开玩乐了,儿子废了,老爸顶上!
服务注册对象 这个在博客留言中我已经详细说明,调试不通可以查看hibernate博客留言
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
hibernate配置文件的常用配置
hibernate.show_sql:是否把hibernate运行时的SQL语句输出到控制台,编码阶段便于测试。
hibernate.format_sql:输出到控制台的SQL语句是否进行排版,便于阅读。建议设TRUE。
hbm2ddl.auto:表结构生成策略。可帮助由java代码生成数据库脚本,进而生成具体的表结构。
	create(表结构存在,先删除,再重新创建)|update(在原有表结构中插入)|create-drop(先创建再删除)|validate(验证表结构,如现在结构与原结构不同,则不会创建表结构)
hibernate.default_schema:默认的数据库。执行SQL时,默认给所有表名加上数据库前缀
hibernate.dialect:配置hibernate数据库方言,hibernate可针对特殊数据库进行优化。
hbm2ddl.auto:1.create表示每次生成新的数据表,再对数据操作2.update表示在原有旧的数据表上执行操作3.create-drop表示先创建后删除(不常用)4.validate:用现有的表结构和原来的表结构进行验证,如果不同就不创建hibernate.default_schema:给数据库表加上一个表前缀

关于session的说明:
1.不建议直接使用jdbc的connection操作数据库,而是通过使用session操作数据库;
2.session可以理解为操作数据库的对象;
3.session与connection,是多对一的关系,每个session都有一个与之对应的connection,一个connection不同时刻可以供多个session使用;
4.把对象保存到关系型数据库中需要调用session的各种方法,如:save(),update(),delete(),createQuery()等。
1.Configuration对象:配置文件对象,读取hibernate配置文件xxx.cfg.xml
2.SessionFactory对象:读取对象/关系映射文件 xxx.hbm.xml
3.session对象:数据库链接对象,获得之后可以操作数据库。可以理解为操作数据库的对象
4.Transaction:使用session操作数据库需要开启的事务
transaction简介:事务
·hibernate对数据的操作都是封装在事务当中,并且默认是非自动提交的方式。
 所以用session保存对象时,如果不开启事务,并且手工提交事务,对象并不会真正保存在数据库中。
·如果想让hibernate想jdbc那样自动提交事务,必须调用session对象的doWork()方法,活得jdbc的connection后,设置其为自动提交事务模式。(注意:通常并不推荐这样做)
--自动提交事务(注意:通常并不推荐这样做)---
//不开启事务(transaction.commit()//提交事务)的方式
 @Test
 public void testSaveStudents(){
  Stusdents s= new Students(1,"ZSF",new Date(),"wudang");
  session.doWork(new Work(){
	@Override
	public void execute(Connection connection) throws SQLException{
	  connection.setAutoCommit(true);
	}
  })

  session.save(s);//保存对象进入数据库
  session.flush();//强制发出SQL语句(通过SQL写入SQL)
 }
---自动提交事务(注意:通常并不推荐这样做)---

private Transaction transaction;

transaction  = session.beginTransaction();//打开事务
transaction.commit()//提交事务

需要注意的是:在使用save()方法后并不会真正输出sql语句,需要调用flush()强制输出sql语句才可以。然后因为采用了自动提交方式(setAutoCommit(true)),数据才真正保存在数据库。

如何获得session对象 ,调用SessionFactory对象的方法:
(1)openSessionion
(2)getCurrentSession
如果使用getCurrentSession需要在hibernate.cfg.xml文件中进行配置:
如果是本地事务(jdbc事务)
<property name="hibernate.current_session_context_class">thread</property>
如果是全局事务(jta事务)
<property name="hibernate.current_session_context_class">jta</property>
openSession 每次使用都是打开一个新的session,使用完需要调用close方法关闭session;
getCurrentSession 是获取当前session对象,连续使用多次时,得到的session都是同一个对象,这就是与openSession的区别之一 
一般在实际开发中,往往使用getCurrentSession多,因为一般是处理同一个事务,所以在一般情况下比较少使用openSession;
前者相当于多例,后者相当于单例。前者创建的session对象要手动关闭,不关闭的话,再次创建session时,就会创建新的session,两个session的hashCode不同;后者创建的session会在事务提交后自动关闭,不需要手动关闭,再次创建的session与已关闭的session是一致的,hashCode相同。
如果你没有设置数据库连接池,那么初始连接池的大小是20,最小是1,也就是说当你启动hibernate的时候,hibernate就初始化了一个connection对象放在你的数据库连接池里面了。如果你第一次调用openSession的时候,hibernate直接就把连接池里面的connection对象给你了,但是如果你没有关闭session,那么这个connection对象就没有被释放,所以当你再次调用openSession的时候,hibernate就会创建一个新的connection对象,如果一直这样,连接池就溢出了
1,事务:事务表示一个由一系列的数据库操作组成的不可分割的逻辑单位,其中的操作要么全做要么全都不做2,JDBC事务JDBC事务由Connnection对象控制管理,也就是说,事务管理实际上是在JDBC Connection中实现。事务周期限于Connection的生命周期。3,JTA事务提供了跨数据库连接(或其他JTA资源)的事务管理能力
hbm配置文件常用设置
hbm.xml配置文档

<hibernate-mapping<br>
	schema="schemaName" //模式的名字
	catalog="catalogName" //目录的名称
	default-cascade="cassade_style" //级联风格
	default-access="field/property/CalssName" //访问策略
	default-lazy="true/false" //加载策略
	package="packagename" //默认包名
/>

<class<br>
	name="ClassName" //对应映射的类<br>
	table="tableName" //对应映射数据库的表<br>
	batch-size="N" //抓取策略,一次抓取多少记录<br>
	where="condition" //条件 eg:抓取条件<br>
	entity-name="EntiyName" //如果需要映射多张表<br>
/>
//表的主键
<id 
	name="propertyName" //对应的属性
	type="typeName" //对应类型
	column="column_nam" //映射数据库中表当中字段名的名称
	length="length" //指定长度
	<generator class="generatorClass"/>//主键生成策略
</id> 
主键生成策略:
由于使用MYSQL,着重讲解一下两个
native:有底层数据库自动生成标识符
assigned:手工赋值
hibernate表单操作的内容
1、单一主键;2、基本类型;3、对象类型;4、组件属性;5、表单操作CRUD实例
单一主键:指表中由某一列来充当主键
assigned 由java应用程序负责生成(手工赋值),主键手动赋值
native 由底层数据库自动生成标识符,如果是MySQL就是increment,如果是Oracle就是sequence,等等。另外,即使手动赋值主键,也不会起作用。主键自动增长,手工赋值不起作用
<generator class="native" />mysql 中为AUTO_INCREMENT PRIMARY KEY
assigned 当为int时 默认0 其他默认null
hibernate映射类型、java数据类型、sql数据类型的对应关系
2.时间类型
在sql中用DATE表示日期,TIME表示时间,TIMESTAMP表示日期+时间
而在java中用java.util.Date即可代表日期、时间或日期+时间
hibernate映射类型date、time、timestamp分别对应sql的DATE、TIME、TIMESTAMP
hibernate映射类型date、time、timestamp对应java的java.util.Date
因此,type属性的取值使用hibernate映射类型能做到更精确的控制,使用java类型java.util.Date输出为日期+时间
date 表示日期:YYYY-MM-ddtime 表示时间:hh:mm:ss tiemstamp时间戳: yyyy-MM-dd hh:mm:ss
另外,映射的数据类型设置会决定最后数据库中的类型。比如我们在Java程序中使用的是java.util.Date类型定义变量Birthday。但在映射关系的配置文档中对应的type改成date类型,那么最后保存在数据库的Birthday类型就是date,也就是YYYY-MM-dd,而非java.util.Date(或者timestamp)的YYYY-MM-dd hh:mm:ss。
CLOB类型:大文本类型;
BLOB:大的二进制文件类型(如音频、视频、图片等)
MySQL不支持变准SQL的CLOB类型,在MySQL智能光,用TEXT,MEDIUMTEXT以及LONGTEXT类型表示长度唱过255字节的长文本数据。
separatorChar
public static final char separatorChar
与系统有关的默认名称分隔符。此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 ‘/’;在 Microsoft Windows 系统上,它为 ‘\’。
separator
public static final String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar。
pathSeparatorChar
public static final char pathSeparatorChar
与系统有关的路径分隔符。此字段被初始为包含系统属性 path.separator 值的第一个字符。此字符用于分隔以路径列表 形式给定的文件序列中的文件名。在 UNIX 系统上,此字段为 ‘:’;在 Microsoft Windows 系统上,它为 ‘;’。
pathSeparator
public static final String pathSeparator
与系统有关的路径分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 pathSeparatorChar。
blob例子 对象类型
	public void saveImage() throws Exception{
		Student s=new Student(1,"李四","男",new Date(),"北京");
		File f=new File("d:"+File.separator+"boy.jpg");
		InputStream input=new FileInputStream(f);
		Blob image=Hibernate.getLobCreator(session).createBlob(input, input.available());
		s.setImage(image);
		session.save(s);
		
	}
	
	public void readImage() throws Exception{
		Student s=(Student) session.get(Student.class, 1);
		Blob image=s.getImage();
		InputStream input=image.getBinaryStream();
		File f=new File("d:"+File.separator+"girl.jpg");
		OutputStream output=new FileOutputStream(f);
		byte[] buff=new byte[input.available()];
		input.read(buff);
		output.write(buff);
		output.close();
		input.close();
	}
组件属性:某个类对象的成员属性是一个自定义类型对象。
存储在数据库中的效果和单一字段一样
1. 使用Hibernate Annotation来做对象关系映射
1) 添加必须包:hibernate-jpa-2.0-api-1.0.0.Final.jar
2) 在实体类中添加JPA的标准注解来进行对象关系映射.注解可以添加在属性上,也可以添加在getXxx()方法之上。
   a) @Entity 映射一个实体类 @Table 指定关联的表
   b) @Id 映射OID
   c) @GeneratedValue 指定OID的生成策略
   d) @Version 映射版本号属性
   e) @Column 指定属性对应的列的信息
   f) @Temporal 指定日期时间的类型(TIMESTAMP,DATE,TIME)
   g) 简单属性可以不用注解。默认就是@Basic
   h) @Transient 指定属性不需要映射
   i) 复杂属性:关联,继承,组件,联合主键,集合   
3) 在Hibernate全局配置文件中使用声明映射类的方式:
   <mapping class="实体类的全限定名"/>
4) 使用Annotation来映射对象关系时,加载Hibernate全局配置要使用AnnotationConfiguration类
5) 持久化操作与之前没有区别。
2. Hibernate Annotation 基本映射
3. 映射多对一
 1) @ManyToOne
 2) 指定关联列@JoinColumn(name="xxx_id")
4. 映射一对多
 1) @OneToMany  默认会使用连接表做一对多的关联
 2) 添加@JoinColumn(name="xxx_id")后,就会使用外键关联,而不使用连接表了。
5. 映射双向一对多
 1) 在多端:
    @ManyToOne
 2) 在一端:
    @OneToMany(mappedBy="多端的关联属性名"):----升级后-->  @OneToMany
    @JoinColumn(name="外键名")
6. cascade属性:指定级联操作的行为(可多选)
  CascadeType.PERSIST :调用JPA规范中的persist(),不适用于Hibernate的save()方法
  CascadeType.MERGE:调用JPA规范中merge()时,不适用于Hibernate的update()方法
 CascadeType.REMOVE:调用JPA规范中的remove()时,适用于Hibernate的delete()方法
 CascadeType.REFRESH:调用JPA规范中的refresh()时,适用于Hibernate的flush()方法
 CascadeType.ALL:JPA规范中的所有持久化方法。
7. mappedBy属性:用在双向关联中,把关系的维护权反转
  跟hibernate XML映射中的property-ref一样。
8. cascade属性和mappedBy用在一起时,一定要通过调用双方的set方法来建立关系。
10. 双向一对一
 1) 基于外键
   a) 在主控方:@OneToOne
   b) 在被控方:@OneToOne(mappedBy="对方的关联属性名")
 2) 基于主键: JPA标准中没有提供共享主键生成问题的标准方法,使用Hibernate的扩展
   a) 在主控方:Car
 @Id
 @GeneratedValue(generator="my-uuid")
 @GenericGenerator(name="my-uuid", strategy="uuid")
 private String id;
 @OneToOne(cascade={CascadeType.ALL})
 @PrimaryKeyJoinColumn
 private Brand brand;
b) 在被控方:Brand
 @Id
 @GeneratedValue(generator="myFG")
 @GenericGenerator(name="myFG", strategy="foreign",parameters=@Parameter(name="property",value="car"))
 private String id; 
 @OneToOne(mappedBy="brand")
 private Car car;
11. 双向多对多
 1. 在主控方:
   //从学生到课程的多对多: 最好由某一端来维护这个关系会更有效率
@ManyToMany
@JoinTable(name="student_course",
joinColumns={@JoinColumn(name="student_id")},
inverseJoinColumns={@JoinColumn(name="course_id")})
private Set<Course> courseSet = new HashSet<Course>();
 2. 在被控方:
  //课程到学生的多对多
@ManyToMany(mappedBy="courseSet")
private Set<Student> stus = new HashSet<Student>();
12. 把双向多对多拆成两个一对多: 1-->*<--1

13. 继承映射:
1. 整个继承树一张表
在父类中添加从下注解
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="type",length=3)
@DiscriminatorValue("u")

子类中添加以下注解
@Entity
@DiscriminatorValue("w")

2. 每个子类一张表
在父类添加如下注解
@Entity
@Table(name="user")
@Inheritance(strategy=InheritanceType.JOINED)
在子类中跟普通实体类的映射相同

3. 每个具体类一张表
在父类中
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class User {
@Id
@GeneratedValue(strategy=GenerationType.TABLE,generator="xxGen")
@TableGenerator(name="xxGen",allocationSize=1)
private Long id;
...
}
在子类中跟普通实体类的映射相同
14. 组件映射
 在组件类中用@Emabbedable
在使用这个组件类中用
@Emabbed
@AttributeOverrides({
@AttributeOverride(name="email", column=@Column(name="p_email")),
@AttributeOverride(name="address", column=@Column(name="p_address")),
@AttributeOverride(name="mobile", column=@Column(name="p_mobile"))
})

15. 联合主键映射
 1. 主键类:用@Emabbedable映射。并实现Serializable接口,使用主键属性重写hashCode()和equals()方法。
 2. 使用这个主键类的类中。用@Id映射。

组件属性:实体类中的某个属性属于用户自定义的类的对象    
作用:将两个实体类合并在一起组建成一个表
<component>标签表示,实体中成员变量的类型是用户自定义的类型。按hibernate的规则,基础类型使用<property>标签,而非基础类型就使用<component>标签。
<component>标签中,name表示这个对象的名字,class表示这个对象是哪个用户自定义类型。其中的property是指,用户自定义类型中的成员变量。
而声明了<component>的那个成员变量,在数据库中的表示方式是:这个用户自定义类型的成员变量中的成员变量,将以列的方式显示在数据库中。即有该变量有三个成员变量,那么数据库中的表就多出对应的三列。
<component name = "address" class="Address"> <property name ="postcode" column="POSTCODE"></property> <property name ="phone" column="PHONE"></property> <property name ="address" column="ADDRESS"></property></component>
单个记录查询get与load的区别
1.在不考虑缓存的情况下,get方法会在调用之后立即向数据库发出sql语句,返回持久化对象。
2.load方法会在调用后返回一个代理对象,该代理对象只保存了实体对象的id,直到使用对象的非主键属性时才会发出sql语句。
3.查询数据库中不存在的数据时,get方法返回null,load方法抛出异常org.hibernate.ObjectNotFoundException
单表的增删改查CRUD1.保存对象,save2.修改对象,update3.删除对象,delete4.查询单个记录,get/load
@Test 生成策略需要为update(create会被清空)
public void testGetStudents {
  Students s = (Students)session.get(Students.class,100)//使用反射得到类型+主键
  System.out.printlin(s);
}
@Test
public void testLoadStudents {
  Students s = (Students)session.load(Students.class,100)//使用反射得到类型+主键
  System.out.printlin(s);
}
@Test
public void testUpdateStudents {
  Students s = (Students)session.get(Students.class,100)//使用反射得到类型+主键
  s.setGender("女");
  session.update(s);
}
@Test
public void testDeleteStudents {
  Students s = (Students)session.get(Students.class,100)//使用反射得到类型+主键
  session.delete(s);
}

可以进行测试区分get与load区别;s.getClass().getName() get返回Students load返回代理类对象Student_$$_javassist_0.....
总结:
 
1.ORM Hibernate 对象关系映射;为了少写和底层数据库相关的sql语句,方便程序的维护、修改,提高跨平台性和可扩展性。Hibernate是Java领域内的一款技术成熟稳定的ORM框架
2.Hibernate开发的基本步骤(1)编写配置文档hibernate.cfg.xml(2)编写实体类。注意:每一个实体类都要与数据库中的一张表一一对应,实体类的编写要遵循JavaBean的要求。(3)生成对应实体类的映射文件并添加到配置文档中(4)调用Hibernate API进行测试
3.session类似于JDBC里面的connection对象。调用session操作数据库,实际上就是调用connection的各种API函数来实现的。
4.openSession与getCurrentSession的区别openSension每次都是创建新的session对象,而getCurrentSenssion使用单例模式,每次创建都是相同的对象。openSession在使用完毕后需要显式地关闭,而getCurrentSession在事务提交之后会自动关闭。
5.单表操作常用的方法增删改查对应使用session当中的save、delete、update、get/load方法
6.单表操作查询一条记录时,get和load的区别get在使用的时候立即发送sql语句,并且获得的是实体类的对象类型,而load只有在使用具体对象的非主键属性的时候才会发送sql语句,而且返回的是一个代理对象。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lozhyf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值