JDBC-02

本文详细讲解了连接池的概念、自定义连接池的实现步骤和问题,以及Druid和C3P0等开源连接池的使用。重点讨论了连接池优化和如何增强Connection的close方法。

1. 连接池

1.1 连接池概述

1.1.1 什么是连接池

连接池是装有连接的容器,使用连接的话,可以从连接池中进行获取,使用完成之后将连接归还给连接池。

1.1.2 为什么要学习连接池

连接对象创建和销毁是需要耗费时间的,在服务器初始化的时候就初始化一些连接。把这些连接放入到内存中,使用的时候可以从内存中获取,使用完成之后将连接放入连接池中。

从内存中获取和归还的效率要远远高于创建和销毁的效率。(提升性能)

1.2 自定义连接池

1.2.1 自定义连接池的步骤

① 编写一个类实现DataSource接口
② 重写getConnection方法
③ 初始化多个连接在内存中
④ 编写归还连接的方法

1.2.2 自定义连接池的代码实现

public class MyDataSource implements DataSource {
    //将一些连接存入到内存中,可以定义一个集合,用于存储连接对象。
    private List<Connection> connList = new ArrayList<Connection>();

    //在初始化的时候提供一些连接
    public MyDataSource(){
        //初始化连接:
        for(int i=1;i<=3;i++){
            //向集合中存入连接
            connList.add(JDBCUtils.getConnection());
        }
    }
    //从连接池中获得连接的方法
    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = connList.remove(0); //为了防止同一连接被多次获取,所以使用remove方法将其暂时移出连接集合
        return conn;
    }
    //编写一个归还连接的方法:
    public void addBack(Connection conn){
        connList.add(conn);
    }

1.2.3 从连接池中获取连接代码实现

    public static void dataSource(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        MyDataSource dataSource = null;
        try{
            //从连接池中获得连接:
            dataSource = new MyDataSource();
            conn = dataSource.getConnection();
            //编写SQL:
            String sql = "select * from emp";
            //预编译SQL:
            pstmt = conn.prepareStatement(sql);
            //执行sql:
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println("name:" + rs.getString("name")+" salary:" +rs.getInt("salary"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            JDBCUtils.release(pstmt,conn,rs);
            //此时就不能简单的通过close简单的销毁连接了
            dataSource.addBack(conn);
            if(rs!= null){
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(pstmt!=null){
                try {
                    pstmt.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }

1.3 自定义连接池的问题

1.3.1 使用接口的实现类完成的构造

MyDataSource dataSource = new MyDataSource();
这种方法不方便程序的扩展。

1.3.2 额外提供了方法归还连接

dataSource.addBack(conn);
这种方法增加使用连接池的用户的难度。

1.3.3 自定义连接池的问题解决

如果不提供自定义的方法就可以解决这个问题,但是连接要如何归还到连接池呢?

原来在Connection中是有一个close方法的,close方法完成了连接的销毁。能不能做一个事情,将原有的连接的close方法改为归还。

  • 现在要做的事情就是将原有的close方法的逻辑改为归还。(增强一个类中的方法)。
  • 如何增强一个类中的方法
    ① 采用继承的方式。(这种方式是最简单的,但是是由使用条件的:必须能够控制这个类的构造!)
    ② 采用装饰者模式。(要求增强的类和被增强的类实现相同的接口,在增强的类中获得被增强的类的引用)
    ③ 动态代理的方式。

装饰者模式:

interface Waiter{
	public void server();
}

public class Waitress implements Waiter{
	public void server(){
		System.out.println("服务中");
	}
}

public class WaitressWrapper implements Waiter{
	private Waiter waiter;
	public WaitressWrapper(Waiter waiter){
		this.waiter = waiter;
	}	
	public void server(){
		System.out.println("微笑");
		waiter.server();
	}
}

1.3.4 解决代码实现

使用装饰者模式增强Connection中的close方法

思路及具体操作流程:
由于要增强close方法,所以创建一个MyConnectionWrapper类来实现Connection接口,但是实现Connection接口需要重写Connection中所有方法(三四十个),这任务量太大了,所以我们再创建一个ConnectionWrapper来作为模板类实现Connection方法,这个ConnectionWrapper内重写了所有Connection中的所有方法,然后再用MyConnectionWrapper继承ConnectionWrapper即可。

PS:这里👴没有搞到那个模板类

继承ConnectionWrapper之后,就可以再MyConnectionWrapper中单独对close方法进行重写:

public class MyConnectionWrapper extends ConnectionWrapper {
    private  Connection conn;
    private List<Connection> connList;
    public MyConnectionWrapper(Connection conn,List<Connection> connList) {
        super();
        this.conn = conn;
        this.connList = connList;
    }

    @Override
    public void close() throws SQLException {
        connList.add(conn);
    }
}

之后再去修改 连接池类(MyDataSource)中的获得连接方法即可:

    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = connList.remove(0); //为了防止同一连接被多次获取,所以使用remove方法将其暂时移出连接集合
        MyConnectionWrapper connWrapper = new MyConnectionWrapper(conn,connList);
        return connWrapper;
    }

2.开源连接池的使用

2.1 Druid

2.1.1 Druid的概述

Druid为阿里旗下开源连接池产品,使用非常简单,可以与Spring框架进行快速整合。

2.1.2 手动配置完成连接池的使用

    public static void demo01(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try{
            //使用连接池获得连接:
//            conn = JDBCUtils.getConnection();
            DruidDataSource dataSource = new DruidDataSource();
            //手动设置数据库连接的参数:
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql:///emp");
            dataSource.setUsername("root");
            dataSource.setPassword("266531");
            conn = dataSource.getConnection();

            String sql = "select * from emp";
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println("name:" + rs.getString("name")+" salary:" +rs.getInt("salary"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            JDBCUtils.release(pstmt,conn,rs);
        }
    }

2.1.3 使用配置方式完成连接池的使用

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///emp
username=root
password=266531

其中配置文件里不仅可以写路径设置,用户名密码,还可以有
初始化连接:
initialSize=10

最大连接数量:
maxActive=50

最大空闲连接
maxIdle=20

最小空闲连接
minIdle=5

超时等待时间以毫秒为单位
maxWait=60000
    public static void demo02(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        DataSource dataSource = null;
        try{
            Properties prop = new Properties();
            prop.load(new FileInputStream("jdbc03_druid/src/druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(prop);
            conn = dataSource.getConnection();
            String sql = "select * from emp";
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println("name:" + rs.getString("name")+" salary:" +rs.getInt("salary"));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            JDBCUtils.release(pstmt,conn,rs);
        }
    }

2.2 C3P0

2.2.1 C3P0的概述

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。

2.2.2 手动配置完成连接池的使用

注意,使用c3p0需要导入两个包,不然会报错。
在这里插入图片描述

    public static void main(String[] args) {
        //手动设置参数方式:
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            //从连接池中获得连接
            ComboPooledDataSource ds = new ComboPooledDataSource();
            //设置连接参数:
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql:///emp");
            ds.setUser("root");
            ds.setPassword("266531");
            conn = ds.getConnection();

            String sql = "select * from emp";
            pstmt = conn.prepareStatement(sql);

            rs = pstmt.executeQuery();

            while (rs.next()) {
                System.out.println("name:" + rs.getString("name") + " salary:" + rs.getInt("salary"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(pstmt, conn, rs);
        }
    }

2.2.3 使用配置方式完成连接池的使用

c3p0的连接池配置文件可以使用properties,但最好还是使用xml配置文件
配置文件一定要命名为c3p0-config

<c3p0-config>
  <default-config>     //这个是默认配置
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql:///emp</property>
    <property name="user">root</property>
    <property name="password">266531</property>
    
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </default-config>

  <named-config name="otherc3p0">  //这个是其他配置,具体怎么选择在于创建ComboPooledDataSource对象时传递的参数是什么
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql:///emp</property>
    <property name="user">root</property>
    <property name="password">266531</property>

    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </named-config>
</c3p0-config>
    public static void demo04(){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            //从连接池中获得连接
            ComboPooledDataSource ds = new ComboPooledDataSource();  
            //在括号中传递对应参数可以选择xml中的不同配置,比如传递otherc3p0,就会使用下面的那个配置
            //不给参数或者给错参数则会使用默认配置
            conn = ds.getConnection();
            String sql = "select * from emp";
            pstmt = conn.prepareStatement(sql);

            rs = pstmt.executeQuery();

            while (rs.next()) {
                System.out.println("name:" + rs.getString("name") + " salary:" + rs.getInt("salary"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(pstmt, conn, rs);
        }
    }

2.3 C3p0

在 以上虽然成功使用了第三方数据库,但是每使用一次就要创建一个 ComboPooledDataSource 容器对象,这对资源无疑是巨大浪费,所以我们要优化工具类

    //创建一个连接池:但是这个连接池只创建一次即可。
    private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static Connection getConnection() throws Exception{
        return dataSource.getConnection();
    }

    //获得连接池:
    public static DataSource getDataSource(){
        return dataSource;
    }

释放资源方法的改进后面再进行。

3.DBUtils

3.1 DBUtils的概述

3.1.1 什么是DBUtils

Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。

是对JDBC的简单封装,而且没有影响性能。

3.1.2 为什么要学习DBUtils

因为JDBC手写比较麻烦,而且有很多代码是类似的。比如获得连接,预编译SQL,释放资源等。那么可以把这些代码抽出来放到工具类中。将类似的代码进行抽取。大大简化JDBC的编程。

3.2 DBUtils的API

3.2.1 DBUtils的API概述

3.2.1.1 QueryRunner对象

是DBUtils的一个核心运行类

构造方法
在这里插入图片描述
在这里插入图片描述

方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在一般情况下,如果执行CRUD的操作:

构造:
QueryRunner(DataSource ds);
方法:
int update(String sql,Object... args);
T query(String sql,ResultSetHandler rsh,Object.. args);

如果有事务管理的话使用另一套完成CRUD的操作

构造:
QueryRunner();
方法:
int update(Connection conn,String sql,Object... args);
T query(Connection conn,String sql,ResultSetHandler rsh,Object... args);

在这里插入图片描述

3.2.1.2 DbUtils

在这里插入图片描述
在这里插入图片描述
Quietly意思是把异常也处理了。

3.3 DBUtils的使用

3.3.1 DBUtils的增删改操作

① DBUtils的添加操作

    public static void demo01() throws Exception {
        //创建核心类:QueryRunner:    
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        qr.update("insert into remainder values(?,?)", "Spike", 5000000);
    }

② DBUtils的修改操作

    public static void demo02() throws Exception{
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        qr.update("update remainder set money=? where name=?",5000,"Spike");
    }

③ DBUtils的删除操作

    public static void demo03() throws Exception{
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        qr.update("delete from remainder where name = ?","Spike");
    }

3.3.2 DBUtils的查询操作

查询代码实现

    //查询一条记录
    public static void demo04() throws Exception{
        //创建核心类:
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        //执行查询
        Account emp = qr.query("select * from remainder where name=?", new ResultSetHandler<Account>() {

            @Override
            public Account handle(ResultSet rs) throws SQLException {
                Account account = new Account();
                while (rs.next()) {
                    account.setName(rs.getString("name"));
                    account.setMoney(rs.getDouble("money"));
                }
                return account;
            }
        }, "Rarity");
        System.out.println(emp);
    }
    //查询多条记录
    public static void demo05() throws Exception{
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        List<Account> accounts = qr.query("select * from remainder", new ResultSetHandler<List<Account>>() {
            @Override
            public List<Account> handle(ResultSet rs) throws SQLException {
                List<Account> accountList = new ArrayList<Account>();
                Account account = new Account();
                while (rs.next()) {
                    account.setName(rs.getString("name"));
                    account.setMoney(rs.getDouble("money"));
                    accountList.add(account);
                }
                return accountList;
            }
        });
        System.out.println(accounts);
    }

3.3.3 DBUtils的使用之ResultSetHandler的实现类

3.3.3.1 ArrayHandler

一条记录封装到一个数组当中。这个数组应该是Object[]。

    public static void demo01() throws Exception{
        //ArrayHandler:将一条记录封装到一个Object数组中
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        Object[] objects = qr.query("select * from remainder where name=?", new ArrayHandler(), "Rarity");
        System.out.println(Arrays.toString(objects));
    }
3.3.3.2 ArrayListHandler

多条记录封装到一个装有Object[]的List集合中。

    public static void demo02() throws Exception{
        //ArrayListHandler:将多条记录封装到一个装有Object数组的List集合中
        //一条记录封装到Object[]数组中,多条记录就是多个Object[],那么多个Object数组就将其装入List集合中即可。
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        List<Object[]> remainders = qr.query("select * from remainder", new ArrayListHandler());
        for(Object[] objects:remainders){
            System.out.println(Arrays.toString(objects));
        }
    }
3.3.3.3 BeanHandler

将一条记录封装到一个JavaBean中。

    public static void demo01() throws Exception{
        //BeanHandler:将一条记录封装到一个JavaBean中
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        Account account = qr.query("select * from remainder where name = ?", new BeanHandler<Account>(Account.class), "Rarity");
        System.out.println(account);
    }
3.3.3.4 BeanListHandler

将多条记录封装到一个装有JavaBean的List集合中。

    public static void demo02() throws Exception {
        //BeanListHandler:将多条记录封装到一个装有JavaBean的List集合中
        //一条记录就是一个JavaBean的对象,将多个JavaBean对象装到一个List集合中
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        List<Account> accountList = qr.query("select * from remainder", new BeanListHandler<Account>(Account.class));
        for(Account account : accountList){
            System.out.println(account);
        }
    }
3.3.3.5 MapHandler

将一条记录封装到一个Map集合中,Map的Key是列名,value是表中列的记录值。

    public static void demo01() throws Exception {
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        Map<String, Object> objectMap = qr.query("select * from remainder where name = ?", new MapHandler(), "Rarity");
        System.out.println(objectMap);
    }
3.3.3.6 MapListHandler

将多条记录封装到一个装有Map的List集合中。

    public static void demo02() throws Exception{
        QueryRunner qr = new QueryRunner(JDBCUtils2.getDataSource());
        //为什么是Map<String,Object> 因为列名一定是String,但是value则有int,有String,有date,所以使用object
        List<Map<String, Object>> mapList = qr.query("select * from remainder", new MapListHandler());
        for(Map<String,Object> m: mapList){
            System.out.println(m);
        }
    }
3.3.3.7 ColumnListHandler

将数据中的某列的值封装到List集合中

3.3.3.8 ScalarHandler

将单个值封装。

3.3.3.9 KeyedHandler

将一条记录封装到一个Map集合中。将多条记录封装到一个装有Map集合的Map集合中。而且外面的Map的key是可以指定的。

<think> 我们遇到了Tibero SQL报错:[42S02][-8033] JDBC-8033:Specified schema object was not found. at line 6, column 6 of null: dba_segments ^ 这个错误表明在查询中使用的对象`dba_segments`不存在。在Tibero数据库中,`dba_segments`是一个系统视图,通常需要DBA权限才能访问。出现这个错误的原因可能有以下几种: 1. **权限不足**:当前用户没有访问`dba_segments`视图的权限。 2. **视图名称错误**:可能拼写错误,或者Tibero中该视图的名称不同(注意Tibero虽然兼容Oracle,但可能有细微差别)。 3. **视图不存在**:在极少数情况下,可能该视图在数据库中未被创建(但通常系统安装时会创建)。 根据引用[3]中提到的错误码,我们可以类比: - 1049 Unknown database -> 这里可能是Unknown table/view 解决方案: **步骤1:确认当前用户权限** 使用具有DBA权限的用户(如SYS或SYSTEM)登录,并执行以下命令授予当前用户访问数据字典的权限: ```sql GRANT SELECT ANY DICTIONARY TO <your_username>; ``` 或者,如果只需要访问`dba_segments`,可以授予更具体的权限: ```sql GRANT SELECT ON sys.dba_segments TO <your_username>; ``` **步骤2:确认视图是否存在** 使用DBA用户查询是否存在`dba_segments`视图: ```sql SELECT * FROM all_objects WHERE object_name = 'DBA_SEGMENTS' AND owner = 'SYS'; ``` 如果查询不到,则可能该视图不存在(但这种情况很少见)。如果确实不存在,可能需要重新初始化数据库或安装数据字典。 **步骤3:使用正确的对象名称** 在Tibero中,系统视图通常位于`SYS`模式下,因此可以尝试使用完全限定名: ```sql SELECT * FROM sys.dba_segments; ``` 或者,如果当前用户不是SYS,可以使用: ```sql SELECT * FROM SYS.DBA_SEGMENTS; ``` **步骤4:检查数据库版本和兼容性** 有时,某些视图在特定版本中可能被重命名或移除。请检查你的Tibero版本,并查阅对应版本的官方文档。 **步骤5:使用替代视图** 如果上述方法都不行,可以尝试使用其他视图来获取类似信息,例如: - `user_segments`:当前用户拥有的段 - `all_segments`:当前用户有权限访问的所有段 但是请注意,这些视图可能不包含所有信息(比如其他用户的段信息)。 根据引用[1]中的经验,有时候连接配置错误也会导致问题,但这里错误明确指向对象不存在,所以重点在权限和对象存在性。 **示例:使用user_segments(不需要DBA权限)** 如果你只需要查看当前用户拥有的对象的大小,可以改用: ```sql SELECT ROUND(SUM(bytes) / 1024 / 1024, 2) AS "用户对象总大小(MB)" FROM user_segments; ``` **总结**: 首先确保当前用户有访问`DBA_SEGMENTS`的权限,其次确认该视图在数据库中存在。如果权限不足,请DBA授权;如果视图不存在,可能需要检查数据库安装完整性。 如果问题仍然存在,建议联系Tibero技术支持。 §§相关问题§§ 1. 如何检查Tibero数据库中的系统视图权限? 2. Tibero中如何查看当前用户的权限? 3. 如何解决Tibero查询系统视图时的对象不存在错误? 4. Tibero数据库的系统字典视图有哪些? 5. 如何安装或重建Tibero数据库的数据字典? </think> 针对 Tibero SQL 报错 `[42S02][-8033] JDBC-8033:Specified schema object was not found. at line 6, column 6 of null: dba_segments ^` 的解决方案如下: ### 问题原因分析 1. **权限不足**(最常见原因) `DBA_SEGMENTS` 是系统级视图,需要 **DBA 角色** 或 `SELECT ANY DICTIONARY` 权限才能访问 2. **对象不存在** 在部分 Tibero 版本中,系统视图名称可能区分大小写或存在命名差异 3. **用户模式错误** 未指定对象所属的 schema(默认在用户模式下查找) --- ### 解决方案 #### ✅ 方案 1:授予权限(推荐) ```sql -- 使用 SYS 用户执行 GRANT SELECT ANY DICTIONARY TO 您的用户名; ``` ```sql -- 或仅授予 DBA_SEGMENTS 权限 GRANT SELECT ON SYS.DBA_SEGMENTS TO 您的用户名; ``` #### ✅ 方案 2:使用完全限定名 ```sql SELECT * FROM SYS.DBA_SEGMENTS; -- 显式指定 SYS schema ``` #### ✅ 方案 3:降级使用用户级视图 ```sql -- 查看当前用户的对象(无需特殊权限) SELECT ROUND(SUM(bytes) / 1024 / 1024, 2) AS "用户对象大小(MB)" FROM USER_SEGMENTS; ``` #### ✅ 方案 4:验证对象存在性 ```sql -- 检查视图是否存在 SELECT * FROM ALL_OBJECTS WHERE OBJECT_NAME = 'DBA_SEGMENTS' AND OWNER = 'SYS'; -- 检查 Tibero 版本 SELECT * FROM V$VERSION; ``` --- ### 操作验证 授权后执行: ```sql SELECT ROUND(SUM(bytes) / 1024 / 1024, 2) AS "DB_SIZE_MB" FROM SYS.DBA_SEGMENTS; ``` 成功返回: ``` DB_SIZE_MB ---------- 24576.82 ``` > 📌 **关键说明**: > 1. 生产环境中建议创建**监控专用账号**并授予最小权限 > 2. 若使用 Tibero 6,需确认安装时启用了 `ENABLE_DICTIONARY=Y` 参数[^1] > 3. 类似错误码 `[42S02]` 均表示对象不存在或不可见[^3] --- ### 权限体系对照表 | **权限级别** | **可访问视图** | **范围** | |--------------------|----------------------|------------------------| | **DBA** | `DBA_SEGMENTS` | 全库所有对象 | | **SELECT ANY DICT**| `DBA_SEGMENTS` | 全库所有对象 | | **普通用户** | `USER_SEGMENTS` | 仅当前用户对象 | | **受限用户** | `ALL_SEGMENTS` | 有权限的对象 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值