SSH之Hibernate(一)

一、Hibernate的入门

1、Hibernate的概述

 1-1 javaEE 三层结构

三层架构

1-2 什么是Hibernate

来自百度百科

Hibernate是一个持久层ORM框架。

1-3 什么是ORM

ORM:Object Relational Mapping(对象关系映射)。指的是将一个Java中的对象与关系型数据库中的表建立一种映射关系,从而操作对象就可以操作数据库中的表。

enter description here

1-4 为什么学习Hibernate

enter description here

2、Hibernate的入门

 2-1 下载Hibernate的开发环境

Hibernate:
https://sourceforge.net/projects/hibernate/files/hibernate-orm/5.0.7.Final/
mysql驱动包:
https://pan.baidu.com/s/1lpa5eNkuoI-Uw6arDf3rlw
log4j:
https://pan.baidu.com/s/1fi6jNGtWtwp_8fXQo-cGPg

 2-2 解压Hibernate

enter description here

  • documentation :Hibernate开发的文档
  • lib :Hibernate开发包
     required :Hibernate开发的必须的依赖包
     optional :Hibernate开发的可选的jar包
  • project :Hibernate提供的项目

 2-3 创建一个项目,引入jar包

  • 数据库驱动包
  • Hibernate开发的必须的jar包
  • Hibernate引入日志记录包
    enter description here

 2-4 创建表

CREATE TABLE `cst_customer` (
  `cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
  `cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
  `cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源',
  `cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业',
  `cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别',
  `cust_phone` varchar(64) DEFAULT NULL COMMENT '固定电话',
  `cust_mobile` varchar(16) DEFAULT NULL COMMENT '移动电话',
  PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 2-5 创建实体类

public class Customer {
	private Long cust_id;
	private String cust_name;
	private String cust_source;
	private String cust_industry;
	private String cust_level;
	private String cust_phone;
	//省略get和set方法

 2-6 创建映射

映射需要通过XML的配置文件来完成,这个配置文件可以任意命名。尽量统一命名规范(类名.hbm.xml)
这里创建一个Customer.hbm.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 下面的约束在hibernate-core-5.0.7.Final.jar下的org.hibernate最后面的hibernate-mapping-3.0.dtd下 -->
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping><!-- 根标签 -->
<!-- 建立类与表的映射,name代表哪个类,table代表哪个表。哪个类与哪个表形成映射 -->
	<class name="com.qgc.hibernate.Demo1.Customer" table="cst_customer">   
		<!-- 建立类中的属性与表中的主键对应,name代表类里面的属性,column代表表里面的字段 -->
		<id name="cust_id" column="cust_id">
			<!-- 使用本地主键生成策略native -->	
			<generator class="native"></generator>
		</id>
		
		<!-- 建立类中普通的属性与表中字段的对应 -->
		<property name="cust_name" column="cust_name"></property>
		<property name="cust_source" column="cust_source"></property>
		<property name="cust_industry" column="cust_industry"></property>
		<property name="cust_level" column="cust_level"></property>
		<property name="cust_phone" column="cust_phone"></property>
	</class>
</hibernate-mapping>

 2-7 创建Hibernate核心配置文件

Hibernate的核心配置文件的名称:hibernate.cfg.xml
在src下创建hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 下面的约束在hibernate-core-5.0.7.Final.jar下的org.hibernate最后面的hibernate-configuration-3.0.dtd下 -->
<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<!-- 下面的参数可以在project\etc\hibernate.properties文件中找到 -->
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql://47.97.91.178:3306/hibernate_day01</property>
		<property name="hibernate.connection.username">qgc</property>
		<property name="hibernate.connection.password">2wsx@WSX</property>
		<!-- 配置Hibernate的方言 -->
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<!--=============可选配置====================-->
			<!-- 打印sql语句 -->
			<property name="hibernate.show_sql">true</property>
			<!-- 格式化sql -->
			<property name="hibernate.format_sql">true</property>
			<!-- 数据库中没有这个表时,自动建表-->
			<property name="hibernate.hbm2ddl.auto">update</property>
		<!--=============可选配置====================-->
		<!-- 告诉hibernate映射文件在哪,注意此处不是.而是/ -->
		<mapping resource="com/qgc/hibernate/Demo1/Customer.hbm.xml"/>
	</session-factory>
</hibernate-configuration>

 2-8 编写测试代码

在包下新建一个HibernateDemo1的类

public class hibernateDemo1 {
	@Test
	//保存客户案例
	public void test() {
		//1、加载Hibernate核心配置文件
		Configuration configure = new Configuration().configure();
		//2、创建一个sessionFactory对象:类似于JDBC中的连接池
		SessionFactory sessionFactory = configure.buildSessionFactory();
		//3、通过sessionFactory获取到session连接对象:类似于JDBC中的Connection
		Session session = sessionFactory.openSession();
		//4、手动开启事务
		Transaction transaction = session.beginTransaction();
		//5、编写代码
		Customer customer = new Customer();
		customer.setCust_name("test");
		session.save(customer);
		//6、提交事务
		transaction.commit();
		//7、资源释放
		session.close();
	}
}

3、Hibernate的常见配置

 3-1 不联网xml不提示问题解决

选择eclipse上方的window按钮,选择Preference。在弹出的对话框中的左上角输入xml catalog
右侧选择User…单机add
Alt
Alt

 3-2 映射的配置

  • 【class标签的配置】
    1. 标签用来建立类与表的映射关系
    2. 属性:
       name :类的全路径
       table :表名(类名与表名一致,table可以省略)
       catalog :数据库名
  • 【id标签的配置】
    1. 标签用来建立类中的属性与表中的主键的对应关系
    2. 属性:
       name :类中的属性名
       column :表中的字段名(类中的属性名和表中的字段名如果一致,column可以省略)
       length :长度
       type :类型
  • 【property标签的配置】
    1. 标签用来建立类中的普通属性与表的字段的对应关系
    2. 属性:
       name :类中的属性名
       column :表中的字段名
       length :长度
       type :类型
       not-null :设置非空
       unique :设置唯一

 3-3 核心的配置

例如:

<hibernate-configuration>
	<session-factory>
		<!-- 下面的参数可以在project\etc\hibernate.properties文件中找到 -->
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.url">jdbc:mysql://47.97.91.178/hibernate_day01</property>
		<property name="hibernate.connection.username">qgc</property>
		<property name="hibernate.connection.password">2wsx@WSX</property>
		<!-- 配置Hibernate的方言 -->
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<!-- <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> -->
		<!-- 告诉hibernate映射文件在哪,注意此处不是.而是/ -->
		<!-- 打印sql语句 -->
		<property name="hibernate.show_sql">true</property>
		<!-- 格式化sql -->
		<property name="hibernate.format_sql">true</property>
		<mapping resource="com/qgc/hibernate/Demo1/Customer.hbm.xml"/>

	</session-factory>
</hibernate-configuration>
  • 必须的配置
    连接数据库的基本的参数

    1. 驱动类
    2. url路径
    3. 用户名
    4. 密码

    方言

  • 可选的配置
    显示SQL :hibernate.show_sql
    格式化SQL :hibernate.format_sql
    自动建表 :hibernate.hbm2ddl.auto

    1. none :不使用hibernate的自动建表
    2. create :如果数据库中已经有表,删除原有表,重新创建,如果没有表,新建表。(测试)
    3. create-drop :如果数据库中已经有表,删除原有表,执行操作,删除这个表。如果没有表,新建一个,使用完了删除该表。(测试)
    4. update :如果数据库中有表,使用原有表,如果没有表,创建新表(更新表结构)
    5. validate :如果没有表,不会创建表。只会使用数据库中原有的表。(校验映射和表结构)。
  • 映射文件的引入
    引入映射文件的位置

<mapping resource="com/qgc/hibernate/Demo1/Customer.hbm.xml"/>

4、Hibernate的核心API

 4-1 Configuration对象(Hibernate的配置对象,了解)

enter description here
作用:

  • 加载核心配置文件

如果是hibernate.properties文件

Configuration cfg = new Configuration();

如果是hibernate.cfg.xml

Configuration cfg = new Configuration().configure();
  • 加载映射文件
// 手动加载映射
configuration.addResource("com/itheima/hibernate/demo1/Customer.hbm.xml");

4-2 SessionFactory对象(Session工厂)

SessionFactory内部维护了Hibernate的连接池和Hibernate的二级缓存(不讲)。是线程安全的对象。一个项目创建一个对象即可。

  • 显示日志(了解)
    在src下面新建log4j.properties配置文件,写入如下代码
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###
# error warn info debug trace 这是权限,如果设置为info这显示error,warn和info的信息
# stdout为向控制台打印 file为向文件输出
log4j.rootLogger= info, stdout
  • 配置连接池(了解)
    在hibernate资源包下的\lib\optional\c3p0下,将3个jar包导入
    在hibernate.cfg.xml文件下插入如下代码
<!-- 配置C3P0连接池 -->
		<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
		<!--在连接池中可用的数据库连接的最少数目 -->
		<property name="c3p0.min_size">5</property>
		<!--在连接池中所有数据库连接的最大数目  -->
		<property name="c3p0.max_size">20</property>
		<!--设定数据库连接的过期时间,以秒为单位,
		如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
		<property name="c3p0.timeout">120</property>
		 <!--每3000秒检查所有连接池中的空闲连接 以秒为单位-->
		<property name="c3p0.idle_test_period">3000</property>

  • 抽取工具类
public class HibernateUtils {
	public static final Configuration cfg;
	public static final SessionFactory sf;
	static {
		cfg = new Configuration().configure();
		sf = cfg.buildSessionFactory();
	}
	public static Session getSession() {
		return sf.openSession();
	}
}	

4-3 Session对象(类似Connection对象是连接对象)

Session代表的是Hibernate与数据库的链接对象。不是线程安全的。与数据库交互桥梁。
Session中的API:

  • 保存方法
    Serializable save(Object obj);
  • 查询方法
    T get(Class c,Serializable id);
    T load(Class c,Serializable id);
    如下代码:
	public void test2() {
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		
		Customer customer = session.get(Customer.class, 1l);
		System.out.println(customer.getCust_id());
		 
		Customer customer2 = session.load(Customer.class, 1l);
		System.out.println(customer2.getCust_id());
		
		transaction.commit();
		session.close();
	}

get方法和load方法的不同(重要)

get方法
 采用的是立即加载,执行到这行代码的时候,就会立即发送SQL语句去查询
 查询后返回的是真是对象本身
 查询一个找不到的对象时返回的是null
load方法
 采用的是延时加载(lazy加载),执行到这行代码的时候不会立即发送SQL语句,当真正使用这个对象的时候才会发送SQL语句去查询
 查询后返回的是代理对象;利用javassist技术产生的代理。
 查询一个找不到的对象的时候,返回ObjectNotFoundException

  • 修改方法
    void update(Object obj);
	public void test3() {
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		
		/*//直接创建对象进行修改(这种方法会将下面代码没有设置值的置为空)
		Customer customer = new Customer();
		customer.setCust_id(1l);
		customer.setCust_name("pig");
		session.update(customer);*/
		
		//先查询再修改(推荐)
		Customer customer = session.get(Customer.class, 1l);
		customer.setCust_name("pig");
		session.update(customer);
		
		transaction.commit();
		session.close();
	}
  • 删除方法
    void delete(Object obj);
	public void test4() {
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		
		/*//直接创建对象删除
		Customer customer = new Customer();
		customer.setCust_id(1l);
		session.delete(customer);*/
		
		//先查询再删除(推荐),----可以进行级联删除
		Customer customer = session.get(Customer.class, 2l);
		session.delete(customer);
		
		transaction.commit();
		session.close();
	}
  • 保存或更新
    void saveOrUpdate(Object obj)
    如果数据库中有,则执行更新如果没有则插入
	public void test5() {
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		
		/*Customer customer = new Customer();
		customer.setCust_name("dog");
		session.saveOrUpdate(customer);*/
		
		Customer customer2 = new Customer();
		customer2.setCust_id(3l);
		customer2.setCust_name("5656556");
		session.saveOrUpdate(customer2);	
		
		transaction.commit();
		session.close();
	}
  • 查询所有
	public void test6() {
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		//以后会讲到以下方法的具体使用
		Query query = session.createQuery("from Customer");	 //此处参数不是sql,是hql(面向对象的查询语言)
		List<Customer> list = query.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		
		//使用SQL语句查询所有
		SQLQuery query2 = session.createSQLQuery("select * from cst_customer");
		List<Object[]> list2 = query2.list();
		for (Object[] objects : list2) {
			System.out.println(Arrays.toString(objects));
		}
		
		
		transaction.commit();
		session.close();
	}

4-4 Transaction对象(事务对象)

Hibernate中管理事务的对象。

  • commit();
  • rollback();

二、主键生成策略&一级缓存&事务管理

 1、持久化类的编写规则

  1-1 什么是持久化类?

持久化:将内存中的一个对象持久化到数据库中的过程。Hibernate框架就是用来进行持久化的框架。
持久化类:一个java对象与数据库表建立了映射关系,那么这个类在hibernate中就成为持久化类

  • 可以理解为 持久化类 = java类 + 映射文件

  1-2 持久化类的编写规则

  • 对持久化类编写一个无参的构造方法 (因为hibernate底层要使用反射生成实例)
  • 属性需要私有,对外提供set和get方法(hibernate设置对象的值)
  • 对持久化类提供一个唯一标识OID与数据库主键对应(确定是否是同一对象)
  • 持久化类中的属性尽量使用包装类类型(如果使用基本数据类型,其默认值是0,会有很多的歧义)
  • 持久化类不要使用final修饰

延迟加载本身是hibernate的一个优化手段,返回的是一个代理对象(javassist,可以对没有实现接口的类产生一个代理-----使用了非常底层的字节码增强技术来继承这个类)。如果持久化类用final修饰,则不能被继承,不能产生代理对象,延时加载的技术也失效了。get方法和load方法就一致了。

2、主键生成策略

 2-1 主键的分类

  • 自然主键
    主键的本身就是表中的一个字段(实体中一个具体的属性)
     比如创建一个学生表,学生表里的省份证号为主键就是自然主键
  • 代理主键
    主键的本身不是表中必须的一个字段,比如id
    在实际开发中,尽量使用代理主键

 2-2 Hibernate的主键生成策略

在实际开发中一般不允许用户手动设置主键,一般将主键交给数据库,手动编写程序进行设置。在Hibernate中,提供了很多种的主键生成策略。

  • increment
     Hibernate提供的自动增长机制,适用于主键为int、short、long类型。在单线程中使用。
     底层原理:底层发送select max(id)from 表名;id+1为当前主键。
  • identity
     适用于short、int、long类型,采用数据库底层的自动增长技术。适用于自动增长的数据库(mysql、MSSQL)但是oracle不支持自动增长。
  • sequence
     适用于int、short、long类型的主键。采用的是序列的方式(oracle支持)mysql就不支持了
  • uuid
     适用于字符串类型的主键。使用Hibernate中的随机方式生成字符串。
  • native
     本地策略,可以在identity和sequence之间来回切换。
  • assigned
     Hibernate放弃外键的管理,需要编写程序设置,或者用户自己设置。

3、持久化类的三种状态(了解)

hibernate是持久化框架,通过持久化类完成ORM操作。Hibernate为了更好的管理持久化类,将持久化类分成三种状态。

  • 瞬时态
    这种对象没有唯一的标识OID,被session管理,称为瞬时态对象。
    获得瞬时态对象
      通过创建javabean的对象获得,例如:Customer customer = new Customer();
    状态转换
      瞬时→持久 save(Object obj)、saveOrUpdate(Object obj)
      瞬时→托管 customer.setId(1)
  • 持久态
    这种对象有唯一标识OID,被session管理,称为持久态对象。
    获得持久态对象
      使用get()、load()方法
    状态转换
      持久→瞬时 delete()
      持久→托管 close()、clear()
    持久态对象可以自动更新数据库
  • 托管态
    种对象有唯一标识OID,没有被session管理,称为托管态
    状态转换
      托管→持久 update()、savaOrUpdate()
      托管→瞬时 customer.setId(null)

如何区分三种状态的对象

	public void test() {
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		
		Customer customer = new Customer();		//瞬时态对象
		customer.setCust_name("adecf");
		session.save(customer);					//持久态对象
		
		transaction.commit();
		session.close();
		System.out.println(customer.getCust_id());	//托管态对象
	}

4、Hibernate一级缓存

 4-1缓存概述

缓存是一种优化方式,将数据存入到内存中,使用的时候直接在缓存中获取,不用通过存储源。

 4-2 Hibernate的一级缓存

Hibernate的一级缓存被称为是session级别的缓存,一级缓存的生命周期与session的生命周期一致。
一级缓存是自带的,不可卸载。

  • 证明一级缓存的存在
public void test1(){
		Session session = HibernateUtils.getSession();
		Transaction transaction = session.beginTransaction();
		
		Customer customer = session.get(Customer.class, 2l);	//发送sql语句
		
		Customer customer2 = session.get(Customer.class, 2l);	//不发送sql语句,在缓存中查找
		
		System.out.println(customer==customer2);//返回结果为true
		transaction.commit();
		session.close();
	}

 4-3 一级缓存的原理

enter description here

5、 Hibernate的事务管理

 5-1 Hibernate中设置事务的管理级别

  • 设置事务的级别
Read uncommitted以上读问题全部解决
Read committed解决脏读,但是不可重复读和虚读有可能发生
Repeatable read解决脏读和不可重复读,但是虚读有可能发生
serializable解决所有的读问题

enter description here
在hibernate.cfg.xml中插入如下代码即可:

<property name="hibernate.connection.isolation">4</property>

 5-2 Hibernate解决Service的事务管理

  5-2-1 为什么要解决service事务管理

enter description here

如上图所示,事务要加到service层。因为service要进行很多的操作才能算是完成一次事务。比如说注册,首先要查询数据库中有没有重名的,然后才是往数据库中增加数据。
在前面的学习中,使用JDBC保证是同一个连接对象采用的是向下传递,也就是在service里面新建collection连接对象,在调用dao中的方法时作为参数传递过去。还有一种方式是采用ThreadLocal绑定到本地线程中
Hibernate内部已经提供了绑定好的ThreadLocal
 在sessionFactory中提供了提供了一个方法getCurrentSession();
 此方法需要完成配置

  5-2-2 如何使用getCurrentSession();方法
  1. 修改配置文件,开启getCurrentSession();方法
#在hibernate.cfg.xml文件下插入如下代码
<property name="hibernate.current_session_context_class">thread</property>
  1. 编写工具类
public class HibernateUtils {
	public static final Configuration cfg;
	public static final SessionFactory sf;
	static {
		cfg = new Configuration().configure();
		sf = cfg.buildSessionFactory();
	}
	public static Session getSession() {
		return sf.openSession();
	}
	public static Session getCurrentSession() {
		return sf.getCurrentSession();
	}
  1. 测试是否成功
	@Test
	public void test() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();
		Customer customer = new Customer();
		customer.setCust_name("588666");
		session.save(customer);
		transaction.commit();
	}

注意:不要手动关闭session了,因为线程接结束后session会自动关闭

6、Hibernate的其它API

 6-1 Query

  • Query接口用于接收HQL,查询多个对象。
	public void test() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();
		String hql = "from Customer";								//查询所有的记录
//		String hql = "from Customer where cust_name like ?";    	//条件查询
		Query query = session.createQuery(hql);
//		query.setParameter(0, "林%");								//设置条件
		query.setFirstResult(1);									//设置分页从哪开始
		query.setMaxResults(2);										//设置分页每页显示多少
		List<Customer> list = query.list();	
		for (Customer customer : list) {
			System.out.println(customer);
		}
		transaction.commit();
	}

 6-2 Criteria

  • 是一种条件查询
  • 更加面向对象的查询方式
//查询所有
	public void test2() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();
		Criteria criteria = session.createCriteria(Customer.class);
		List<Customer> list = criteria.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		transaction.commit();
	}
//条件查询
	public void test2() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();
		Criteria criteria = session.createCriteria(Customer.class);
//		criteria.add(Restrictions.ilike("cust_name", "林%"));
		criteria.add(Restrictions.ilike("cust_name", "得",MatchMode.ANYWHERE));
		List<Customer> list = criteria.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		transaction.commit();
	}
//分页查询与上表一样

 6-3 SQLQuery(了解)

  • SQL用于接收sql。语句特别复杂的情况下使用此方式。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

超人不会飞aa

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

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

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

打赏作者

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

抵扣说明:

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

余额充值