通关JDBC

一、JDBC简介

JDBC概念:

  • JDBC就是使用Java语言操作关系型数据库的一套API 
  • 全程:(Java DataBase Connectivity)Java数据库连接

JDBC本质:

  • 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
  • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类

 

 JDBD好处:

  •  各数据库厂商使用相同的接口,Java代码不需要针对不同的数据库分别开发
  • 可随时替换底层数据库,访问数据库的Java代码基本不变

二、JDBC快速入门

 JDBC快速入门

三、JDBC API 详解

DriverManager

  •  DriverManager(驱动管理类)作用:
  1. 注册驱动
  2. 获取数据库的连接 

 1、注册驱动

Class.forName("com.mysql.jdbc.Driver");

 查看Driver类源代码:

 提示:

  • MySQL5之后的驱动包,可以省略注册驱动包的流程
  • 自动加载jar包中META-INF/Services/java.sql.Driver文件中的驱动文件

 2、获取连接

static Connection getConnection(String url, String user, String password)

  • 参数 

1、url:连接路径

语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…

示例:jdbc:mysql://127.0.0.1:3306/db1

细节:

  • 如果连接的是本机的MySQL服务器,并且mysql服务默认的端口是3306,则url可以简写为:jdbc:mysql:///数据库名?参数键值对
  • 配置useSSL=false参数,禁用安全连接方式,解决警告提醒

2、user:用户名

3、password:密码

Connection

  • Connection(数据库连接对象)作用:
  1.  获取执行SQL的对象
  2. 管理事务

1、获取执行SQL的对象

  • 普通的执行SQL的对象

Statement createStatement()

  • 预编译SQL的执行SQL对象:防止SQL注入

PreparedStatement prepareStatement(sql)//后面展开讲

  • 执行存储过程的对象

CallableStatement prepareCall(sql)

2、事务管理

  • MySQL事务管理

开启事务:BEGIN;/START TRANSACTION;

提交事务:COMMIT;

回滚事务:ROLLBACK;

MySQL默认自动提交事务

  • JDBC事务管理:Connection接口中定义了3个对应方法

开启事务:setAutoCommit(boolean autoCommit): ture为自动提交事务;false为手动提交事务,即为开启事务(其实就是是否自动提交事务,no,我要自己手动提交,那我们就先开启一个事务吧)

提交事务:commit()

回滚事务:rollback()

public static void main(String[] args) throws Exception {
        //1、注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2、获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url,username,password);

        //3、定义sql
        String sql1 = "update emp set salary = 7000 where id = 1";
        String sql2 = "update emp set salary = 7000 where id = 2";

        //4、获取执行sql的对象Statement
        Statement statement = conn.createStatement();

        try {
            //开启事务
            conn.setAutoCommit(false);
            //5、执行sql
            int count1 = statement.executeUpdate(sql1);//受影响的行数
            //6、处理结果
            System.out.println(count1);

            int i=3/0;

            //5、执行sql
            int count2 = statement.executeUpdate(sql2);//受影响的行数
            //6、处理结果
            System.out.println(count2);

            //提交事务
            conn.commit();
        } catch (SQLException e) {
            //回滚事务
            conn.rollback();
            throw new RuntimeException(e);
        }


        //7、释放资源
        statement.close();
        conn.close();

    }

 数值未改变

 若不进行事务的处理,即注释掉conn.setAutoCommit(false)和 conn.commit()conn.rollback()则会出现如下结果

 第一条sql语句执行,第二条语句不执行。在某些实际情况中,如张三借李四400块钱,sql1是张三的钱多了400,sql2是李四的钱少了400,如果像上面那样只执行一条,显然是不合理的。

Statement

  • Statement作用:执行sql语句

int executeUpdate(sql):执行DML、DDL语句

返回值:(1)DML语句影响行数(2)DDL语句执行后,执行成功也可能返回0,如删除数据库的时候

 ResultSet executeQuery(sql):执行DQL语句

返回值:ResultSet结果集对象

ResultSet

  • ResultSet(结果集对象)作用:封装了DQL查询语句的结果

 ResultSet stmt.executeQuery(sql):执行DQL语句,返回ResultSet对象

  • 获取查询结果

 boolean next():(1)将光标从当前位置向前移动一行(2)判断当前行是否为有效行

返回值:

  • true:有效行,当前行有数据
  • false:无效行,当前行没有数据

 xxx GetXxx(参数):获取数据

xxx:数据类型;如:int getInt(参数);String getString(参数)

参数:

  • int:列的编号,从1开始
  • String:列的名称

 结果即为某行某列的数据。

  • 使用步骤:
  1.  游标向下移动一行,并判断该行是否有数据:next()
  2. 获取数据:getXxx(参数)

 //判断游标是否是最后一段末尾

while(rs.next()){

        //获取数据

        rs.getXxx(参数);

}

@Test
    public void testResultSet() throws Exception {
        //1、注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2、获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url,username,password);

        //3、定义sql
        String sql = "select * from emp";

        //4、获取实行statement对象
        Statement stmt = conn.createStatement();

        //5、执行sql
        ResultSet rs = stmt.executeQuery(sql);

        //6、处理结果,遍历rs中的所有数据
        //6.1光标向下移动一行,并判断当前行是否有数据
        while(rs.next()){
            //6.2获取数据 getXxx()
            int id = rs.getInt(1);
            String name = rs.getString(2);
            String sex = rs.getString(3);
            double salary = rs.getDouble(4);
            Date date = rs.getDate(5);

            System.out.println(id);
            System.out.println(name);
            System.out.println(sex);
            System.out.println(salary);
            System.out.println(date);
            System.out.println("-------------------");
        }

        //7、释放资源
        rs.close();
        stmt.close();
        conn.close();
    }

案例:

需求:查询accout账户表数据,封装为Account对象中,并存储到ArrayList集合中

@Test
    public void testResultSet2() throws Exception {
        //1、注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2、获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url,username,password);

        //3、定义sql
        String sql = "select * from account";

        //4、获取实行statement对象
        Statement stmt = conn.createStatement();

        //5、执行sql
        ResultSet rs = stmt.executeQuery(sql);

        //创建集合
        List<Account> list = new ArrayList<>();

        //6、处理结果,遍历rs中的所有数据
        //6.1光标向下移动一行,并判断当前行是否有数据
        while(rs.next()){
            Account account = new Account();
            //6.2获取数据 getXxx()
            int id = rs.getInt(1);
            String name = rs.getString(2);
            Double money = rs.getDouble(3);

            //赋值
            account.setId(id);
            account.setName(name);
            account.setMoney(money);

            //存入集合
            list.add(account);
        }

        System.out.println(list);

        //7、释放资源
        rs.close();
        stmt.close();
        conn.close();
    }

PreparedStatement

  • PreparedStatement作用:预编译SQL语句并执行,预防SQL注入问题
  • SQL注入:SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。

 SQL注入演示:

需求:完成用户登录

先创建要用到的表:

-- 删除tb_user表
DROP TABLE IF EXISTS tb_user;
-- 创建tb_user表
CREATE TABLE tb_user(
	id INT,
	username VARCHAR(20),
	password VARCHAR(32)
);

-- 添加数据
INSERT INTO tb_user VALUES (1,'zhangsan','123'),(2,'list','234');

SELECT * FROM tb_user;

 先实现一个简单的登录逻辑:

@Test
    public void testResultSet() throws Exception {
        //1、注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2、获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url,username,password);

        //接收用户输入用户名和密码
        String name = "zhangsan";
        String pwd = "123";

        String sql = "select * from tb_user where username = '" + name + "' and password = '"+ pwd +"'";

        //获取statement对象
        Statement stmt = conn.createStatement();

        //执行sql
        ResultSet rs = stmt.executeQuery(sql);

        //判断登录是否成功
        if (rs.next()){
            System.out.println("登陆成功~");
        }else {
            System.out.println("登陆失败~");
        }
        //7、释放资源
        rs.close();
        stmt.close();
        conn.close();
    }

 演示SQL注入:

/**
     * 演示SQL注入
     * @throws Exception
     */
    @Test
    public void testLogin_Inject() throws Exception {
        //1、注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2、获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url,username,password);

        //接收用户输入用户名和密码
        String name = "fasdfas";
        String pwd = "'or'1'='1";

        String sql = "select * from tb_user where username = '" + name + "' and password = '"+ pwd +"'";

        //获取statement对象
        Statement stmt = conn.createStatement();

        //执行sql
        ResultSet rs = stmt.executeQuery(sql);

        //判断登录是否成功
        if (rs.next()){
            System.out.println("登陆成功~");
        }else {
            System.out.println("登陆失败~");
        }
        //7、释放资源
        rs.close();
        stmt.close();
        conn.close();
    }

结果:

这为什么呢?我们把sql语句先打印看看 

 System.out.println(sql);

 打印结果:

select * from tb_user where username = 'fasdfas' and password = ''or'1'='1' 

 '1'='1'为永真条件,所以where username = 'fasdfas' and password = ''or'1'='1'的结果无论如何都为true,所以rs对象可以成功获得数据,及可以登陆成功。

解决SQL注入:

1、获取PrepareStatement对象

//SQL语句中的参数值,使用?占位符替代

String sql = "Select * from user where username = ? and password = ?;

//通过Connection对象获取,并传入对应sql语句

PreparedStatement pstmt = conn,prepareStatement(sql);

2、设置参数值

PreparedStatement对象setXxx(参数1,参数2): 给?赋值

Xxx:数据类型;如SetInt(参数1,参数2)

参数:

  • 参数1:?的位置编号,从1开始
  • 参数2:?的值

3、执行sql

executeUpdate();/executeQuery(); //不需要再传递sql

@Test
    public void testPrepareStatement() throws Exception {
        //1、注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2、获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/db1";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url,username,password);

        //接收用户输入用户名和密码
        String name = "zhangsan";
        String pwd = "'or'1'='1";

        //定义sql
        String sql = "select * from tb_user where username = ? and password = ?";

        //获取Preparement对象
        PreparedStatement pstmt = conn.prepareStatement(sql);

        //设置?的值
        pstmt.setString(1,name);
        pstmt.setString(2,pwd);

        //执行sql
        ResultSet rs = pstmt.executeQuery();

        //判断登录是否成功
        if (rs.next()){
            System.out.println("登陆成功~");
        }else {
            System.out.println("登陆失败~");
        }
        //7、释放资源
        rs.close();
        pstmt.close();
        conn.close();
    }

执行结果:

原因:使用Preparement对象使传入字符串发生了转义,MySQL中实际执行的是select * from tb_user where username = 'zhangsan' and password = '\' or \'1\' = \'1' ;

 PreparedStatement原理:

  • PrepareStatement好处
  1. 预编译SQL,性能更高
  2. 防止SQL注入,将敏感字符进行转义

  •  MySQL预编译功能默认没有开启,这时需要我们来手动开启预编译功能,开启方法为,再url后加上useServerPrepStmts=true.
  • 配置MySQL执行日志(重启MySQL服务后生效)

配置mysql执行日志教程

如果运行代码后日志文件里没有更新日志内容,重启mysql即可。

 

然后就ok了

 

 执行预编译的日志文件:

 未预编译执行:

 预编译多次执行时sql时:

 未预编译时: 

原理:

  1. 在获取PreparedDtatement对象,将sql语句发送给MySQL服务器进行检查,编译(这些步骤很耗时)
  2. 执行时就不用再进行这些步骤了,速度更快
  3. 如果sql模板一样,则只需要执行一次检查,编译

四、数据库连接池 

 数据库连接池简介:

  • 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
  • 释放 空闲时间超过最大空闲时间的数据库,来避免因为没有释放数据库连接而引起的数据库连接遗漏
  • 好处:
    • 资源重用
    • 提升系统响应速度
    • 避免数据库连接遗漏

可以把数据库连接池看作一个迎宾接待处,里面有若干个迎宾即连接,来接待客人,根据他们的需求来分配连接,如果有客人长时间消费,就让迎宾去接待下一个客人,这样就充分的利用了迎宾即连接也满足了客人需求。

数据库连接池实现:

  • 标准接口:DataSource
    • 官方(SUN)提供的数据库连接池标准接口,由第三方组织实现此接口。
    • 功能:获取连接

Connection getConnection()

  • 常见的数据库连接池:
    • DBCP
    • C3P0
    • Druid 
  • Druid(德鲁伊)
    • Druid连接池是阿里巴巴开源的数据库连接池项目
    • 功能强大,性能优秀,是Java语言最好的数据库连接池之一

Druid使用步骤:

  1.  导入jar包druid-1.1.12jar
  2. 定义配置文件
  3. 加载配置文件
  4. 获取数据库连接池对象
  5. 获取连接

1、导入jar包druid-1.1.12jar

 导入方式参考JDBC快速入门。下载可到下面这个网址下载Maven Repository: com.alibaba » druid » 1.1.12 (mvnrepository.com)

2、定义配置文件

在src包下新建这样一个文件 

 

 文件内容为:

#druid.properties文件的配置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=root
#初始化连接数量
initialSize=5
#最大连接数
maxActive=10
#最大等待时间
maxWait=3000

 复制过去即可,如果连接出现问题,可以试着把false中的f改成大写,或检查密码是否错误,如果不行,建议到别处查找解决办法。

3、加载配置文件

新建一个类同时可新建一个包。

//3、加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("D:\\JAVA\\Test\\JDBC_Test\\jdbc\\src/druid.properties.properties"));

 除使用绝对路径外,也可以使用相对路径

prop.load(new FileInputStream("src/druid.properties.properties"));

 如果行不通的话,可以在前面加上模块名。

4、获取数据库连接池对象

DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

5、获取连接

Connection connection = dataSource.getConnection();
System.out.println(connection);

 这样就是连接成功了。

介绍了JDBC所有知识点,接下来我们来进行一下实战练习。

练习:

要求:完成商品品牌数据的增删改查操作

  • 查询:查询所有数据
  • 添加:添加品牌
  • 修改:根据id修改
  • 删除:根据id删除

环境准备: 

  • 先建立需要的数据库表
-- 删除tb_brand表
DROP TABLE IF EXISTS tb_brand;
-- 创建tb_brand表
CREATE TABLE tb_brand(
	-- id主键
	id int PRIMARY KEY auto_increment,
	-- 品牌名称
	brand_name VARCHAR(20),
	-- 企业名称
	company_name VARCHAR(20),
	-- 排序字段
	ordered int,
	-- 描述信息
	description VARCHAR(100),
	-- 状态: 0:禁用 1:启用
	status int
)

-- 添加数据
INSERT INTO tb_brand (brand_name,company_name,ordered,description,status)
VALUES ('三只松鼠','三只松鼠股份有限公司',5,'好吃不上火',0),
			 ('华为','华为技术有限公司',100,'华为致力于把数字世界带个每个人、每个家庭,每个组织,构建万物互联的智慧世界',1),
			 ('小米','小米科技有限公司',50,'are you ok',1);
			 
SELECT * FROM tb_brand;
  • 准备实体类
public class Brand {
    //id主键
   private Integer id;
   // 品牌名称
   private String brand_name;
   // 企业名称
   private String company_name;
   // 排序字段
   private Integer ordered;
   // 描述信息
   private String description;
  // 状态: 0:禁用 1:启用
   private Integer status;
   //使用Integer原因:int默认值为0,而状态属性为0表示禁用,可能对业务产生影响;Integer默认值为null,就可以避免上述影响

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBrand_name() {
        return brand_name;
    }

    public void setBrand_name(String brand_name) {
        this.brand_name = brand_name;
    }

    public String getCompany_name() {
        return company_name;
    }

    public void setCompany_name(String company_name) {
        this.company_name = company_name;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brand_name='" + brand_name + '\'' +
                ", company_name='" + company_name + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}
  • 测试用例

 基本步骤大同小异,标红的地方会有差异。

查询所有数据:

  1. 获取Connection
  2. 定义SQL:select * from tb_brand;
  3. 获取PreparedStatement对象
  4. 设置参数:不需要
  5. 执行SQL
  6. 处理结果:List<Brand>
  7. 释放资源
@Test
    public void testSelect() throws Exception {
        //1、获取Connection
        //加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("src/druid.properties.properties"));
        //获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
        //获取数据库连接
        Connection connection = dataSource.getConnection();

        //2、定义SQL
        String sql = "select * from tb_brand;";

        //3、获取pstmt对象
        PreparedStatement pstmt = connection.prepareStatement(sql);

        //4、设置参数

        //5、执行SQL
        ResultSet resultSet = pstmt.executeQuery();

        //6、处理结果 List<Brand> 封装Brand对象,装载List集合
        Brand brand = null;
        List<Brand> brandList = new ArrayList<>();
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String brandName = resultSet.getString("brand_name");
            String companyName = resultSet.getString("company_name");
            int ordered = resultSet.getInt("ordered");
            String description = resultSet.getString("description");
            int status = resultSet.getInt("status");

            brand = new Brand();
            brand.setId(id);
            brand.setBrandName(brandName);
            brand.setCompanyName(companyName);
            brand.setOrdered(ordered);
            brand.setDescription(description);
            brand.setStatus(status);

            //装载集合
            brandList.add(brand);
            System.out.println(brand);
        }


        //7、释放资源
        resultSet.close();
        pstmt.close();
        connection.close();
    }

添加数据:

后面都只列出有差异的地方

1、编写SQL语句                                                                                                                             

insert into tb_brand(brand_name,company_name,ordered,description,status) valuse (?,?,?,?,?);

 2、是否需要参数?需要:除了id外的所有数据

3、返回结果如何封装?boolean

@Test
    public void testAdd() throws Exception {
        //接收页面提交参数,现在还没有学习相关技术,我们先手动生成
        String brandName = "香飘飘";
        String companyName = "香飘飘有限公司";
        int ordered = 1;
        String description = "绕地球一圈";
        int status = 1;
        
        //1、获取Connection
        //加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("src/druid.properties.properties"));
        //获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
        //获取数据库连接
        Connection connection = dataSource.getConnection();

        //2、定义SQL
        String sql = "insert into tb_brand (brand_name,company_name,ordered,description,status) values(?,?,?,?,?);";

        //3、获取pstmt对象
        PreparedStatement pstmt = connection.prepareStatement(sql);

        //4、设置参数
        pstmt.setString(1,brandName);
        pstmt.setString(2,companyName);
        pstmt.setInt(3,ordered);
        pstmt.setString(4,description);
        pstmt.setInt(5,status);
        
        //5、执行SQL
        int count = pstmt.executeUpdate();
        
        //6、处理结果 
        System.out.println(count>0);

        //7、释放资源
        pstmt.close();
        connection.close();
    }

修改:

1、编写SQL语句

update tb_brand
set brand_name = ?,
    company_name = ?,
    ordered = ?,
    description = ?,
    status = ?
where id = ?;

2、是否需要参数?需要:Brand对象所有数据

3、返回结果如何封装

@Test
    public void testUpdate() throws Exception {
        //接收页面提交参数,现在还没有学习相关技术,我们先手动生成
        String brandName = "香飘飘";
        String companyName = "香飘飘有限公司";
        int ordered = 3;
        String description = "绕地球三圈";
        int status = 1;
        int id = 4;

        //1、获取Connection
        //加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("src/druid.properties.properties"));
        //获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
        //获取数据库连接
        Connection connection = dataSource.getConnection();

        //2、定义SQL
        String sql = "update tb_brand\n"+
        "set brand_name = ?,\n"+
        "company_name = ?,\n"+
        "ordered = ?,\n"+
        "description = ?,\n"+
        "status = ?\n"+
        "where id = ?";

        //3、获取pstmt对象
        PreparedStatement pstmt = connection.prepareStatement(sql);

        //4、设置参数
        pstmt.setString(1,brandName);
        pstmt.setString(2,companyName);
        pstmt.setInt(3,ordered);
        pstmt.setString(4,description);
        pstmt.setInt(5,status);
        pstmt.setInt(6,id);

        //5、执行SQL
        int count = pstmt.executeUpdate();

        //6、处理结果
        System.out.println(count>0);

        //7、释放资源
        pstmt.close();
        connection.close();
    }

删除:

1、编写SQL语句

delete from tb_brand where id = ?

2、是否需要参数?需要:id

3、返回结果如何封装?boolean

@Test
    public void testDelete() throws Exception {
        //接收页面提交参数,现在还没有学习相关技术,我们先手动生成
        int id = 4;

        //1、获取Connection
        //加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("src/druid.properties.properties"));
        //获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
        //获取数据库连接
        Connection connection = dataSource.getConnection();

        //2、定义SQL
        String sql = "delete from tb_brand where id = ?";

        //3、获取pstmt对象
        PreparedStatement pstmt = connection.prepareStatement(sql);

        //4、设置参数
        pstmt.setInt(1,id);

        //5、执行SQL
        int count = pstmt.executeUpdate();

        //6、处理结果
        System.out.println(count>0);

        //7、释放资源
        pstmt.close();
        connection.close();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值