连接池讲解

1,自定义链接池

      讲解

       为什么要使⽤连接池

Connection 对象在 JDBC 使⽤的时候就会去创建⼀个对象,使
⽤结束以后就会将这个对象给销毁了 (close) 。每次创建和销毁
对象都是耗时操作,需要使⽤连接池对其进⾏优化。
程序初始化的时候,初始化多个连接,将多个连接放⼊到池
( 集合 ) 中。每次获取的时候,都可以直接从连接池中进⾏获
取。使⽤结束以后,将连接归还到池中。

⽣活⾥⾯的连接池例⼦

⽼⽅式 :
下了地铁需要骑⻋,跑去⽣产⼀个,然后骑完之后,直接
把⻋销毁了 .
连接池⽅式 摩拜单⻋ :
骑之前, 有⼀个公司⽣产了很多的⾃⾏⻋,下了地铁需要
骑⻋,直接扫码使⽤就好了,然后骑完之后,还回去

连接池原理

1. 程序⼀开始就创建⼀定数量的连接,放在⼀个容器 ( 集合 )
中,这个容器称为连接池。
2. 使⽤的时候直接从连接池中取⼀个已经创建好的连接对象 ,
使⽤完成之后 归还到池⼦
3. 如果池⼦⾥⾯的连接使⽤完了 , 还有程序需要使⽤连接 ,
等待⼀段时间 (eg: 3s), 如果在这段时间之内有连接归还 ,
拿去使⽤ ; 如果还没有连接归还 , 新创建⼀个 , 但是新创建的
这⼀个不会归还了
4. 集合选择 LinkedList
增删⽐较快
LinkedList ⾥⾯的 removeFirst() addLast() ⽅法和连接
池的原理吻合

⼩结

使⽤连接池的⽬的: 可以让连接得到复⽤, 避免浪费

⾃定义连接池-初级版本

⽬标

根据连接池的原理 , 使⽤ LinkedList ⾃定义连接池

分析

1. 创建⼀个类 MyDataSource, 定义⼀个集合 LinkedList
2. 程序初始化的时候 , 创建 5 个连接 存到 LinkedList 3. 定义 getConnection() LinkedList 取出 Connection 返回
4. 定义 addBack() ⽅法归还 Connection LinkedList

实现

/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 09:37
* ⾃定义连接池的第⼀个版本
* 1. 创建⼀个容器,存放连接
* 2. 默认往容器中存放5个连接
* 在构造函数中编写代码
* 3. 提供⼀个⽅法,让调⽤者获取连接
* 4. 提供⼀个⽅法,让调⽤者归还连接
*
* 当前第⼀个版本存在的问题:
* 1. 新创建的连接(原本没有在连接池中的连接)也会归还回
连接池
* 2. 连接池使⽤的耦合性太⾼了,不便于以后项⽬切换连接池
*/
public class MyDataSource {
 private LinkedList<Connection>
connectionPool = new LinkedList<>();
 public MyDataSource() {
 //初始化往connectionPool中存放5个连接
 for (int i = 0; i < 5; i++) {
 try {
 //创建⼀个连接
 Connection conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 //将连接添加到connectionPool中
 connectionPool.add(conn);
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 }
 /**
 * 获取连接的⽅法
 * @return
 */
 public Connection getConn() throws
SQLException {
 //如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
 Connection conn = null;
 if (connectionPool.size() > 0){
 //从容器中获取连接
 conn =
connectionPool.removeFirst();
 }else {
 //则新创建连接
 conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 }
 return conn;
 }
 public void addBack(Connection connection){
 //归还连接:其实就是将要归还的那个连接,添加到容
器的尾部
 connectionPool.addLast(connection);
 }
}

⼩结

1. 创建⼀个类 MyDataSource, 定义⼀个集合 LinkedList
2. 程序初始化 ( 静态代码块 ) ⾥⾯ 创建 5 个连接存到 LinkedList
3. 定义提供 Connection 的⽅法
4. 定义归还 Connection 的⽅法

⾃定义连接池-进阶版本

⽬标

实现datasource完成⾃定义连接池

分析

在初级版本版本中 , 我们定义的⽅法是 getConnection(). 因为
是⾃定义的 . 如果改⽤李四的⾃定义的连接池 , 李四定义的⽅法
getAbc(), 那么我们的源码就需要修改 , 这样不⽅便维护 .
sun 公司定义了⼀个接⼝ datasource, 让⾃定义连接池有了
规范

讲解

datasource接⼝概述

Java 为数据库连接池提供了公共的接⼝:
javax.sql.DataSource ,各个⼚商 ( ⽤户 ) 需要让⾃⼰的连接池
实现这个接⼝。这样应⽤程序可以⽅便的切换不同⼚商的连接
池!
javax.sql.DataSource接口,中文翻译数据源,其实就是连接池。
从数据源中得到Connection连接对象。接口Sun公司没有具体的实现,
由各大数据库厂商去实现,很多第三方的公司也可以实现。
代码实现
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 10:01
* ⾃定义连接池的第⼆版:
* 编写连接池,并且实现官⽅的DataSource接⼝
*
* 存在的问题:DataSource中并没有提供归还连接的⽅法
*/
public class MyDataSource2 implements
DataSource{
 private LinkedList<Connection>
connectionPool = new LinkedList<>();
 public MyDataSource2() {
 //初始化往connectionPool中存放5个连接
 for (int i = 0; i < 5; i++) {
 try {
 //创建⼀个连接
 Connection conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 //将连接添加到connectionPool中
 connectionPool.add(conn);
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 }
 @Override
 public Connection getConnection() throws
SQLException {
 //如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
 Connection conn = null;
 if (connectionPool.size() > 0){
 //从容器中获取连接
 conn =
connectionPool.removeFirst();
 }else {
 //则新创建连接
 conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 }
 return conn;
 }
 @Override
 public Connection getConnection(String
username, String password) throws SQLException
{
 return null;
 }
 @Override
 public <T> T unwrap(Class<T> iface) throws
SQLException {
 return null;
 }
 @Override
 public boolean isWrapperFor(Class<?> iface)
throws SQLException {
 return false;
 }
 @Override
 public PrintWriter getLogWriter() throws
SQLException {
 return null;
 }
 @Override
 public void setLogWriter(PrintWriter out)
throws SQLException {
 }
 @Override
 public void setLoginTimeout(int seconds)
throws SQLException {
 }
 @Override
 public int getLoginTimeout() throws
SQLException {
 return 0;
 }
 @Override
 public Logger getParentLogger() throws
SQLFeatureNotSupportedException {
 return null;
 }
}

⼩结

编写连接池遇到的问题

实现 DataSource 接⼝后 ,addBack() 不能调⽤了 .
能不能不引⼊新的 api, 直接调⽤之前的 connection.close(),
但是这个 close 不是关闭 , 是归还
解决办法

继承
条件 : 可以控制⽗类 , 最起码知道⽗类的名字
装饰者模式
作⽤:改写已存在的类的某个⽅法或某些⽅法
条件 :
增强类和被增强类实现的是同⼀个接⼝
增强类⾥⾯要拿到被增强类的引⽤
动态代理

⾃定义连接池-终极版本

⽬标

使⽤装饰者模式改写 connection close() ⽅法 , connection
归还

讲解

⾃定义连接池终极版本分析

增强connectionclose()⽅法, 其它的⽅法逻辑不改

1. 创建 WrapperConnection 实现 Connection
2. WrapperConnection ⾥⾯需要得到被增强的 connection
对象 ( 通过构造⽅法传进去 )
3. 改写 close() 的逻辑 , 变成归还
4. 其它⽅法的逻辑 , 还是调⽤被增强 connection 对象之前的逻
 实现

WrapperConnection

/**
* 包名:com.sunlw.customer.connection
*
* @author sunlw
* ⽇期2020-07-06 10:16
* 依赖倒置原则:
* 尽量依赖抽象,不依赖具体
*/
public class WrapperConnection implements
Connection{
 private Connection connection;
 private LinkedList<Connection>
connectionPool;
 public WrapperConnection(Connection
connection,LinkedList<Connection>
connectionPool) {
 this.connection = connection;
 this.connectionPool = connectionPool;
 }
 @Override
 public void close() throws SQLException {
 //将当前这个连接归还回原来那个连接池容器
 connectionPool.addLast(this);
 }
 @Override
 public Statement createStatement() throws
SQLException {
 return connection.createStatement();
 }
 @Override
 public PreparedStatement
prepareStatement(String sql) throws
SQLException {
 return
connection.prepareStatement(sql);
 }
 @Override
 public CallableStatement prepareCall(String
sql) throws SQLException {
 return null;
 }
 @Override
 public String nativeSQL(String sql) throws
SQLException {
 return null;
 }
 @Override
 public void setAutoCommit(boolean
autoCommit) throws SQLException {
 connection.setAutoCommit(autoCommit);
 }
 @Override
 public boolean getAutoCommit() throws
SQLException {
 return false;
 }
 @Override
 public void commit() throws SQLException {
 connection.commit();
 }
 @Override
 public void rollback() throws SQLException
{
 connection.rollback();
 }
 //....其它⽅法省略
}
MyDataSource03
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 10:01
* ⾃定义连接池的第三版:
* 1. 可以归还连接
* 2. 如果是原本在连接池中的连接,就归还;如果是新创建的
连接⽤完后就销毁
*
* 如果是原本在连接池中的连接,调⽤close()⽅法不销毁,
⽽是将其归还回连接池
* 如果是新创建的连接,调⽤close()⽅法,就销毁(执⾏原本
的close)
*
* 要在不修改类的源码的基础之上,改变类的⽅法
* 1. 继承(在这⾥没法⽤)
* 2. 动态代理(代理模式)
* 3. 装饰者模式
* 1. 装饰者和被装饰者要实现相同的接⼝
* 2. 将被装饰者的对象传⼊装饰者中
* 3. 不需要修改的⽅法,直接调⽤被装饰者的⽅法;需要
修改的⽅法,由装饰者重写
*/
public class MyDataSource3 implements
DataSource{
 private LinkedList<Connection>
connectionPool = new LinkedList<>();
 public MyDataSource3() {
 //初始化往connectionPool中存放5个连接
 for (int i = 0; i < 5; i++) {
 try {
 //创建⼀个装饰后的连接
 Connection conn = new
WrapperConnection(DriverManager.getConnection("
jdbc:mysql:///day20?
characterEncoding=utf8","root","123"),connectio
nPool);
 //将连接添加到connectionPool中
 connectionPool.add(conn);
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 }
 @Override
 public Connection getConnection() throws
SQLException {
 //如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
 Connection conn = null;
 if (connectionPool.size() > 0){
 //从容器中获取连接
 conn =
connectionPool.removeFirst();
 }else {
 //则新创建连接
 conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 }
 return conn;
 }
 @Override
 public Connection getConnection(String
username, String password) throws SQLException
{
 return null;
 }
 @Override
 public <T> T unwrap(Class<T> iface) throws
SQLException {
 return null;
 }
 @Override
 public boolean isWrapperFor(Class<?> iface)
throws SQLException {
 return false;
 }
 @Override
 public PrintWriter getLogWriter() throws
SQLException {
 return null;
 }
 @Override
 public void setLogWriter(PrintWriter out)
throws SQLException {
 }
 @Override
 public void setLoginTimeout(int seconds)
throws SQLException {
 }
 @Override
 public int getLoginTimeout() throws
SQLException {
 return 0;
 }
 @Override
 public Logger getParentLogger() throws
SQLFeatureNotSupportedException {
 return null;
 }
}

⼩结

1. 创建⼀个 MyConnection 实现 Connection
2. MyConnection 得到被增强的 connection 对象
3. 改写 MyConnection ⾥⾯的 close() ⽅法的逻辑为归还
4. MyConnection ⾥⾯的其它⽅法 调⽤被增强的 connection
对象之前的逻辑
5. MyDataSource03 getConnection() ⽅法⾥⾯ 返回了
myConnection

⾃定义连接池扩展版本-使⽤动态代理

使⽤动态代理创建 Connection 对象的代理对象,增强
Connection close ⽅法
/**
* 包名:com.sunlw.customer.datasource
*
* @author sunlw
* ⽇期2020-07-06 10:01
* ⾃定义连接池的第四版:
* 1. 可以归还连接
* 2. 如果是原本在连接池中的连接,就归还;如果是新创建的
连接⽤完后就销毁
*
* 使⽤动态代理技术,增强connection的close⽅法
*/
public class MyDataSource4 implements
DataSource{
 private LinkedList<Connection>
connectionPool = new LinkedList<>();
 public MyDataSource4() {
 //初始化往connectionPool中存放5个连接
 for (int i = 0; i < 5; i++) {
 try {
 Connection connection =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 ClassLoader classLoader =
connection.getClass().getClassLoader();
 //创建动态代理对象
 Connection connectionProxy =
(Connection)
Proxy.newProxyInstance(classLoader, new Class[]
{Connection.class}, new InvocationHandler() {
 @Override
 public Object invoke(Object
proxy, Method method, Object[] args) throws
Throwable {
 //判断执⾏⽅法是否是close⽅
法,如果是,则增强,如果不是则执⾏被代理者原本的⽅法
 if
(method.getName().equals("close")) {
 //增强close
 //将当前这个连接对象,
添加到容器中
 
connectionPool.addLast((Connection) proxy);
 return null;
 }
 //不需要增强的⽅法,就执⾏原
本的⽅法
 return
method.invoke(connection,args);
 }
 });
 //将连接添加到connectionPool中
 
connectionPool.add(connectionProxy);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 @Override
 public Connection getConnection() throws
SQLException {
 //如果容器中有连接,则从容器中获取,如果容器中
没有连接,就直接新创建连接
 Connection conn = null;
 if (connectionPool.size() > 0){
 //从容器中获取连接
 conn =
connectionPool.removeFirst();
 }else {
 //则新创建连接
 conn =
DriverManager.getConnection("jdbc:mysql:///day2
0?characterEncoding=utf8", "root", "123");
 }
 return conn;
 }
 @Override
 public Connection getConnection(String
username, String password) throws SQLException
{
 return null;
 }
 @Override
 public <T> T unwrap(Class<T> iface) throws
SQLException {
 return null;
 }
 @Override
 public boolean isWrapperFor(Class<?> iface)
throws SQLException {
 return false;
 }
 @Override
 public PrintWriter getLogWriter() throws
SQLException {
 return null;
 }
 @Override
 public void setLogWriter(PrintWriter out)
throws SQLException {
 }
 @Override
 public void setLoginTimeout(int seconds)
throws SQLException {
 }
 @Override
 public int getLoginTimeout() throws
SQLException {
 return 0;
 }
 @Override
 public Logger getParentLogger() throws
SQLFeatureNotSupportedException {
 return null;
 }
}

 第三⽅连接池

常⽤连接池

1.⽬标

常⽤连接池

2.分析

通过前⾯的学习,我们已经能够使⽤所学的基础知识构建⾃定
义的连接池了。其⽬的是锻炼⼤家的基本功,帮助⼤家更好的
理解连接池的原理 , 但现实是残酷的,我们所定义的 连接池 和
第三⽅的连接池相⽐,还是显得渺⼩ . ⼯作⾥⾯都会⽤第三⽅
连接池 .

3.讲解

常⻅的第三⽅连接池如下 :
C3P0 是⼀个开源的 JDBC 连接池,它实现了数据源和 JNDI
定,⽀持 JDBC3 规范和 JDBC2 的标准扩展。 C3P0 是异步操
作的,所以⼀些操作时间过⻓的 JDBC 通过其它的辅助线程
完成。⽬前使⽤它的开源项⽬有 Hibernate Spring 等。
C3P0 有⾃动回收空闲连接功能
阿⾥巴巴 - 德鲁伊 druid 连接池: Druid 是阿⾥巴巴开源平台
上的⼀个项⽬,整个项⽬由数据库连接池、插件框架和
SQL 解析器组成。该项⽬主要是为了扩展 JDBC 的⼀些限
制,可以让程序员实现⼀些特殊的需求。
DBCP(DataBase Connection Pool) 数据库连接池,是
Apache 上的⼀个 Java 连接池项⽬,也是 Tomcat 使⽤的连
接池组件。 dbcp 没有⾃动回收空闲连接的功能。

4.⼩结

我们⼯作⾥⾯⽤的⽐较多的是 :
C3P0
druid
光连接池

C3P0使用

1.⽬标

掌握 C3P0 的使⽤

2.路径

1. c3p0 介绍
2. c3p0 的使⽤ ( 硬编码 )
3. c3p0 的使⽤ ( 配置⽂件 )
4. 编写 C3P0Util ⼯具类

3.讲解

c3p0介绍

C3P0 开源免费的连接池!⽬前使⽤它的开源项⽬有:
Spring Hibernate 等。使⽤第三⽅⼯具需要导⼊ jar 包,
c3p0 使⽤时还需要添加配置⽂件 c3p0-config.xml.
使⽤ C3P0 需要添加 c3p0-0.9.1.2.jar

c3p0的使⽤

通过硬编码来编写

步骤
1. 拷⻉ jar
2. 创建 C3P0 连接池对象
3. C3P0 连接池对象⾥⾯获得 connection
实现 :
ComboPooledDataSource cpds = new
ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/we
b10");
cpds.setUser("root");
cpds.setPassword("123456");
Connection connection = cpds.getConnection();
通过配置⽂件来编写
步骤 :
1. 拷⻉ jar
2. 拷⻉配置⽂件 (c3p0-config.xml) src ⽬录【名字不要改】
3. 创建 C3P0 连接池对象【⾃动的读取】
4. 从池⼦⾥⾯获得连接
实现 :
编写配置⽂件 c3p0-config.xml ,放在 src ⽬录下(注:⽂
件名⼀定不要改 )
<c3p0-config>
 <default-config>
 <property
name="driverClass">com.mysql.jdbc.Driver</prope
rty>
 <property
name="jdbcUrl">jdbc:mysql://localhost:3306/web1
1</property>
 <property name="user">root</property>
 <property name="password">123</property>
 <property
name="initialPoolSize">5</property>
 </default-config>
</c3p0-config>
编写 Java 代码 ( 会⾃动读取 resources ⽬录下的 c3p0-
config.xml, 所以不需要我们解析配置⽂件 )
DataSource ds = new ComboPooledDataSource();

使⽤c3p0改写⼯具类

编写 C3P0Util ⼯具类,提供 DataSource 对象,保证整个项⽬
只有⼀个 DataSource 对象
/**
* 包名:com.sunlw.utils
*
* @author sunlw
* ⽇期2020-07-06 11:43
* 这个⼯具类就负责,提供C3P0连接池对象
*/
public class C3P0Util {
 private static DataSource dataSource;
 static {
 dataSource = new
ComboPooledDataSource();
 }
 /**
 * 获取连接池
 * @return
 */
 public static DataSource getDataSource(){
 return dataSource;
 }
}

⼩结

1.C3P0 配置⽂件⽅式使⽤
拷⻉jar
拷⻉配置⽂件到 resources 【配置⽂件的名字不要改】
创建 C3P0 连接池对象
2. C3P0 ⼯具类
保证 DataSource 连接池只有⼀个【 static

知识点-DRUID

1.⽬标

掌握 DRUID 连接池的使⽤

2.路径

1. DRUID 的介绍
2. DRUID 的使⽤ ( 硬编码⽅式 )
3. DRUID 的使⽤ ( 配置⽂件⽅式 )
4. DRUID 抽取成⼯具类

3.讲解

DRUID介绍
Druid 是阿⾥巴巴开发的号称为监控⽽⽣的数据库连接池,
Druid 是国内⽬前最好的数据库连接池。在功能、性能、扩展
性⽅⾯,都超过其他数据库连接池。 Druid 已经在阿⾥巴巴部
署了超过 600 个应⽤,经过很久⽣产环境⼤规模部署的严苛考
验。如:⼀年⼀度的双⼗⼀活动,每年春运的抢⽕⻋票
Druid 的下载地址: https://github.com/alibaba/druid
DRUID 连接池使⽤的 jar 包: druid-1.0.9.jar
DRUID的使⽤
通过硬编码⽅式

步骤 :
1. 导⼊ DRUID jar
2. 创建 Druid 连接池对象 , 配置 4 个基本参数
3. Druid 连接池对象获得 Connection
实现 :
//1. 创建DataSource
DruidDataSource dataSource = new
DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.D
river");//设置驱动
dataSource.setUrl("jdbc:mysql://localhost:3306/
web17");//设置数据库路径
dataSource.setUsername("root");//设置⽤户名
dataSource.setPassword("123");//设置密码
dataSource.setInitialSize(5);//设置初始化连接的数量
//2. 从数据源⾥⾯获得Connection
Connection connection =
dataSource.getConnection();
通过配置⽂件⽅式

步骤 :
1. 导⼊ DRUID jar
2. 拷⻉配置⽂件到 src ⽬录
3. 根据配置⽂件创建 Druid 连接池对象
4. Druid 连接池对象获得 Connection
实现 :
创建 druid.properties, 放在 src ⽬录下
url=jdbc:mysql:///day20
username=root
password=123
driverClassName=com.mysql.jdbc.Driver
编写 Java 代码
//0 根据druid.properties创建配置⽂件对象
Properties properties = new Properties();
// 关联druid.properties⽂件
InputStream is =
DruidDemo.class.getClassLoader().getResourceAsS
tream("druid.properties");
properties.load(is);
//1. 创建DataSource
DataSource dataSource =
DruidDataSourceFactory.createDataSource(propert
ies);
//2. 从数据源(连接池)获得连接对象
Connection connection =
dataSource.getConnection();
Druid⼯具类
/**
* 包名:com.sunlw.utils
*
* @author sunlw
* ⽇期2020-07-06 11:45
*/
public class DruidUtil {
 private static DataSource dataSource;
 static {
 try {
 //1. 创建Properties对象
 Properties properties = new
Properties();
 //2. 将配置⽂件转换成字节输⼊流
 InputStream is =
DruidUtil.class.getClassLoader().getResourceAsS
tream("druid.properties");
 //3. 使⽤properties对象加载is
 properties.load(is);
 //druid底层是使⽤的⼯⼚设计模式,去加载配
置⽂件,创建DruidDataSource对象
 dataSource =
DruidDataSourceFactory.createDataSource(propert
ies);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 public static DataSource getDataSource(){
 return dataSource;
 }
}

⼩结

Druid 配置⽂件使⽤
拷⻉ jar
拷⻉配置⽂件到 src
读取配置⽂件成 properties 对象
使⽤⼯⼚根据 properties 创建 DataSource
DataSource 获得 Connection
  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值