JDBC之基础知识

JDBC笔记

JDBC开发流程

1.加载并注册JDBC驱动
2.创建数据库连接
3.创建Statement对象
4.遍历查询结果
5.关闭连接,释放资源

MySQL驱动下载地址

https://www.mysql.com/products/connector/

数据库连接过程
String dbDriver = "com.mysql.cj.jdbc.Driver"; //JDBC驱动类
String dbURL = "jdbc:mysql://localhost:3306/imooc" ;//连接字符串
String dbUsername = "root";//数据库用户名
String dbPassword = "123456";//数据库密码
//1.加载并初始化JDBC驱动
Class.forName(dbDriver);
//2.创建数据库连接
Connection connection = DriverManager.getConnection(dbURL,
dbUsername, dbPassword);
Class.forName的作用

Class.forName用于加载指定的JDBC驱动类
Class.forName本质是通知JDBC注册这个驱动类
驱动由数据库厂商自行开发,连接字符串也不同

数据库驱动的连接字符串

截图来自慕课网

DriverManager的作用

DriverManager类用于注册/管理JDBC驱动程序
DriverManager.getConnection(连接字符串,用户名,密码)方法的作用建立数据库的连接
返回值Connection对象,对应数据库的物理网络连接
Connection对象: Connection用于JDBC与数据库的网络通信对象。 java.sql.Connection是一个接口,具体的内容由驱动厂商实现, 所有数据库的操作都建立在Connection对象的基础上

MySQL连接字符串

格式: jdbc:mysql://[主机ip][:端口]/数据库名?参数列表
主机ip与端口是可选设置,默认值为127.0.0.1与3306
参数列表采用url编码,格式:参数1=值1&参数2=值2&…

MySQL连接字符串常用参数

截图来自慕课网

SQL注入攻击

将SQL语句写入输入中从而导致的泄露数据
SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为
SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理
解决方法: 放弃Statement改用PreparedStatement处理SQL
PreparedStatement: 预编译Statement是Statement的子接口
PreparedStatement对SQL进行参数化,预防SQL注入攻击
PreparedStatementkStatement执行效率更高

改进把Statement换为PreparedStatement
/*为预防SQL注入攻击, 将Statement改为PreparedStatement, 将输入值参数化
stmt = conn.createStatement();
// 结果集JDBC接口,用于保存数据库返回结果
rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");*/
String sql = "select * from employee where dname=? and eno>?";
pstmt = conn.prepareStatement(sql);
// 把输入的字符串参数化,第一个参数是位置索引,从1开始,第二个参数时传入参数值。
pstmt.setString(1, pdname);
pstmt.setInt(2, 3500);
rs = pstmt.executeQuery();
           
封装DbUtils工具类

节省每次创建数据库连接和关闭数据库连接的代码量

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @param user 数据库用户名
     * @param password 数据库密码
     * @return 新的Connection对象
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public static Connection getConnection(String user, String password) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
        Connection conn = DriverManager.getConnection(url,user,password);
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果及对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            if(conn != null && !conn.isClosed()){
                conn.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
JDBC实现查询
import java.sql.*;
import java.util.Scanner;

public class QueryCommand implements Command{

    public void execute(){
        System.out.println("请输入部门名称: ");
        Scanner in = new Scanner(System.in);
        String pdname = in.next();
        // Connection、 Statement、 ResultSet都内置了Close方法,放到try/catch块外边扩大生命周期。
        Connection conn = null;
        PreparedStatement pstmt = null;
        // Statement stmt = null;
        ResultSet rs = null;
        /*1.加载并注册JDBC驱动
        2.创建数据库连接
        3.创建Statement对象
        4.遍历查询结果
        5.关闭连接,释放资源*/
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
            conn = DriverManager.getConnection(url,"root","123456");
             /*为预防SQL注入攻击, 将Statement改为PreparedStatement, 将输入值参数化
             stmt = conn.createStatement();
            // 结果集JDBC接口,用于保存数据库返回结果
            rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");*/
            String sql = "select * from employee where dname=? and eno>?";
            pstmt = conn.prepareStatement(sql);
            // 把输入的字符串参数化,第一个参数是位置索引,从1开始,第二个参数时传入参数值。
            pstmt.setString(1, pdname);
            pstmt.setInt(2, 3500);
            rs = pstmt.executeQuery();
            // rs.next( )返回布尔值,代表是否存在下一条记录
            // 如果有,返回true ,同时结果集提取下一条记录
            // 如果没有,返回false,循坏就会停止
            while(rs.next()){
                //可以写两种参数来得到返回值,columnLabel为字段名,columnIndex为字段索引,从1开始。
                Integer eno = rs.getInt(1);
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }

        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 当Connection断开时Statement和ResultSet自动断开,所以没有必要手动关闭
            try {
                if(conn != null && !conn.isClosed()){
                    conn.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }

        }
    }

}
JDBC实现增加新的数据
import com.imooc.jdbc.common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class InsertCommand implements Command {

    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        Float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection("root", "123456");
            String sql = "insert into employee (eno,ename,salary,dname) value (?,?,?,?)";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, eno);
            pstmt.setString(2, ename);
            pstmt.setFloat(3, salary);
            pstmt.setString(4, dname);
            // 所有增、删、改操作都用executeUpdate更新数据库的表,返回影响数据的行数
            Integer cnt = pstmt.executeUpdate();
            System.out.println("cnt:" + cnt);
            System.out.println(ename + "员工入职手续已办理");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null, pstmt, conn);
        }

    }

}
JDBC实现更新数据
import com.imooc.jdbc.common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class UpdateCommand implements Command {
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工新的薪资:");
        Float salary = in.nextFloat();
        Connection conn = null;
        PreparedStatement pstmt = null;


        try {
            conn = DbUtils.getConnection("root", "123456");
            String sql = "update employee set salary=? where eno=?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setFloat(1, salary);
            pstmt.setInt(2, eno);
            // 所有增、删、改操作都用executeUpdate更新数据库的表,返回影响数据的行数
            Integer cnt = pstmt.executeUpdate();
            System.out.println("cnt:" + cnt);
            if(cnt == 1) {

                System.out.println(eno + "员工薪资已调整为:" + salary);
            } else {
                System.out.println("未找到" + eno + "编号员工数据");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(null, pstmt, conn);
        }
}
}
JDBC实现删除数据
import com.imooc.jdbc.common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class DeleteCommand implements Command {


    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;


        try {
            conn = DbUtils.getConnection("root", "123456");
            String sql = "delete from employee where eno=?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, eno);
            // 所有增、删、改操作都用executeUpdate更新数据库的表,返回影响数据的行数
            Integer cnt = pstmt.executeUpdate();
            System.out.println("cnt:" + cnt);
            if(cnt == 1) {

                System.out.println("员工数据已删除");
            } else {
                System.out.println("未找到" + eno + "编号员工数据");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(null, pstmt, conn);
        }
    }
}
JDBC管理事务

JDBC允许两种事务模式
自动提交事务: 自动提交模式是指每执行一次写操作SQL,自动提交事务
自动提交开启方法: conn.setAutoCommit(true)
自动事务是JDBC默认行为,此模式无法保证多数据一致性
手动提交事务: 手动提交模式是指显式调用commit()与rollback()方法管理事务
手动提交开启方法: conn.setAutoCommit(false)
手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法

手动提交事务例子
import com.imooc.jdbc.common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection("root", "123456");
            // 关闭自动事务提交
            conn.setAutoCommit(false);
            String sql = "insert into employee (eno, ename, salary, dname) value (?, ?, ?, ?)";
            for (int i = 1000; i < 2000; i++) {
                if(i == 1005){
                    // 当抛出错误时,由于是手动提交事务,还未执行事务的提交,所以可以进行回滚,不会出现只插入了一部分数据的情况
                    throw new RuntimeException("插入错误");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, i);
                pstmt.setString(2, "员工" + i);
                pstmt.setFloat(3, 4000f);
                pstmt.setString(4, "市场部");
                pstmt.executeUpdate();
            }
            // 手动提交事务
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (conn != null && !conn.isClosed()){
                    conn.rollback();
                }
            } catch (SQLException ex) {
                e.printStackTrace();
            } finally {
                DbUtils.closeConnection(null, pstmt, conn);
            }

        }
    }
}
将数据封装为实体类

实体类 Java bean 的格式要求:

  1. 具备默认构造函数
  2. 属性私有
  3. 存在getter与setter
    注:实体类的名字应尽量与表名相同,属性名应与数据的字段名相同
员工数据的实体类
public class Employee {
    
    public Employee(){

    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }
}
数据分页代码
import com.imooc.jdbc.common.DbUtils;
import com.imooc.jdbc.hrapp.entity.Employee;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class PaginationCommand implements Command {

    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList<>();
        try {
            conn = DbUtils.getConnection("root", "123456");
            String sql = "select * from employee limit ?, 10";
            pstmt = conn.prepareStatement(sql);
            // 分页数据,MySql的limit从0开始,第二个参数为偏移量,每次提取10个数据
            pstmt.setInt(1,(page-1)*10);
            rs = pstmt.executeQuery();
            while (rs.next()){
               Integer eno = rs.getInt("eno");
               String ename = rs.getString("ename");
               Float salary = rs.getFloat("salary");
               String dname = rs.getString("dname");
               // 将数据的值传入到实体类中并用add添加到列表中
                Employee emp = new Employee();
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                list.add(emp);
                System.out.println(list);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(rs, pstmt, conn);
        }

    }
}
JDBC处理日期对象

date类型只精确到天,
datetime类型精确到秒,但二者的处理方式在JDBC中相同。
java.util.Date 为java.sql.Date的父类,当查询时,可直接用JDBC的 java.util.Date接收数据库中的java.sql.Date日期类型数据。修改数据时,要先将输入的字符串转为java.sql.Date类型的数据再传入数据库中,也可以先将数据转为java.util.Date类型再转为java.sql.Date类型。

// 实体类中的代码
// 此处创建为 java.util.Date 类型
private Date hiredate;

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
 // 查询数据日期类的代码
  Date hiredate = rs.getDate("hiredate");
  emp.setHiredate(hiredate);
  list.add(emp);
// 新增数据日期类型的代码
 java.sql.Date hiredate = null;
        while (true){
        try {
            System.out.println("请输入入职日期:");
            String strHiredate = in.next();
            hiredate = java.sql.Date.valueOf(strHiredate);
            break;
        } catch (Exception e) {
            System.out.println("日期格式有误。");
            continue;
        }
        }
//  String 转为java.util.Date再转为java.sql.Date类型的代码
System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        long time = udHiredate.getTime();//获取自1970年的到设置日期为止的毫秒数
        java.sql.Date sdHiredate = null;
        sdHiredate = new java.sql.Date(time);
JDBC数据批处理
// 3行把代码改为批处理
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BatchSample {

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection("root", "123456");
            conn.setAutoCommit(false);
            String sql = "insert into employee (eno, ename, salary, dname) value (?, ?, ?, ?)";
            // 1.将 PreparedStatement 解析 sql 语句放到循环外执行
            pstmt = conn.prepareStatement(sql);
            for (int i = 1000; i < 2000; i++) {

                pstmt.setInt(1, i);
                pstmt.setString(2, "员工" + i);
                pstmt.setFloat(3, 4000f);
                pstmt.setString(4, "市场部");
                // 2.把pstmt添加到批处理的任务中
                pstmt.addBatch();
            }
            // 3.执行批处理任务
            pstmt.executeBatch();
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if (conn != null && !conn.isClosed()){
                    conn.rollback();
                }
            } catch (SQLException ex) {
                e.printStackTrace();
            } finally {
                DbUtils.closeConnection(null, pstmt, conn);
            }

        }
    }
}
连接池
Druid连接池

Druid是阿里巴巴开源连接池组件,是最好的连接池之一
Druid对数据库连接进行有效管理与重用,最大化程序执行效率
连接池负责创建管理连接,程序只负责取用与归还
在应用程序启动的时候连接池的连接被初始化并创建

Druid连接池下载地址

https://github.com/alibaba/druid

创建连接池
  1. 在lib目录下添加jar包
    添加jar包
  2. 点击File -> Project Structure -> Modules -> Dependencies 添加工程依赖
  3. 在src目录下编写driud-config.properties配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username=root
password=123456
initialSize=10
maxActive=20
  1. Druid连接池创建代码
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.common.DbUtils;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
 * Druid连接池配置与使用
 */

public class DruidSample {
    public static void main(String[] args) {
        // 1. 加载属性文件
        Properties properties = new Properties();
        // 获取文件路径

        System.out.println(DruidSample.class.getResource("/druid-config.properties").getPath());
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        try {
            // getPath()方法会对url进行编码,需要用decode解码为原来的路径字符串
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            // 加载配置文件
            properties.load(new FileInputStream(propertyFile));
        } catch (Exception e) {
            e.printStackTrace();
        }

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 2. 获取DataSource数据源 / 数据库对象
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            // 3. 创建数据库连接
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0, 10");
            rs = pstmt.executeQuery();
            while(rs.next()){
                //可以写两种参数来得到返回值,columnLabel为字段名,columnIndex为字段索引,从1开始。
                Integer eno = rs.getInt(1);
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            /** 连接关闭的区别
             * 不使用连接池: conn.close() 关闭连接
             * 使用连接池: conn.close() 将连接回收至连接池
             */
            DbUtils.closeConnection(rs, pstmt, conn);
        }
    }
}
Apache Commons DBUtils

commons-dbutils是Apache提供的开源JDBC工具类库
它是对JDBC的简单封装,学习成本极低
使用commons-dbutils可以极大简化JDBC编码工作量

Apache Commons DBUtils下载地址

https://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.hrapp.entity.Employee;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 *  Apache DbUtils + Druid联合使用例子
 */

public class DbUtilsSample {
    public static void query(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();

        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            // 使用DbUtils进行查询操作
            // 如果添加dataSource, qr.query方法可以自己创建连接和关闭连接
            QueryRunner qr = new QueryRunner(dataSource);
            //Object[]{10}用来按顺序存放sql语句中的参数
            List<Employee> list = qr.query("select * from employee limit ? , 10", new BeanListHandler<>(Employee.class), new Object[]{10});
            for(Employee emp: list){
                System.out.println(emp.getEname());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    public static void update(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        Connection conn = null;

        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            conn = dataSource.getConnection();
            // 使用DbUtils进行查询操作
            // 如果自己创建连接则不需要传入dataSource参数
            QueryRunner qr = new QueryRunner();
            String sql1 = "update employee set salary=salary+1000 where eno=?";
            String sql2 = "update employee set salary=salary-500 where eno=?";
            qr.update(conn,sql1, new Object[]{1000});
            qr.update(conn,sql2, new Object[]{1001});
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (conn != null && !conn.isClosed()) {
                    conn.close();
                }
            } catch(SQLException e){
                throw new RuntimeException(e);
            }

        }

    }

    public static void main(String[] args) {
        query();
        update();

    }

}

注: 可用 show full processlist 命令查看当前MySQL数据库的连接情况。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值