JDBC进阶

JDBC的事务⽀持

JDBC的事务⽀持

事务的概念

当⼀个业务需求涉及到N个DML操作时,这个业务(或者时N个DML操作)当成⼀个整体来处理。在处理的过程中,如果有失败或异常,我们要回到业务开始时。如果成功处理,我们再将数据持久化到磁盘中。这样⼀个过程我们称之为⼀个事务。事务具有原⼦性。不可切割。

总结:
事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全成功,要么全不成功。

关键字:
commit
rollback
savepoint

事务的特性(ACID)

原⼦性(Atomicity)
指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。

⼀致性(Consistency)
事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。转账前和转账后的总⾦额不变。
隔离性(Lsolation)
事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。
持久性(Durability)
指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响

MySQL事务

- 默认情况下,MySQL每执⾏⼀条SQL语句,都是⼀个单独的事务。
- 如果需要在⼀个事务中包含多条SQL语句,那么需要开启事务和结束事务。
    开启事务:start transaction;
    结束事务:commit或rollback;
    
	事务开始于
•连接到数据库上,并执⾏⼀条DML语句insert、update或delete
•前⼀个事务结束后,⼜输⼊了另⼀条DML语句

事务结束于
•执⾏commit或rollback语句。
•执⾏⼀条DDL语句,例如create table语句,在这种情况下,会⾃动执⾏commit语句。
•执⾏⼀条DDL语句,例如grant语句,在这种情况下,会⾃动执⾏commit。
•断开与数据库的连接
•执⾏了⼀条DML语句,该语句却失败了,在这种情况中,会为这个⽆效的DML语句执⾏rollback语句。

sql语句实现事务⽀持
1.回滚情况

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
SELECT * FROM account;
UPDATE account SET balance=balance+10000 WHERE id=2;
ROLLBACK;

2.提交情况

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
SELECT * FROM account;
UPDATE account SET balance=balance+10000 WHERE id=2;
COMMIT;

JDBC的事务⽀持(⼿动控制事务)

Connection.setAutoCommit(boolean flag):此⽅法可以取消事务的⾃动提交功能,值为false。
Connection.commit():进⾏事务提交。
Connection.rollback():进⾏事务回滚.

在这里插入图片描述

多事务的情况

在这里插入图片描述

总结:
数据库通过设置事务的隔离级别防⽌以上情况的发⽣

隔离机制

隔离机制分类

在这里插入图片描述

注意点
1.oracle的隔离级别是read committed

mysql的隔离级别是repeatable read
3.级别越⾼,性能越低,数据越安全
4.设置隔离级别必须在事务之前
mysql中隔离级别相关操作
查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
更改当前的事务隔离级别:SET TRANSACTION ISOLATION LEVEL 四个级别之⼀。
JDBC控制事务的隔离级别
Connection接⼝:

在这里插入图片描述

设置⽅法:
Connection.setTransactionIsolation (int level);

数据库连接池技术

连接池技术出现的原因

在与数据库连接过程中,会⾮常消耗内存,性能⼤打折扣。如果每次请求都去重新连接数据库。那么,宕机的⼏率很⾼。

连接池技术原理和优势

- 原理:
	连接池对象在初始化阶段 ⼀次性创建N个连接对象,这些连接对象存储在连接池对象中。当有请求过来时,先从连接池中寻找空闲连接对象并使⽤,当使⽤完后,将连接对象归还给连接池,⽽不是真正意义上断开连接。
- 优势:
	这样也可以满⾜成千上万个请求,解决建⽴数据库连接耗费资源和时间很多的问题,提⾼性能。

在这里插入图片描述

编写标准的数据源

⾃定义数据库连接池要实现javax.sql.DataSource接⼝,⼀般都叫数据源

代码实现

在这里插入图片描述

编写数据源时遇到的问题

描述:连接对象要放回池⼦,不能关闭.
在这里插入图片描述

编写数据源时的解决办法

a.装饰设计模式:使⽤频率很⾼
⽬的:改写已存在的类的某个⽅法或某些⽅法,装饰设计模式(包装模式)
⼝诀:
1、编写⼀个类,实现与被包装类相同的接⼝。(具备相同的⾏为)
2、定义⼀个被包装类类型的变量。
3、定义构造⽅法,把被包装类的对象注⼊,给被包装类变量赋值。
4、对于不需要改写的⽅法,调⽤原有的⽅法。
5、对于需要改写的⽅法,写⾃⼰的代码。

在这里插入图片描述

b.默认适配器:装饰设计模式⼀个变体

在这里插入图片描述

常⽤的连接池技术

- dbcp 	:是apache组织旗下的⼀个数据库连接池技术产品
- c3p0 	:是⼀个开源的连接池技术
- druid :是阿⾥的数据库连接池技术

dbcp

资源jar包

commons-dbcp2-2.6.0.jar
commons-pool2-2.4.3.jar
commons-logging.jar

配置⽂件dbcp.properties

此配置⽂件请放在src⽬录下

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/qianfeng
username=root
pwd=123456
maxTotal=50
maxIdle=10
minIdle=3
initialSize=5
maxWaitMillis=60000

DBUtildbcp类型的编写

package monrningwork02.util;

import org.apache.commons.dbcp.BasicDataSource;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DBUtildbcp {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;
    private static int maxTotal;
    private static int maxIdle;
    private static int minIdle;
    private static int initialSize;
    private static long maxWaitMillis;
    //声明⼀个dbcp连接池变量
    private static BasicDataSource pool;
    static{
        try{
            pool = new BasicDataSource();//连接池对象
//使⽤类加载器中提供的⽅法来获取字节流对象,同时指定配置⽂件
            InputStream is = DBUtildbcp.class.getClassLoader().getResourceAsStream("dbcp.properties");
            Properties prop = new Properties();
            prop.load(is);//将配置⽂件⾥的内容封装到prop对象内
            driver = prop.getProperty("driver");
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            password = prop.getProperty("pwd");
            maxTotal= Integer.parseInt(prop.getProperty("maxTotal"));
            maxIdle= Integer.parseInt(prop.getProperty("maxIdle"));
            minIdle= Integer.parseInt(prop.getProperty("minIdle"));
            initialSize= Integer.parseInt(prop.getProperty("initialSize"));
            maxWaitMillis= Long.parseLong(prop.getProperty("maxWaitMillis"));
            pool.setDriverClassName(driver);
            pool.setUrl(url);
            pool.setUsername(username);
            pool.setPassword(password);
             //连接池⽀持的最⼤连接数
            //pool.setMaxTotal(maxTotal);
            //连接池⽀持的最⼤空闲数
            pool.setMaxIdle(maxIdle);
            //⽀持的最⼩空闲数
            pool.setMinIdle(minIdle);
            //连接池对象创建时初始化的连接数
            pool.setInitialSize(initialSize);
            //空闲等待时间
            //pool.setMaxWaitMillis(maxWaitMillis);
            //注册驱动
            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException
    {
//从连接池中获取空闲对象
        return pool.getConnection();
    }
    public static void closeConnection(Connection conn, Statement stat, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(stat!=null){
                stat.close();
            }
            if(conn !=null){
                conn.close(); //会将连接对象归还给连接池内
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

c3p0

资源jar包

c3p0-0.9.5-pre8.jar
mchange-commons-java-0.2.7.jar

配置⽂件c3p0-config.xml

配置⽂件请放在src⽬录下

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property
                name="jdbcUrl">jdbc:mysql://localhost:3306/mydb2</property>
        <property
                name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="acquireIncrement">10</property>
        <property name="maxPoolSize">50</property>
        <property name="minPoolSize">2</property>
        <property name="initialPoolSize">5</property>
        <property name="maxIdleTime">600</property>
    </default-config>
</c3p0-config>

DBUtilc3p0类型的编写

package monrningwork02.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DBUtilC3p0 {
    //构造器会⾃动检索src下有没有指定⽂件名称的配置⽂件,然后会⾃动赋值给其相应的属性
    private static ComboPooledDataSource pool = new
            ComboPooledDataSource("c3p0-config");
    public static Connection getConnection() throws SQLException
    {
//从连接池中获取空闲对象
        return pool.getConnection();
    }
    public static void closeConnection(Connection conn, Statement stat, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(stat!=null){
                stat.close();
            }
            if(conn !=null){
                conn.close(); //会将连接对象归还给连接池内
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

druid

资源jar包

druid-1.1.18.jar 

配置⽂件druid.properties

放在src⽬录下。注意,前⾯的key值是固定写法

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/qianfeng
username=root
password=123456
maxActive=20
minIdle=3
initialSize=5
maxWait=60000

DBUtildruid类型的编写

package monrningwork02.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DBUtildruid {
    //创建连接池对象
    private static DataSource pool = null;
    static {
        try {
        //使⽤类加载器提供的⽅法读取db.properties,返回⼀个字节流对象
            InputStream is = DBUtildruid.class.getClassLoader().getResourceAsStream("druid.properties");
        //创建Properties对象,⽤于加载流内部的数据
            Properties prop = new Properties();
            prop.load(is); //加载流内部的信息,以key-value的形式进⾏加载
        //调⽤静态⽅法,会⾃动给⾃⼰的属性赋值
            pool = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            System.out.println("注册驱动失败");
            e.printStackTrace();
        }
    }
    /**
     * 获取连接对象
     *
     * @return 连接对象
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public static Connection getConnection() throws SQLException, ClassNotFoundException {
    //return DriverManager.getConnection(url, username,password);
    //从连接池中获取连接对象
        return pool.getConnection();
    }
    /**
     * 关闭数据库连接
     *
     * @param rs 结果集对象
     * @param stat 处理sql的执⾏对象Statement
     * @param conn 连接对象
     */
    public static void closeConnection(ResultSet rs, Statement stat, Connection conn) {
        try {
            if (rs != null) {
                rs.close();
            }
            if (stat != null) {
                stat.close();
            }
            if (conn != null) {
                conn.close();//释放连接,归还给连接池
            }
        } catch (Exception e) {
            System.out.println("数据库连接关闭失败");
            e.printStackTrace();
        }
    }
}

DAO设计模式

DAO简介

- DAO是数据访问对象(Data Access Object)的简写。
- 建⽴在数据库与业务层之间,封装所有对数据库的访问操作,我们也可称之为持久层。
- ⽬的: 将数据访问逻辑和业务逻辑分开

在这里插入图片描述

⼀个DAO设计模式包含以下内容

  1. 定义实体类: 通过对象关系映射(ORM)将数据库的表结构映射成java类型;表中的每⼀条记录映射成类的实例。⽤于数据的传递。
  2. 定义⼀个接⼝: 在此接⼝中,定义应⽤程序对此表的所有访问操作,如增,删,改、查,等⽅法。
  3. 定义接⼝的实现类 实现接⼝中的所有抽象⽅法。
  4. 定义⼀个DAO⼯⼚类型 ⽤于返回接⼝实例 这样,开发⼈员只需要使⽤DAO接⼝即可,具体逻辑就变得透明了,⽆需了解内部细节。

dbutils第三⽅⼯具类的使⽤

简介

- 作⽤:
DBUtils是java编程中的数据库操作实⽤⼯具,⼩巧简单实⽤。
DBUtils封装了DAO层(持久层)的逻辑。减少了开发周期。
1.对于数据表的读操作,他可以把结果转换成List,Array,Set等java集合,便于程序员操作;
2.对于数据表的写操作,也变得很简单(只需写sql语句)
3.可以使⽤数据源,使⽤JNDI,数据库连接池等技术来优化性能--重⽤已经构建好的数据库连接对象

- jar包:commons-dbutils-1.7.jar

- 常⽤API:
1. QueryRunner类型:可以直接使⽤连接池技术来操作数据库,进⾏增删改查
	构造器:QueryRunner(DataSource ds)
		返回⼀个指定数据库连接池得QueryRunner对象
	⾮静态⽅法:query(String sql, ResultSetHandler<T> rsh)
		通过sql,及其ReusltSetHandler的⼦类型来获取数据并封装成相应对象
	它主要有三个⽅法
query() ⽤于执⾏select
update() ⽤于执⾏insert update delete
batch() 批处理
2. ResultSetHandler:关于结果集的⼀个接⼝。
	⽤于定义select操作后,怎样封装结果集.
        其实现类举例如下:
        BeanHandler:将查询到的数据的第⼀条封装成实体类对象
        BeanListHandler:将查询到的数据的第⼀条封装成实体类对象的集合

增删改代码测试

//第⼀种:使⽤的是⽆参的QueryRunner()⽅法----为了更加⽅便的使⽤
事务,因为可以直接获取到Connection对象
//1.创建⼲活的的对象--QueryRunner
QueryRunner qRunner = new QueryRunner();
//2.获取连接对象
Connection connection = C3P0Util.getConnection();
//3.调⽤update()⽅法实现对数据库的访问
int num = qRunner.update(connection, sql,6,"⻢六","345");
if (num >0) {
	System.out.println("增加成功");
}else {
	System.out.println("增加失败");
}
//第⼆种:使⽤的是有参的QueryRunner()⽅法--参数是数据源
//1.创建⼲活⼉的对象并绑定数据源
// QueryRunner qRunner2 = new
QueryRunner(C3P0Util.getDataSource());
// //2.调⽤update()⽅法
// int num2 = qRunner2.update(sql,7,"⻢六1","3451");
// if (num2 >0) {
// 		System.out.println("增加成功");
// }else {
// 		System.out.println("增加失败");
// }
}

查找功能实现

直接使⽤ResultSetHandler接⼝

public static User findUser1(User user) throws SQLException, ClassNotFoundException {
    QueryRunner queryRunner = new QueryRunner();
    Connection connection = DBUtildruid.getConnection();
    return queryRunner.query(connection,"select * from user where password=? and name=?",new BeanHandler<User>(User.class),
            user.getPassword(),user.getName());
}

我们发现通过实现ResultSetHandler接⼝,可以以各种形式获取数据库的数据,所以系统就封装了⼀批ResultSetHandler的⼦类实现各种功能.

ResultSetHandler下的所有结果处理器(⼦类)

注意:以下的⼦类中,重点掌握BeanHandler和BeanListHandler的功能实现
ArrayHandler:适合取1条记录。把该条记录的每列值封装到⼀个数组中Object[]
ArrayListHandler:适合取多条记录。把每条记录的每列值封装到⼀个数组中Object[],把数组封装到⼀个List中
ColumnListHandler:取某⼀列的数据。封装到List中。
KeyedHandler:取多条记录,每⼀条记录封装到⼀个Map中,再把这个Map封装到另外⼀个Map中,key为指定的字段值。
MapHandler:适合取1条记录。把当前记录的列名和列值放到⼀个Map中
MapListHandler:适合取多条记录。把每条记录封装到⼀个Map中,再把Map封装到List中
ScalarHandler:适合取单⾏单列数据
BeanHandler:返回⼀条记录-得到的是模型----重点掌握
BeanListHandler:返回所有的数据–将记录装⼊模型,将模型装⼊集合(list)—重点掌握

xml与json

XML

简介

eXtensible Markup Language 可扩展标记语⾔,⾥⾯的标记都是⽤户⾃定义的,标记都是成对的。作⽤是⽤来存储数据和传输数据。特别适合⽹络传输。

语法

* ⽂档声明:
    * 必须写在xml⽂档的第⼀⾏。
    * 写法:<?xml version="1.0" ?>
    * 属性:
        * version:版本号 固定值 1.0
        * encoding:指定⽂档的码表。默认值为 iso-8859-1
        * standalone:指定⽂档是否独⽴ yes 或 no
        
		* 元素:xml⽂档中的标签
    ** ⽂档中必须有且只能有⼀个根元素
    * 元素需要正确闭合。<body></body> <br/>
    * 元素需要正确嵌套
    * 元素名称要遵守:
        * 元素名称区分⼤⼩写
        * 数字不能开头
    * ⽂本:
        - 特殊符号:
            < : &lt;
            > : &gt;
            & : &amp;
          	" : &quot;
			' : &apos;
    * CDATA: ⾥边的数据会原样显示
    * <![CDATA[ 数据内容 ]]>
* 属性:
    * 属性值必须⽤引号引起来。单双引号都⾏
    * 注释:
    <!-- 注释内容 -->
* 处理指令:现在基本不⽤
	<?xml-stylesheet type="text/css" href="1.css"?>

简单案例

<?xml version="1.0" encoding="UTF-8"?>
<书架>
    < 出版社=呵呵>
        <书名>⾦瓶梅</书名>
        <作者>陈冠希</作者>
        <单价>10</单价>
        <批发价>20</批发价>
    </>
    <>
        <书名>葵花宝典</书名>
        <作者>东⽅不败</作者>
        <单价>10</单价>
    </>
</书架>

功能

​ 数据存储
​ 与⽂件对⽐
​ 配置⽂件
作为配置⽂件存在,xml中主要配置的⼀些具有复杂的层级关系的数据
Properties⽂件中主要配置的⼀些key和value这样的数据。
​ 数据传输

⼤部分使⽤json
json格式:{user:[{},{}],address:千锋}
{}代表map []代表数组
数据显示
可以使⽤html和xml,html主要⽤于⽹⻚显示.

与html的区别

xml是可扩展的标记语⾔,html是超⽂本标记语⾔
xml⾥的标记都是⽤户⾃定义的,html都是预定义的
xml⽤于存储和传输,html⽤于显示数据
html语法松散,xml语法严格

约束

1.约束就是xml的书写规则
2.约束的分类
dtd(Document Type Definition)约束
schema约束
3.dtd分类(简单约束)
内部dtd:在xml内部定义dtd
外部dtd:在外部⽂件中定义dtd
本地dtd⽂件:

本地dtd⽂件的编写规则介绍:以student.dtd为例
	1.<!ELEMENT students (student*) > 构成:<!ELEMENT 元素名(规则(正则表达式))>,students是根节点,括号中是⽗节点的直接⼦节点
	2.举例:正则表达式中 (student*)表示根节点的直接⼦节点必须都是student,并且可以出现0次或多次(name,age,sex) 表示student的元素是他们(#PCDATA)表示当前节点是叶⼦节点
	3.<!ATTLIST student number ID #REQUIRED> !ATTLIST表示属性设置,number ID:表示每个student节点的ID号,必须是唯⼀的 #REQUIRED:表示ID号必须有
	4.对应的xml中的设置:<!DOCTYPE students SYSTEM"student.dtd">,将这个设置放在⽂档声明的下⾯,表示当前⽂档遵守dtd的约束; 每个student节点的属性中都要标明 ID号,如:number="s0001",且每个ID号都不⼀样.

​ ⽹络dtd⽂件:

原始⽂件 (student.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE students SYSTEM "student.dtd">
<students>
    <student number="s0001">
        <name>zs</name>
        <age>abc</age>
        <sex>yao</sex>
    </student>
</students>

dtd约束⽂件(student.dtd)

<!ELEMENT students (student*) >
<!ELEMENT student (name,age,sex)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
<!ATTLIST student number ID #REQUIRED>

4.schema约束(详细约束)

    导⼊xsd约束⽂档:
    1、编写根标签
    2、引⼊实例名称空间
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	3、引⼊名称空间 xsi:schemaLocation="http://www.itcast.cn/xml student.xsd"
    4、引⼊默认的名称空间
    
    例⼦:
	xmlns:xmlnamespace:xml的命名空间,命名空间标识是命名空间最重要的属性,重要到当输出⼀个命名空间时就直接转换为它的标识。标识有个规范的称呼:URI(统⼀资源定位符)。URI的最⼤特点是唯⼀性。如果不唯⼀就失去了辨识的意义。
    xmlns="http://www.qianfeng.cn/xml"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.qianfeng.cn/xml student.xsd"

json字符串

就是⽤特殊的字符串
需要使⽤双引号。
传输数据时多以json串形式传递数据,可读性差,但是效率⽐xml⾼
表示对象:必须使⽤{}
var json = "
{'city':'hangzhou','BESTJINGDIAN':'XIHU','SECONDJINGDIAN':'LEIFE NGTA'}"
表示数组:必须使⽤[]
var json = '[{"city":"hangzhou"},{"city":"beijing"},{"city":"shanghai"}]'

核⼼⾯试题

1.说出数据连接池的⼯作机制是什么?
J2EE服务器启动时会建⽴⼀定数量的池连接,并⼀直维持不少于此数⽬的池连接。客户端程序需要连接时,池驱动程序会返回⼀个未使⽤的池连接并将其标记为忙。如果当前没有空闲连接,池驱动程序就新建⼀定数量的连接,新建连接的数量由配置参数决定。当使⽤的池连接调⽤完成后,池驱动程序将此连接表记为空闲,其他调⽤就可以使⽤这个连接。

⼀个命名空间时就直接转换为它的标识。标识有个规范的称呼:URI(统⼀资源定位符)。URI的最⼤特点是唯⼀性。如果不唯⼀就失去了辨识的意义。
xmlns=“http://www.qianfeng.cn/xml”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://www.qianfeng.cn/xml student.xsd”


##  json字符串

```json
就是⽤特殊的字符串
需要使⽤双引号。
传输数据时多以json串形式传递数据,可读性差,但是效率⽐xml⾼
表示对象:必须使⽤{}
var json = "
{'city':'hangzhou','BESTJINGDIAN':'XIHU','SECONDJINGDIAN':'LEIFE NGTA'}"
表示数组:必须使⽤[]
var json = '[{"city":"hangzhou"},{"city":"beijing"},{"city":"shanghai"}]'

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值