第三方连接池

C3P0连接池

  • C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml.

  • 使用C3P0需要添加c3p0-0.9.1.2.jar

  • 创建C3P0连接池对象

  • 设置连接池参数

  • 获得连接

  • 预编译sql语句,得到预编译对象

  • 设置sql语句参数

  • 执行sql语句,处理结果

  • 释放资源

public class Test1_硬编码方式 {
    public static void main(String[] args) throws Exception{
        //- 创建C3P0连接池对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        //- 设置连接池参数
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/day19_1");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setInitialPoolSize(5);

        //- 获得连接
        Connection connection = dataSource.getConnection();

        //- 预编译sql语句,得到预编译对象
        String sql = "select * from user where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        //- 设置sql语句参数
        ps.setInt(1,2);

        //- 执行sql语句,处理结果
        ResultSet resultSet = ps.executeQuery();
        // 定义一个User变量
        User user = null;
        while (resultSet.next()) {
            // 取值
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            // 封装数据
            user = new User(id,username,password,nickname);
        }

        System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数
        System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数
        System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数

        //- 释放资源
        JDBCUtils.release(resultSet,ps,connection);
        System.out.println(user);


        Thread.sleep(5000);
        System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数
        System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数
        System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数
    }
}

  • 拷贝c3p0-config.xml配置文件到src路径下,然后修改配置文件中的参数值

    • c3p0-config.xml文件必须放在src路径下,否则会报错

    • c3p0-config.xml文件名必须是c3p0-config.xml,不能修改

    • c3p0-config.xml文件中property标签中的name属性值不能修改

public class Test2_配置文件方式 {
    public static void main(String[] args) throws Exception{
        //- 创建C3P0连接池对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        //- 获得连接
        Connection connection = dataSource.getConnection();

        //- 预编译sql语句,得到预编译对象
        String sql = "select * from user where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        //- 设置sql语句参数
        ps.setInt(1,2);

        //- 执行sql语句,处理结果
        ResultSet resultSet = ps.executeQuery();
        // 定义一个User变量
        User user = null;
        while (resultSet.next()) {
            // 取值
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            // 封装数据
            user = new User(id,username,password,nickname);
        }

        System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数
        System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数
        System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数

        //- 释放资源
        JDBCUtils.release(resultSet,ps,connection);
        System.out.println(user);


        Thread.sleep(5000);
        System.out.println("正在使用的:"+dataSource.getNumBusyConnections());// 正在使用连接数
        System.out.println("正在空闲的:"+dataSource.getNumIdleConnections());// 空闲连接数
        System.out.println("总的连接数:"+dataSource.getNumConnections());// 总连接数
    }
}
 

使用c3p0改写工具类【重点】

  • 问题: 每次需要连接的时候,都需要创建连接池对象,用完了就销毁,所以就会不断的创建连接池,销毁连接池

  • 解决: 整个程序只需要创建一个连接池对象,其余地方直接使用这个唯一的连接池对象获得连接即可

  • 工具类:

    • 思路:

      • 创建唯一的连接池对象---->private static final修饰

      • 提供一个获取连接池对象的静态方法

      • 提供一个获取连接的静态方法

      • 提供一个释放资源的静态方法

public class C3P0Utils {
    //- 创建唯一的C3P0连接池对象
    private static final ComboPooledDataSource DATASOURCE = new ComboPooledDataSource();

    /**
     * 获得连接池的方法
     * @return
     */
    public static DataSource getDataSource(){
        return DATASOURCE;
    }

    /**
     * 获得连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = DATASOURCE.getConnection();
        return connection;
    }

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

public class Test3_测试工具类 {
    public static void main(String[] args) throws Exception{
        // 获得连接池
        Connection connection = C3P0Utils.getConnection();

        //- 预编译sql语句,得到预编译对象
        String sql = "select * from user where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        //- 设置sql语句参数
        ps.setInt(1,2);

        //- 执行sql语句,处理结果
        ResultSet resultSet = ps.executeQuery();
        // 定义一个User变量
        User user = null;
        while (resultSet.next()) {
            // 取值
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            // 封装数据
            user = new User(id,username,password,nickname);
        }

        //- 释放资源
        C3P0Utils.release(resultSet,ps,connection);
        System.out.println(user);

    }
}

DRUID连接池

public class Test1_硬编码方式 {
    public static void main(String[] args) throws Exception{
        //- 创建Druid连接池对象
        DruidDataSource dataSource = new DruidDataSource();

        //- 设置连接池的配置参数
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/day19_1");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setInitialSize(5);

        //- 获得连接
        Connection connection = dataSource.getConnection();

        //- 预编译sql语句,得到预编译对象
        String sql = "select * from user where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        //- 设置sql语句参数
        ps.setInt(1,2);

        //- 执行sql语句,处理结果
        ResultSet resultSet = ps.executeQuery();
        // 定义一个User变量
        User user = null;
        while (resultSet.next()) {
            // 取值
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            // 封装数据
            user = new User(id,username,password,nickname);
        }

        System.out.println("正在使用的:"+dataSource.getActiveCount());// 1
        System.out.println("正在空闲的:"+dataSource.getPoolingCount());// 4

        //- 释放资源
        JDBCUtils.release(resultSet,ps,connection);
        System.out.println(user);
        System.out.println("正在使用的:"+dataSource.getActiveCount());// 0
        System.out.println("正在空闲的:"+dataSource.getPoolingCount());// 5
    }
}

通过配置文件方式【重点】

  • 思路:

    • 导入Druid的jar包

    • 拷贝druid.properties配置文件到src路径下

      • druid.properties配置文件建议放在src路径下,可以修改

      • druid.properties配置文件名建议是druid.properties,可以修改

      • druid.properties配置文件的键名不能修改,必须是set方法去掉set,然后首字母变小写

    • 使用:

      • 创建Properties对象,加载配置文件中的数据

      • 创建Druid连接池对象,传入Properties对象

      • 获得连接

      • 预编译sql语句,得到预编译对象

      • 设置sql语句参数

      • 执行slq语句,处理结果

      • 释放资源

public class Test2_配置文件方式 {
    public static void main(String[] args) throws Exception{
        // - 创建Properties对象
        Properties pro = new Properties();

        // - 加载配置文件中的数据
        FileInputStream fis = new FileInputStream("day20\\src\\druid.properties");
        pro.load(fis);

        //- 创建Druid连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);

        //- 获得连接
        Connection connection = dataSource.getConnection();

        //- 预编译sql语句,得到预编译对象
        String sql = "select * from user where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        //- 设置sql语句参数
        ps.setInt(1,4);

        //- 执行sql语句,处理结果
        ResultSet resultSet = ps.executeQuery();
        // 定义一个User变量
        User user = null;
        while (resultSet.next()) {
            // 取值
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            // 封装数据
            user = new User(id,username,password,nickname);
        }

        //- 释放资源
        JDBCUtils.release(resultSet,ps,connection);
        System.out.println(user);

    }
}

Druid工具类的制作

public class DruidUtils {

    // 定义DataSource成员变量
    private static DataSource dataSource;

    static {
        try {
            // - 创建Properties对象
            Properties pro = new Properties();

            // - 加载配置文件中的数据
            InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            pro.load(is);

            //- 创建Druid连接池对象
            dataSource = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    /**
     * 获得连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

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

DBUtils的概述

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

DBUtils的常用API介绍

  1. 创建QueryRunner对象的API

    public QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection

  2. QueryRunner执行增删改的SQL语句的API

    int update(String sql, Object... params), params参数就是可变参数,参数个数取决于语句中问号的个数

    功能: 执行增删改的sql语句

    参数1: sql语句

    参数2: sql语句需要的参数值

  3. 执行查询的SQL语句的API

    返回值类型 query(String sql, ResultSetHandler<T> rsh, Object... params),其中ResultSetHandler是一个接口,表示结果集处理者

  4. 使用DBUtils完成增删改

public class TestDemo {
    // 创建QueryRunner对象,传入连接池
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

    @Test
    public void insert() throws SQLException {
        // 调用update方法
        String sql = "insert into user values(null,?,?,?)";
        int rows = qr.update(sql, "tq", "123456", "老田");
        System.out.println("受影响的行数:" + rows);
    }

    @Test
    public void update() throws SQLException {
        // 调用update方法
        String sql = "update user set password = ? where nickname = ?";
        int rows = qr.update(sql, "abcdef","老田");
        System.out.println("受影响的行数:" + rows);
    }

    @Test
    public void delete() throws SQLException {
        // 调用update方法
        String sql = "delete from user where nickname = ?";
        int rows = qr.update(sql,"老田");
        System.out.println("受影响的行数:" + rows);
    }
}
 

  1. JavaBean说白了就是一个类, 用来封装数据用的

  2. JavaBean要求

    • 私有字段\成员变量

    • 提供公共的get/set方法

    • 无参构造

    • 建议满参构造

    • 实现Serializable

  3. 字段(成员变量)和属性

    • 字段: 成员变量 eg: private String username;

    • 属性: set\get方法去掉get或者set首字母变小写 eg: setUsername()方法-去掉set->Username-->首字母变小写->username

    一般情况下,我们通过IDEA直接生成的set/get 习惯把字段和属性搞成一样而言

  4. 注意:

    • 使用DBUtils查询得到的结果可以自动封装成员一个对象,但有一个前提条件就是该对象所属的类的属性名必须和表中的字段名一致,否则封装失败

    • 也就是说: 类的set\get方法去掉set\get,然后首字母变小写之后得到的名称,必须和表中的列名一致

使用DBUtils完成查询

ArrayHandler: 适合封装查询结果为一条记录,会把这条记录中每个字段的值封装到Object[]数组中
ArrayListHandler:适合封装查询结果为多条记录,会把每条记录中的值封装到一个数组中,再把这些数组存储到List集合
BeanHandler:适合封装查询结果为一条记录的,把这条记录的数据封装到一个指定的对象中
BeanListHandler:适合封装查询结果为多条记录,会把每条记录中的值封装到一个对象中,再把这些对象存储到List集合
ColunmListHandler:适合封装查询结果为单列多行的,会把当前列中所有的值存储到List集合
KeyedHandler:适合封装查询结果为多条记录的,会把每一条记录封装成一个Map集合,再把Map集合存储到另一个Map集合中
MapHandler: 适合封装查询结果为一条记录的,会把这条记录中每个字段的值封装到Map集合中
MapListHandler:适合封装查询结果为多条记录,会把每条记录中的值封装到一个Map集合中,再把这些Map集合存储到List集合    
ScalarHandler:适合查询结果为单个值的 

public class TestDemo1_查询结果为一条记录 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());


    @Test
    public void select01() throws SQLException {
        // 查询一条记录,封装成一个数组
        String sql = "select * from user where id = ?";
        Object[] arr = qr.query(sql, new ArrayHandler(), 6);
        System.out.println(Arrays.toString(arr));
    }
}

查询一条数据封装到JavaBean对象中(使用BeanHandler)

public class TestDemo1_查询结果为一条记录 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());


    @Test
    public void select02() throws SQLException {
        // 查询一条记录,封装成一个JavaBean
        String sql = "select * from user where id = ?";
        User user = qr.query(sql, new BeanHandler<User>(User.class), 6);
        System.out.println(user);
    }

 
}

查询一条数据,封装到Map对象中(使用MapHandler)

public class TestDemo1_查询结果为一条记录 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());


    @Test
    public void select03() throws SQLException {
        // 查询一条记录,封装成一个Map
        String sql = "select * from user where id = ?";
        Map<String, Object> map = qr.query(sql, new MapHandler(), 6);
        System.out.println(map);
    }
}

public class TestDemo2_查询结果多条记录 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());


    @Test
    public void select01() throws SQLException {
        // 查询多条记录,每条记录封装成一个数组,再把数组存储到List集合中
        String sql = "select * from user";
        List<Object[]> list = qr.query(sql, new ArrayListHandler());
        for (Object[] arr : list) {
            System.out.println(Arrays.toString(arr));
        }
    }

}

public class TestDemo2_查询结果多条记录 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

    @Test
    public void select02() throws SQLException {
        // 查询多条记录,每条记录封装成一个JavaBean,再把JavaBean存储到List集合中
        String sql = "select * from user";
        List<User> list = qr.query(sql, new BeanListHandler<User>(User.class));
        for (User user : list) {
            System.out.println(user);
        }
    }
}

查询多条数据,封装到List<Map>对象中(使用MapListHandler)

public class TestDemo2_查询结果多条记录 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());

    @Test
    public void select03() throws SQLException {
        // 查询多条记录,每条封装成一个Map,再把Map存储到List集合中
        String sql = "select * from user";
        List<Map<String, Object>> list = qr.query(sql, new MapListHandler());
        for (Map<String, Object> map : list) {
            System.out.println(map);
        }
    }
}

查询单个数据(使用ScalarHandler())

public class TestDemo4_查询结果单个值 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());


    @Test
    public void select01() throws SQLException {
        // 查询单列多行,封装到一个List集合中
        String sql = "select count(*) from user";
        long count = (Long)qr.query(sql, new ScalarHandler());
        System.out.println(count);
    }

}

查询单列多个值(使用ColumnListHandler)

public class TestDemo3_查询结果单列多行 {

    // 创建QueryRunner对象,传入连接池对象
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());


    @Test
    public void select01() throws SQLException {
        // 查询单列多行,封装到一个List集合中
        String sql = "select username from user";
        List<Object> list = qr.query(sql, new ColumnListHandler());
        for (Object o : list) {
            System.out.println(o);
        }
    }

}

自定义DBUtils

元数据

  • 概述:元数据(MetaData),即定义数据的数据。打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。

    简单来说: 数据库的元数据就是 数据库、表、列的定义信息。 分类: 参数元数据,结果集元数据

  • 参数:---->数据

    • 参数元数据: 参数的个数,参数的类型

  • 结果集--->数据

    • 结果集元数据:

      • 结果集列的个数

      • 结果集列的名字

      • 结果集列的类型

      • 结果集列对应的java类型

参数元数据

  • 概述: 参数元素数据就是使用ParameterMetaData类来表示

  • 如何获取参数元数据对象:

    • 使用PreparedStatement预编译对象来获取参数的元数据对象

      • public ParameterMetaData getParameterMetaData()

  • ParameterMetaData相关的API

    • int getParameterCount(); 获得参数个数

    • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)

  • 获取参数元素数据:

    • 步骤:

      • 获取参数的元数据对象

      • 根据参数的元数据对象获取参数的元数据

public class Test {
    public static void main(String[] args) throws Exception{
        String sql = "select * from user where username = ? and password = ?";
        // 需求: 获取该sql语句参数的个数
        // 1.获得连接
        Connection connection = C3P0Utils.getConnection();

        // 2.预编译sql语句,得到预编译对象
        PreparedStatement ps = connection.prepareStatement(sql);

        // 3.通过预编译对象获得参数元数据对象
        ParameterMetaData pmd = ps.getParameterMetaData();

        // 4.通过参数元数据对象获得参数的个数
        System.out.println("参数个数:"+pmd.getParameterCount());
        //System.out.println("参数类型:"+pmd.getParameterClassName(1));// 报异常
        //System.out.println("参数类型:"+pmd.getParameterTypeName(1));// 报异常
        //System.out.println("参数类型:"+pmd.getParameterType(1));// 报异常

        // ...

    }
}

结果集元素数据

  • 1.概述: 使用ResultSetMetaData类来表示结果集元数据

    • 获取方式: ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来

    • 作用:ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。

  • 2.ResultSetMetaData相关的API

    • getColumnCount(); 获取结果集中列项目的个数

    • getColumnName(int column); 获得数据指定列的列名

    • getColumnTypeName();获取指定列的SQL类型

    • getColumnClassName();获取指定列SQL类型对应于Java的类型

public class Test {
    public static void main(String[] args) throws Exception{
        String sql = "select * from user";
        // 需求: 获取该sql语句查询的结果集元数据(结果集列的个数,列的名称,列的类型,...)
        // 1.获得连接
        Connection connection = C3P0Utils.getConnection();

        // 2.预编译sql语句,得到预编译对象
        PreparedStatement ps = connection.prepareStatement(sql);

        // 3.执行sql语句,得到结果集对象
        ResultSet resultSet = ps.executeQuery();

        // 4.通过结果集对象获得结果集元数据对象
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();

        // 5.通过结果集元数据对象获得结果集的元数据(结果集列的个数,列的名称,列的类型,...)
        int columnCount = resultSetMetaData.getColumnCount();
        System.out.println("列的个数:"+columnCount);
        for (int i = 1; i <= columnCount; i++) {
            System.out.println("列的名称:"+resultSetMetaData.getColumnName(i));
            System.out.println("列的sql类型:"+resultSetMetaData.getColumnTypeName(i));
            System.out.println("列的java类型:"+resultSetMetaData.getColumnClassName(i));
        }

    }
}

自定义DBUtils增删改

public class MyQueryRunner {
    //- 定义一个DataSource成员变量
    private DataSource dataSource;

    //- 定义一个有参构造方法,空参构造方法
    public MyQueryRunner() {
    }

    public MyQueryRunner(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    //- 定义一个update(String sql,Object... args)完成增删改操作
    public int update(String sql,Object... args) throws SQLException {
        // 1.获得连接
        Connection connection = dataSource.getConnection();

        // 2.预编译sql语句,得到预编译对象
        PreparedStatement ps = connection.prepareStatement(sql);

        // 3.设置sql语句参数
        // 3.1 获得参数的元数据对象
        ParameterMetaData pmd = ps.getParameterMetaData();
        // 3.2 获得参数的个数
        int parameterCount = pmd.getParameterCount();
        // 3.3 设置参数
        for (int i = 0; i < parameterCount; i++) {
            ps.setObject(i+1,args[i]);
        }

        // 4.执行sql语句,处理结果
        int rows = ps.executeUpdate();

        // 5.释放资源
        JDBCUtils.release(null,ps,connection);
        // 6.返回结果
        return rows;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值