第十章 数据库连接池与DBUtils工具

10.1数据连接池

JDBC由于每操作一次数据库,都会执行一次创建和断开Connection对象的操作,这种频繁的操作Connection对象十分影响数据库的访问效率,并且增加了代码量。Apache组织提供了DBUtils工具类库,该类库实现了对JDBC的简单封装,能在不影响性能的情况下极大地简化JDBC的编码工作。
—— 什么是数据库连接池
在JDBC编程中,每次创建和断开Connection对象都会消耗一定时间和IO资源。这是因为Java程序与数据库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则要把代表连接的java.sql.Connection对象等加载到内存中,所以建立数据库连接的开销大,尤其是在大量的并发访问时。
在这里插入图片描述
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,当应用程序访问数据库时并不是直接创建Connection,而是向连接池“申请”一个Connection。如果连接池中有空闲的Connection,则将其返回,否则创建新的Connection。使用完毕后,连接池会将该Connection回收,并交付其他的线程使用,以减少创建和断开数据库的连接次数,提高数据库的访问效率。
—— DataSource 接口
负责与数据库建立连接,并定义了返回值为Connection对象的方法,如下:
1)Connection getConnection()
2)Connection getConnection(String username, String password()
相同点,它们都能用来获取Connection对象,不同点,第1个方法是通过无参的方式建立与数据库的连接,第2个方法是通过传入登录信息的方式建立与数据库的连接。

通常把实现了javax.sql.DataSource接口的类称为数据源。数据源中存储了所有建立数据库连接的信息,通过提供正确的数据源名称,可以找到相应的数据库连接。

数据源中包含数据库连接池。如果数据是水,数据库就是水库,数据源就是连接到水库的管道,终端用户看到的数据集是管道里流出来的水。一些开源组织提供了数据源的独立实现,常用的有DBCP数据源和C3P0数据源。
—— DBCP(DataBase Connection Pool) 数据源
它是Apache组织下的开源连接池实现,也是Tomcat服务器使用的连接池组件。单独使用DBCP数据源时,需要在应用程序中导入两个JAR包,如下 :
1.commons-dbcp.jar包
commons-dbcp.jar包是DBCP数据源的实现包,包含所有操作数据库连接信息和数据库连接池初始化信息的方法,并实现了DataSource接口的getConnection()方法。
2.commons-pool.jar包

commons-pool.jar包是DBCP数据库连接池实现包的依赖包,为commons-dbcp.jar包中的方法提供了支持。

commons-dbcp.jar中包含两个核心的类,分别是BasicDataSourceFactory和BasicDataSource,它们都包含获取DBCP数据源对象的方法。
BasicDataSource是DataSource接口的实现类,主要包括设置数据源对象的方法,该类的常用方法如下
在这里插入图片描述
BasicDataSourceFactory是创建BasicDataSource对象的工厂类,它包含一个返回值为BasicDataSource对象的方法createDataSource(),该方法通过读取配置文件的信息生成数据源对象并返回给调用者。这种把数据库的连接信息和数据源的初始化信息提取出来写进配置文件的方式,让代码看起来更加简洁,思路也更加清晰。

使用DBCP数据源时,首先要创建数据源对象,数据源对象的创建方式有两种如下:
1.通过BasicDataSource类直接创建数据源对象

在使用BasicDataSource类创建一个数据源对象时,需要手动给数据源对象设置属性值,然后获取数据库连接对象。案例如下:

(1)创建Web项目,导入mysql-connector-java-5.0.8-bin.jar、commons-dbcp-1.4.jar以及commons-pool-1.6.jar这三个包。

package cn.itcast.chapter10.example;
 
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
 
import com.mysql.cj.jdbc.DatabaseMetaData;
public class Example01 {
    public static DataSource ds = null;
    static {
        //获取DBCP数据源实现类对象
        BasicDataSource bds = new BasicDataSource();
        //设置连接数据库需要的配置信息
        bds.setDriverClassName("com.mysql.jsbc.Driver");
        bds.setUrl("jdbc:mysql://localhost:3306/jdbc");
        bds.setUsername("root");
        bds.setPassword("itcast");
        //设置连接池的参数
        bds.setInitialSize(5);
        bds.setMaxActive(5);
        ds = bds;
    }
    public static void main(String[] args) throws SQLException {
    	//获取数据库连接对象
    	Connection conn = ds.getConnection();
    	//获取数据库连接信息
    	DatabaseMetaData metaData = conn.getMetaData();
    	//打印数据库连接信息
    	System.out.println(metaData.getURL()+",UserName="+metaData.getUserName()+","+metaData.getDriverName());
    }
}

(2)运行main()方法后,程序的运行结果如图
在这里插入图片描述
2.通过读取配置文件创建数据源对象

即使用BasicDataSourceFactory工厂类读取配置文件,创建数据源对象,然后读取数据库连接对象。

(1)在src目录下创建dbcpconfig.properties文件,该文件用于设置数据库的连接信息和数据源的初始化信息,如图

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
username=root
password=itcast
#初始化连接
initialSize=5
#最大连接数量
maxActive=10
#最大空闲连接
maxIdle=10

(2)创建Example02类,该类中采取了从配置文件中获取数据库的连接信息的数据源的初始化信息的方式

package cn.itcast.chapter10.example;
 
import java.io.InputStream;
import java.sql.SQLException;
 
import javax.activation.DataSource;
import javax.jms.ConnectionMetaData;
 
import org.apache.commons.dbcp2.BasicDataSourceFactory;
 
import com.mysql.cj.jdbc.DatabaseMetaData;
import com.sun.xml.internal.fastinfoset.sax.Properties;
 
public class Example02 {
	public static DataSource ds = null;
	static {
		//新建一个配置文件对象
		Properties prop = new Properties();
		try {
			//通过类加载器找到文件路径,读取配置文件
			InputStream in = new Example02().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
			//把文件以输入流的形式加载到配置对象中
			prop.load(in);
			//创建数据对象
			ds = BasicDataSourceFactory.createDataSource(prop);
		} catch (Exception e) {
		throw new ExceptionInInitializerError(e);
		}
	}
	public static void main(String[] args) throws SQLException {
		//获得数据库连接对象
		ConnectionMetaData conn = ds.getConnection();
		//获取数据库连接信息
		DatabaseMetaData metaData = conn.getMetaData();
		//打印数据库连接信息
		System.out.println(metaData.getURL()+",UserName="+metaData.getUserName()+","+metaData.getDriverName());
	}
	
}
 

—— C3P0 数据源
C3P0是目前最流行的开源数库连接池之一,它实现了DataSource数据源接口,支持JDBC2和JDBC3的标准规范,易于扩展并且性能优越,著名的开源框架Hibernate和Spring都支持该数据源。在使用C3P0开发时,需要了解C3P0中DataSource接口的实现类ComboPooleDataSource,它是C3P0的核心类,提供了数据源对象的相关方法,如下
在这里插入图片描述
当使用C3P0数据源时,首先需要创建数据源对象,创建数据源对象可以使用ComboPooledDataSource类,该类有两个构造方法,分别是ComboPooleDataSource()和ComboPooledDataSource(String configName)。如下

1.通过ComboPooleDataSource()构造方法创建数据源对象

需要手动给数据源对象设置属性值,然后获取数据库连接对象,步骤如下

(1)在项目chapter10中导入JAR包c3p0-0.9.1.2.jar,然后在cn.itcast.chapter10.example包下创建一个Example03类,该类采用C3P0数据源手动代码的方式获取Connection对象,如下

package cn.itcast.chapter10.example;
 
import java.sql.SQLException;
 
import javax.activation.DataSource;
 
public class Example03 {
	public static DataSource ds = null;
	//初始化C3P0数据源
	static {
		ComboPooledDataSource cpds = new ComboPooledDataSource();
		//设置连接数据需要的配置信息
		try {
			cpds.setDirverClass("com.mysql.jdbc.Dirver");
			cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc");
			cpds.setUser("root");
			cpds.setPassword("itcast");
			//设置连接池的参数
			cpds.setInitialPoolSize(5);
			cpds.setMaxPoolSize(15);
			ds = cpds;
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
	public static void main(String[] args) throws SQLException {
		//获取数据库连接对象
		System.out.println(ds.getConnection());
	}
}

(2)执行main()方法,程序的运行结果如图
在这里插入图片描述
2.通过读取配置文件创建数据源对象

使用ComboPooledDataSource(String configName)构造方法读取c3p0-config.xml配置文件,从而创建数据源对象,然后获取数据库连接对象。案例如下

(1)在src根目录下创建一个c3p0-config.xml文件,用于设置数据库的连接信息和数据源的初始化信息,如下

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<default-config>
		<property name="dirverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">
			jdbc:mysql://localhost:3306/jdbc
		</property>
		<property name="user">root</property>
		<property name="password">itcast</property>
		<property name="checkoutTimeout">30000</property>
		<property name="initialPoolSize">10</property>
		<property name="maxIdleTime">30</property>
		<property name="maxPoolSize">100</property>
		<property name="mainPoolSize">10</property>
		<property name="maxStatements">200</property>
	</default-config>
	<name-config name="itcast">
		<property name="dirverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">
			jdbc:mysql://localhost:3306/jdbc
		</property>
		<property name="user">root</property>
		<property name="password">itcast</property>
		<property name="initialPoolSize">5</property>
		<property name="maxPoolSize">15</property>
	</name-config>
</c3p0-config>

(2)在cn.itcast.chapter10.example包下创建一个Example04类,该类中使用C3P0数据源从配置文件中获取Connection对象,如下

package cn.itcast.chapter10.example;
 
import java.sql.SQLException;
 
import javax.activation.DataSource;
 
public class Example04 {
	public static DataSource ds = null;
	//初始化C3P0数据源
	static {
		//使用c3p0-config.xml配置文件中的named-config结点中name属性的值
		ComboPooledDataSource cpds = new ComboPooledDataSource("itcast");
		ds = cpds;
	}
	public static void main(String[] args) throws SQLException {
		System.out.println(ds.getConnection());
	}
}

(3)执行main()方法,程序的运行结果如下
在这里插入图片描述
在使用ComboPooledDataSource(String configName)方法创建对象时必须遵以下两点。

(1)配置文件名称必须为c3p0-config.xml或者c3p0.properties,并且位于该项目的src根目录下。

(2)当传入的configName值为空或者不存在时,则使用默认的配置方式创建数据源。

10.2 DBUtils 工具

—— DBUtils 工具
为更加简单化的使用JDBC,Apache组织提供了一个DBUtils工具,它是操作数据库的一个组件,实现了对JDBC的简单封装,可以在不影响性能的情况下极大地简化JDBC的编码工作量。
DBUtils工具的核心类是org.apache.commons.dbutils.QueryRunner类和org.apache.commons.dbutils.ResultSetHandler接口。
—— QueryRunner 类
QueryRunner类简化了执行SQL语句的diamante,它与ResultSetHandler组合在一起就能完成大部分的数据库操作,大大地减少了编码量。QueryRunner类提供了带有一个参数的构造方法,该方法以javax.sql.DataSource作为参数传递到QueryRunner的构造方法中类获取Connection对象。针对不同的数据库操作,QueryRunner类提供了几种常见的方法,如下
1)query(String sql, ResultSetHandler rsh, Object…params)方法
该方法用于执行查询操作,它可以从提供给构造方法的数据源DataSource或使用的setDataSource()方法中获得连接。
2)update(String sql, Object…params)方法
该方法用于执行插入、更新或者删除操作,其中,参数params表示SQL语句中的置换参数。
3)update(String sql)方法
该方法用来执行插入、更新或者删除操作,它不需要置换参数。
—— ResultSetHandler 接口
ResultSetHandler接口用于处理ResultSet结果集,它可以将结果集中的数据转换为不同的形式。根据结果集中数据类型的不同,ReusltSetHandler提供了几种常见的实现类,如下
1)BeanHandler:将结果集中的第1行数据封装到一个对应的JavaBean实例中。
2)BeanLisHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,并存放到List里。
3)ScalarHandler:将结果集中某一条记录的其中某一列的数据存储成Object对象。
如果上述实现类没有提供想要的功能,可以通过自定义一个实现ResultSetHandler接口的类,然后通过重写handler()方法,实现结果集的处理。
—— ResultSetHandler 实现类
1.BeanHandler和BeanListHandler
BeanHandler和BeanLisHandler实现类是将结果集中的数据封装到对应的JavaBean实例中,这也是实际开发中最常用的结果集处理方法。接下来,通过代码实现来学习如何使用BeanHandler和BeanListHandler以及两者的区别。如下
(1)在名为jdbc的数据库中创建数据表user,创建语句如下

CREATE TABLE USER (
	id INT(3) PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20) NOT NULL,
	PASSWORD VARCHAR(20) NOT NULL
);
INSERT INTO USER(NAME,PASSWORD) VALUES ('zhangsan','123456');
INSERT INTO USER(NAME,PASSWORD) VALUES ('lisi','123456');
INSERT INTO USER(NAME,PASSWORD) VALUES ('wangwu','123456');

结果如图:
在这里插入图片描述

(2)将下载的DBUtils工具的JAR包commons-dbutils-1.6.jar添加到项目的lib目录中,并将第9章中文件JDBCUtils.java复制到cn.itcast.chapter10.example包下
(3)在chapter10项目的cn.itcast.chapter10.example包中创建一个名为BaseDao的类,该类中编写了一个通用的查询方法,如下

package cn.itcast.chapter10.example;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.dbutils.ResultSetHandler;
public class BaseDao {
	public static Object query(String sql, ResultSetHandler<?> rsh, Object... params) throws SQLException {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			//获得连接
			conn = JDBCUtils.getConection();
			//预编译sql
			pstmt = conn.prepareStatement(sql);
			//将参数设置进去
			for (int i = 0; params != null && i < params.length; i++) {
				pstmt.setObject(i + 1, params[i]);
			}
			//发送sql
			rs = pstmt.executeQuery();
			//让调用者去实现对结果集的处理
			Object obj = rsh.handle(rs);
			return obj;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//释放资源
			JDBCUtils.release(rs, pstmt, conn);
		}
		return rs;
	}
}

(4)在cn.itcast.chapter10.example包下创建实体类User,使用该类来创建User对象,其实现方式如下

package cn.itcast.chapter10.example;
 
public class User {
	private int id;
	private String name;
	private String password;
	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 String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

(5)在cn.itcast.chapter10.example包下创建类ResultSetTest1,该类用于演示BeanHandler类对结果集的处理,如下

package cn.itcast.chapter10.example;
 
import java.sql.SQLException;
 
import org.apache.commons.dbutils.ResultSetHandler;
public class ResultSetTest1 {
	public static void testBeanHandler() throws SQLException {
		BaseDao basedao = new BaseDao();
		String sql = "select * from user where id=?";
		User user = (User) basedao.query(sql, newBeanHandler(User.class), 1);
		System.out.println("id为1的User对象的name值为:"+user.getName());
	}
	private static ResultSetHandler<?> newBeanHandler(Class<User> class1) {
		// TODO Auto-generated method stub
		return null;
	}
	public static void main(String[] args) throws SQLException {
		testBeanHandler();
	}
}

(6)执行类ResultSetTest1中的main()方法,执行结果如图所示
在这里插入图片描述

(7)在cn.itcast.chapter10.example包下创建类ResultSetTest2,该类用于演示BeanListHandler类对结果集的处理,如下

package cn.itcast.chapter10.example;
import java.sql.SQLException;
import java.util.ArrayList;
import org.apache.commons.dbutils.handlers.BeanListHandler;
public class ResultSetTest2 {
	public static void testBeanListHandler() throws SQLException {
		BaseDao basedao = new BaseDao();
		String sql = "select * from user";
		ArrayList<User> list = (ArrayList<User> basedao.query(sql, new BeanListHandler(User.class));
		for (int i = 0; i < list.size(); i++) {
			System.out.println("第"+(i+1)+"条数据的username值为:"+list.get(i).getName());
		}
	}
	public static void main(String[] args) throws SQLException {
		testBeanListHandler();
	}
}

(8)执行类ResultTest2中的main()方法,执行结果如下
在这里插入图片描述

2.ScalarHandler
在使用DBUtils工具操作数据库时,如果需要输出结果集中一行数据的指定字段值,可以使用ScalarHandler类。案例如下
(1)在cn.itcast.chapter10.example包下创建类ResultSetTest3,该类用于演示ScalarHandler类的使用方法,如下

package cn.itcast.chapter10.example;
import java.sql.SQLException;
import org.apache.commons.dbutils.handlers.ScalarHandler;
public class ResultSetTest3 {
	public static void testScalarHandler() throws SQLException {
		BaseDao basedao = new BaseDao();
		String sql = "select * from user where id=?";
		Object arr = (Object) basedao.query(sql, new ScalarHandler("name"), 1);
		System.out.println(arr);
	}
	public static void main(String[] args) throws SQLException {
		testScalarHandler();
	}
}

(2)执行ResultSetTest3类中的main()方法,控制台的输出结果如下
在这里插入图片描述

【任务 10】使用DBUtils实现增删改查

【任务目标】
通过所学DBUtils的相关知识,学会如何使用DBUtils工具对数据库进行增删改查的基本操作。
【实现步骤】
1.创建C3p0Utils类
在项目chapter10的src目录下,创建一个名为cn.itcast.jdbc.utils的包,然后在该包下创建C3p0Utils类,该类用于创建数据源,如下

package cn.itcast.jdbc.utils;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3p0Utils {
	private static DataSource ds;
	static {
		ds = new ComboPooledDataSource();
	}
	public static DataSource getDataSource() {
		return ds;
	}
}

2.创建DBUtilsDao类

在项目chapter10的src目录下,创建一个名为cn.itcast.jdbc.demo的包,然后在该包下创建一个DBUtilsDao类,该类实现了对user表增删改查的基本操作。如下

package cn.itcast.jdbc.demo;
 
import java.sql.SQLException;
 
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
 
import cn.itcast.chapter10.example.User;
import cn.itcast.jdbc.utils.C3p0Utils;
 
import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List;
 
public class DBUtilsDao {
	//查询所有,返回List集合
	public List findAll() throws SQLException {
		//创建QueryRunner对象
		QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());
		//写SQL语句
		String sql = "select * from user";
		//调用方法
		List list = (List)runner.query(sql, new BeanListHandler(User.calss));
		return list;
	}
	//查询单个,返回对象
	public User find(int id) throws SQLException {
		//创建QueryRunner对象
		QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());
		//写SQL语句
		String sql = "select * from user where id=?";
		//调用方法
		User user = (User)runner.query(sql, new BeanHandler(User.class), new Object[] {id});
		return user;
	}
	//添加用户的操作
	public Boolean insert(User user) throwsSQLException {
		//创建QueryRunner对象
		QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());
		//写SQL语句
		String sql = "insert into user (name, passwrod) values (?,?)";
		//调用方法
		int num = runner.update(sql, new Object[] {user.getName(), user.getPassword()});
		if(num > 0)
			return true;
		return false;
	}
	//修改用户的操作
	public Boolean update(User user) throws SQLException {
		//创建QueryRunner对象
		QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());
		//写SQL语句
		String sql = "update user set name=?,password=? where id=?";
		//调用方法
		int num = runner.update(sql, new Object[] {user.getName(), user.getPassword(), user.getId()});
		if (num > 0)
			return true;
		return false;
	}
	//删除用户的操作
	public Boolean delete(int id) throws SQLException {
		//创建QueryRunner对象
		QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());
		//写SQL语句
		String sql = "delete from user where id=?";
		//调用方法
		int num = runner.update(sql, id);
		if (num > 0) 
			return true;
		return false;
	}
}

3.测试DBUtilsDao类中的增速删改查操作
(1)增加数据。在cn.itcast.jdbc.demo包中创建类DBUtilsDaoTest1对增加操作进行测试,如下

package cn.itcast.jdbc.demo;
import java.sql.SQLException;
import cn.itcast.chapter10.example.User;
public class DBUtilsDaoTest1 {
	private static DBUtilsDao dao = new DBUtilsDao();
	public static void testInsert() throws SQLException {
		User user = new User();
		user.setName("zhaoliu");
		user.setPassword("666666");
		boolean b = dao.insert(user);
		System.out.println(b);
	}
	public static void main(String[] args) throws SQLException {
		testInsert();
	}
}

运行结果如下:
在这里插入图片描述

(2)修改数据。在cn.itcast.jdbc.demo包下创建测试类DBUtilsDaoTest2,如下

package cn.itcast.jdbc.demo;
import java.sql.SQLException;
import cn.itcast.chapter10.example.User;
public class DBUtilsDaoTest2 {
	private static DBUtilsDao dao = new DBUtilsDao();
	public static void testupdate() throws SQLException {
		User user = new User();
		user.setName("zhaoliu");
		user.setPassword("666777");
		user.setId(4);
		boolean b = dao.update(user);
		System.out.println(b);
	}
	public static void main(String[] args) throws SQLException {
		testupdate();
	}
}

运行结果如下
在这里插入图片描述

(3)删除数据。在cn.itcast.jdbc.demo包下创建测试类DBUtilsDaoTest3,如下

package cn.itcast.jdbc.demo;
import java.sql.SQLException;
import cn.itcast.chapter10.example.User;
public class DBUtilsDaoTest2 {
	private static DBUtilsDao dao = new DBUtilsDao();
	public static void testupdate() throws SQLException {
		User user = new User();
		user.setName("zhaoliu");
		user.setPassword("666777");
		user.setId(4);
		boolean b = dao.update(user);
		System.out.println(b);
	}
	public static void main(String[] args) throws SQLException {
		testupdate();
	}
}

(4)查询数据。在cn.itcast.jdbc.demo包下创建测试类DBUtilsDaoTest4,如下

package cn.itcast.jdbc.demo;
import java.sql.SQLException;
import cn.itcast.chapter10.example.User;
public class DBUtilsDaoTest4 {
	private static DBUtilsDao dao = new DBUtilsDao();
	private static void testfind() throws SQLException {
		User user = dao.find(2);
		System.out.println(user.getId()+","+user.getName()+","+user.getPassword());
	}
	public static void main(String[] args) throws SQLException {
		testfind();
	}
}

执行DBUtilsDaoTest4类中的main()方法,控制台的显示结果如下
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值