【JDBC】

【JDBC】

JDBC的基本概念

概念:Java Database Connectivity,Java 数据库连接,Java语言操作数据库。

JDBC本质:希望使用统一的一套Java代码可以操作所有的关系型数据库**,JDBC定义了一套操作所有关系型数据库的规则(即接口),每一个数据库厂商都去实现这套接口,提供数据库驱动jar包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。**

快速入门

步骤:

  1. 导入驱动jar包 mysql-connector-java-8.0.11.jar
    • 复制jar包到项目的libs目录下
    • 右键 --> Add as Library
  2. 注册驱动
  3. 获取数据库的连接对象 Connection
  4. 定义sql
  5. 获取执行sql语句的对象 Statement
  6. 执行sql,接收返回的结果
  7. 处理结果
  8. 释放资源
/**
 * JDBC快读入门
 */
public class JdbcDemo01 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1. 导入驱动jar包 `mysql-connector-java-8.0.11.jar`
        //  -  复制jar包到项目的libs目录下
        //  -  右键 --> Add as Library
        //2. 注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //3. 获取数据库的连接对象 Connection
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT", "root", "root");
        //4. 定义sql
        String sql = "UPDATE account SET balance = 1000 WHERE id = 1";
        //5. 获取执行sql语句的对象 Statement
        Statement statement = connection.createStatement();
        //6. 执行sql,接收返回的结果
        int count = statement.executeUpdate(sql);
        //7. 处理结果
        System.out.println(count);
        //8. 释放资源
        statement.close();
        connection.close();

    }
}

详解各个类/接口

  1. DriverManager:驱动管理对象。
  2. Connection:数据库连接对象。
  3. Statement:执行sql的对象。
  4. ResultSet:结果集对象。
  5. PreparedStatement:执行sql的对象,功能比父接口Statement更强大。

DriverManager

功能:

  1. 注册驱动;
  2. 获取数据库连接。

注册驱动:(告诉程序该使用哪一个数据库驱动jar包)

static void registerDriver(Driver driver):注册与给定的驱动DriverManager。

写代码时使用:Class.forName("com.mysql.cj.jdbc.Driver");

通过查看源码发现:在com.mysql.cj.jdbc.Driver类中存在静态代码块:

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

注意:mysql5之后的驱动jar包中可以省略注册驱动的步骤,但是建议还是写上注册启动语句。

原因:在jar包 --> META-INF --> services中存在一个java.sql.Driver文件,里面有com.mysql.cj.jdbc.Driver语句,可以自动注册驱动。

获取数据库连接:

方法:

static Connection getConnection(String url, String user, String password){};

参数:

  • url:指定连接的路径

    • mysql的语法:"jdbc:mysql://ip地址名(域名):端口号/数据库名称"

    • 细节:如果连接的是本机的mysql服务器,并且mysql服务默认端口号是3306,则url可以简写为:

      "jdbc:mysql:///数据库名称"

  • user:用户名

  • password:密码

Connection

功能:

  1. 获取执行sql的对象:
方法含义
Statement createStatement()创建一个 Statement对象,用于将SQL语句发送到数据库。
PreparedStatement prepareStatement(String sql)创建一个 PreparedStatement对象,用于将参数化SQL语句发送到数据库。
  1. 管理事务:

    • 开启事务:
    void setAutoCommit(boolean autoCommit)//调用该方法设置参数为false,即开启事务,关闭自动提交
    • 回滚事务:
    void rollback()
    • 提交事务:
    void commit()

Statement【重点】

功能:

  1. 执行sql:
方法含义
boolean execute(String sql)执行给定的SQL语句,该语句可能返回多个结果。 【了解】
int executeUpdate(String sql)执行给定的DML语句,这可能是 INSERTUPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。
ResultSet executeQuery(String sql)执行给定的DQL(SELECT)语句,该语句返回单个 ResultSet对象。

注意:executeUpdate(String sql)可以通过返回的行数判断DML语句是否执行成功。

练习
  1. account表中添加一条记录
  2. account表中修改记录
  3. account表中删除一条记录
  4. 创建一个表student,里面有id,name,age三列
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcDemo03 {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取数据库的连接对象
            connection = DriverManager.getConnection("jdbc:mysql:///practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT", "root", "root");
            //3.定义sql语句
            //3.1 account表中添加2条记录
            String sql1 = "INSERT INTO account VALUES(NULL,\"赵六\",1000),(NULL,\"田七\",1000)";
            //3.2 account表中修改记录:
            String sql2 = "UPDATE account SET NAME = \"赵敏\",balance = 3000 where  NAME = \"赵六\"";
            //3.3 account表中删除2条记录
            String sql3 = "DELETE FROM account where id = 2 OR id = 3";
            //3.4 创建一个表student,里面有id,name,age三列
            String sql4 = "CREATE TABLE student(id int Primary Key Auto_increment,name varchar(32),age int)";
            //4.获取.获取执行sql语句的对象 Statement
            statement = connection.createStatement();
            //5.执行sql语句
            int count1 = statement.executeUpdate(sql1);
            int count2 = statement.executeUpdate(sql2);
            int count3 = statement.executeUpdate(sql3);
            statement.executeUpdate(sql4);
            System.out.println("数据添加成功!"+count1+"行数据受到影响!");
            System.out.println("数据修改成功!"+count2+"行数据受到影响!");
            System.out.println("数据删除成功!"+count3+"行数据受到影响!");

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            //7.释放资源
            //先判断是否为null,避免空指针异常
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}

ResultSet

ResultSet对象:结果集对象,封装查询结果。

方法含义
boolean next()将光标从当前位置向前移动一行,判断当前行是否是最后一行末尾(是否有数据)
Xxx getXxx(int index)获取Xxx类型的数据,参数index代表列数,从1开始
Xxx getXxx(String str)获取Xxx类型的数据,参数str代表列的名称
//定义sql语句
String sql = "SELECT * FROM account";
//5.执行sql语句
resultSet = statement.executeQuery(sql);
//游标向下移动一行
resultSet.next();
//获得并打印第一行的数据
int id = resultSet.getInt(1);
String name = resultSet.getString("name");
double balance = resultSet.getDouble(3);
System.out.println(id+"--"+name+"--"+balance);

注意:ResultSet对象创建后,要.close释放资源。

Result遍历结果集
while(resultSet.next()) {
    //获得并打印第一行的数据
    int id = resultSet.getInt(1);
    String name = resultSet.getString(2);
    double balance = resultSet.getDouble(3);
    System.out.println(id + "--" + name + "--" + balance);
}

练习

需求:以上一节多表查询练习中的emp表为例,查询emp表中的数据,并将其封装为对象,将对象存储在集合中,然后遍历集合打印。

分析:

emp表中的每一列就相当于类的每一个属性,emp表中的每一行数据,就相当于是该类的每一个对象。所以可以新建一个Emp类,将emp表的每一行数据都封装为一个对象。

在domain包中,定义一个Emp类:

package cn.kaikeba.domain;

import java.util.Date;

public class Emp {
    private int id;
    private String name;
    private int job_id;
    private int mgr;
    private Date joindate;
    private double salary;
    private double bonus;
    private int dept_id;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getJob_id() {
        return job_id;
    }

    public void setJob_id(int job_id) {
        this.job_id = job_id;
    }

    public int getMgr() {
        return mgr;
    }

    public void setMgr(int mgr) {
        this.mgr = mgr;
    }

    public Date getJoindate() {
        return joindate;
    }

    public void setJoindate(Date joindate) {
        this.joindate = joindate;
    }

    public double getSalary() {
        return salary;
    }

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

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public int getDept_id() {
        return dept_id;
    }

    public void setDept_id(int dept_id) {
        this.dept_id = dept_id;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", job_id=" + job_id +
                ", mgr=" + mgr +
                ", joindate=" + joindate +
                ", salary=" + salary +
                ", bonus=" + bonus +
                ", dept_id=" + dept_id +
                '}';
    }
}

使用Jdbc,读取表中数据,将每一行封装为一个对象,存储到集合中,遍历集合打印对象:

package cn.kaikeba.jdbc;

import cn.kaikeba.domain.Emp;

import java.sql.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class JdbcDemo04 {
    public static void main(String[] args) {
        //8.调用方法获取集合
        List<Emp> list = findAll();
        //9.遍历集合,获取并打印对象
        //增强for循环遍历:
        for (Emp emp : list) {
            System.out.println(emp);
        }
        System.out.println("------------------------------------------------------------------");
        //迭代器遍历:
        Iterator<Emp> iterator = list.iterator();
        while (iterator.hasNext()){
            Emp emp = iterator.next();
            System.out.println(emp);
        }
    }

    /**
     * 查询所有emp对象,存储到集合中并返回
     * @return
     */
    public static List<Emp> findAll(){
        List<Emp> list = new ArrayList<>();
        Connection connection = null;
        Statement statement = null;
        ResultSet rs = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取Connection对象
            connection = DriverManager.getConnection("jdbc:mysql:///practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT", "root", "root");
            //3.获取执行sql语句的Statement对象
            statement = connection.createStatement();
            //4.定义sql语句
            String sql = "SELECT * FROM emp";
            //5.执行sql语句,获取结果集对象rs
            rs = statement.executeQuery(sql);
            //6.遍历结果集,将获得的数据存储到emp对象中,并将emp对象存储到集合中
            while(rs.next()){
                Emp emp = new Emp();
                emp.setId(rs.getInt("id"));
                emp.setBonus(rs.getDouble("bonus"));
                emp.setDept_id(rs.getInt("dept_id"));
                emp.setJob_id(rs.getInt("job_id"));
                emp.setJoindate(rs.getDate("joindate"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setName(rs.getString("ename"));
                emp.setSalary(rs.getDouble("salary"));
                list.add(emp);
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            //7.释放资源
            //先判断是否为null,避免空指针异常
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return list;
    }
}

JDBC工具类

抽取JDBC工具类:JDBCUtils。

  • 目的:简化书写。

  • 分析:

    1. 抽取注册驱动的方法

    2. 抽取获取连接对象的方法

      • 需求:不想传递参数(麻烦),还得保证工具类的通用性。

      • 解决:配置文件

        jdbc.properties

        url = …

        user = …

        password = …

    3. 抽取释放资源的方法

将相关配置信息写进配置文件jdbc.properties中:

url=jdbc:mysql:///practise?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
user=  root
password = root
driver = com.mysql.cj.jdbc.Driver

编写JDBCUtil类:

mport java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;

/**
 * JDBC工具类
 */
public class JDBCUtil {
    private static String url;
    private static String user;
    private static String password;
    /**
     * 文件的读取,只需要读取一次即可拿到这些值,使用静态代码块
     */
    static{
        //读取资源文件,获取值
        try {
            //1.创建Properties集合类对象
            Properties pro = new Properties();
            //获取src路径下的文件的方式-->Classload 类加载器
            ClassLoader classLoader = JDBCUtil.class.getClassLoader();
            URL resource = classLoader.getResource("jdbc.properties");
            String path = resource.getPath();
            //2.加载文件
            pro.load(new FileReader(path));

            //3.获取属性,赋值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            Class.forName(pro.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
    /**
     * 获取连接
     * @return 连接对象
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url,user,password);
    }

    /**
     * 释放资源1
     * @param statement
     * @param connection
     */
    public static void close(Statement statement,Connection connection){
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

    }

    /**
     * 释放资源2
     * @param statement
     * @param connection
     * @param resultSet
     */
    public static void close(Statement statement, Connection connection, ResultSet resultSet){
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

    }
}

使用JDBCUtil工具类对快速入门的代码进行修改:

public class JdbcDemo01 {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        try {
            //1.注册驱动
            //2.创建数据库连接对象,数据库信息在配置文件中修改
            connection = JDBCUtil.getConnection();
            //4. 定义sql
            String sql = "INSERT INTO account VALUES(null,\"王五\",1000)";
            //5. 获取执行sql语句的对象 Statement
            statement = connection.createStatement();
            //6. 执行sql,接收返回的结果
            int count = statement.executeUpdate(sql);
            //7. 处理结果
            System.out.println(count);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //8. 释放资源
            JDBCUtil.close(statement, connection);
        }

    }
}

练习

需求:通过键盘录入用户名和密码,判断用户是否登录成功,如登录成功则提示登陆成功,失败则体是登录失败。

步骤:

1.创建一个数据库表,里面存储用户的用户名和密码;

2.创建一个类,里面有一个判断接收的账号密码在数据库表中是否存在的方法。

.创建一个数据库表:

CREATE TABLE USER(
id int PRIMARY KEY auto_increment,
username VARCHAR(20),
PASSWORD varchar(20)
);

INSERT INTO USER VALUES(null,"zhangsan","123");
INSERT INTO USER VALUES(null,"lisi","234");

创建一个类,里面有一个判断接收的账号密码在数据库表中是否存在的方法:

import cn.kaikeba.util.JDBCUtil;

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

/**
 * 需求:通过键盘录入用户名和密码,判断用户是否登录成功,如登录成功则提示登陆成功,失败则体是登录失败。
 */
public class JdbcDemo05 {
    public static void main(String[] args) {
        //接收用户输入
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        System.out.println("请输入密码:");
        String password = input.next();
        JdbcDemo05 jdbcDemo05 = new JdbcDemo05();
        boolean login = jdbcDemo05.login(username, password);
        if (login){
            System.out.println("登陆成功!");
        }else{
            System.out.println("用户名或密码错误!");
        }
    }
    /**
     * 登录方法
     */
    public boolean login(String username,String password){
        if (username == null || password == null){
            return false;
        }
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            //1.注册驱动
            //2.创建数据库连接对象,数据库信息在配置文件中修改
            connection = JDBCUtil.getConnection();
            //4. 定义sql
            String sql = "Select * From user WHERE username = '"+username+"' and password = '"+password+"'";
            //5. 获取执行sql语句的对象 Statement
            statement = connection.createStatement();
            //6. 执行sql,接收返回的结果
            resultSet = statement.executeQuery(sql);
            //7. 判断结果集中是否有参数中的帐号密码
            return resultSet.next();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //8. 释放资源
            JDBCUtil.close(statement, connection,resultSet);
        }
        return false;
    }
}

SQL注入问题

SQL注入问题:在拼接sql语句时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题。

如,在上面用户登录的练习中:

用户名随便输入,密码输入:

a' or 'a' = 'a

这时无论如何都可以登陆成功。

sql语句为:

Select * From user WHERE username = username and password = 'a' or 'a' = 'a'

解决SQL注入问题:使用PreparedStatement对象来解决。

PreparedStatement中的SQL语句是预编译SQL。

预编译SQL:参数使用?作为占位符。

注意:后期都会使用PreparedStatement来完成增删改查的所有操作

  1. 可以防止sql注入
  2. 效率更高

步骤:

  1. 导入驱动jar包 mysql-connector-java-8.0.11.jar
  2. 注册驱动
  3. 获取数据库的连接对象 Connection
  4. 定义sql
    • 注意:sql的参数使用?作为占位符,如:Select * From user WHERE username =?and password = ?;
  5. 获取执行sql语句的对象 PreparedStatement,调用传入预编译sql语句
  6. 给?赋值:
    • 方法,setXxx(参数1,参数2)
      • 参数1:?的位置编号 从1开始
      • 参数2:?的值
  7. 执行sql,接收返回的结果,不需要传递sql语句
  8. 处理结果
  9. 释放资源

上面的用户登录案例可以进行如下改进:

import cn.kaikeba.util.JDBCUtil;

import java.sql.*;
import java.util.Scanner;

/**
 * 需求:通过键盘录入用户名和密码,判断用户是否登录成功,如登录成功则提示登陆成功,失败则体是登录失败。
 */
public class JdbcDemo05 {
    public static void main(String[] args) {
        //接收用户输入
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        System.out.println("请输入密码:");
        String password = input.next();
        JdbcDemo05 jdbcDemo05 = new JdbcDemo05();
        boolean login = jdbcDemo05.login(username, password);
        if (login){
            System.out.println("登陆成功!");
        }else{
            System.out.println("用户名或密码错误!");
        }
    }
    /**
     * 登录方法
     */
    public boolean login(String username,String password){
        if (username == null || password == null){
            return false;
        }
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //1.注册驱动
            //2.创建数据库连接对象,数据库信息在配置文件中修改
            connection = JDBCUtil.getConnection();
            //4. 定义sql
            String sql = "Select * From user WHERE username = ? and password = ? ";
            //5. 获取执行sql语句的对象 Statement,不需要传递sql语句
            preparedStatement = connection.prepareStatement(sql);
            //给? 赋值
            preparedStatement.setString(1,username);
            preparedStatement.setString(2,password);
            //执行sql,接收返回的结果
            resultSet = preparedStatement.executeQuery();
            //7. 判断结果集中是否有参数中的帐号密码
            return resultSet.next();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //8. 释放资源
            JDBCUtil.close(preparedStatement, connection,resultSet);
        }
        return false;
    }
}

JDBC控制事务

事务:一个包含多个步骤的业务操作。如果这个业务被事务管理,则这多个步骤要么同时成功,要么同时失败。

操作:

  1. 开启事务
  2. 回滚事务
  3. 提交事务

使用Connection对象来管理事务:

  • 开启事务:在执行sql之前开启事务
void setAutoCommit(boolean autoCommit)//调用该方法设置参数为false,即开启事务,关闭自动提交
  • 回滚事务:在catch中写回滚事务的代码,当有异常发生时执行代码,回滚事务
void rollback()
  • 提交事务:当所有sql都执行完就提交事务
void commit()

转账案例

import cn.kaikeba.util.JDBCUtil;

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

/**
 * 事务操作
 */
public class JdbcDemo06 {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement1 = null;
        PreparedStatement preparedStatement2 = null;
        try {
            //1.注册驱动,获取连接
            connection = JDBCUtil.getConnection();
            //开启事务,将自动提交设为关:
            connection.setAutoCommit(false);
            //2.定义sql
            //?转出?元
            String sql1 = "UPDATE account set balance = balance - ? where name = ?";
            //?接收?元
            String sql2 = "UPDATE account set balance = balance + ? where name = ?";
            //3.获取执行sql对象
            preparedStatement1 = connection.prepareStatement(sql1);
            preparedStatement2 = connection.prepareStatement(sql2);
            //4.设置参数
            //转出500元
            preparedStatement1.setDouble(1,500);
            //接收500元
            preparedStatement2.setDouble(1,500);
            //张三转出
            preparedStatement1.setString(2,"张三");
            //李四收钱
            preparedStatement2.setString(2,"李四");
            //5.执行sql语句
            int count1 = preparedStatement1.executeUpdate();
            int i = 3/0;
            int count2 = preparedStatement2.executeUpdate();
            //提交事务
            connection.commit();
        } catch (Exception throwables) {
            //如果出现异常,事务回滚
            if (connection != null){
                try {
                    connection.rollback();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            throwables.printStackTrace();
        }finally{
            JDBCUtil.close(preparedStatement1,connection);
            JDBCUtil.close(preparedStatement2,null);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值