mybatis笔记-2、JDBC总结

JDBC简述

  • JDBC(Java Data Base Connectivity,java数据库连接)是SUN公司开发的,一种用于执行SQL语句的Java API
  • JDBC是Java访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口和类组成
  • JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信
  • mysql的驱动mysql-connector-java-5.1.37-bin.jar

JDBC原理

下载jar包,登录mybatis

mybatis主包,还有以来的jar包。

ORM:概念 ,
Mybatis是ORM的一个实现/Hibernate
orm可以是的开发人员 像操作对象一样 操作数据库表。

开发mybatis程序从步骤:
1.配置mybatis
conf.xml:配置数据库信息 和 需要加载的映射文件
表 - 类
映射文件xxMapper.xml :增删改查标签
测试类:
session.selectOne(“需要查询的SQL的namespace.id”,“SQL的参数值”);

JDBC访问数据库流程:

  1. 加载数据库驱动---------------------一
  2. 创建并获取数据库链接----------------二
  3. 创建jdbc statement对象-------------------三
  4. 设置sql语句
  5. 设置sql语句中的参数(使用preparedStatement)
  6. 通过statement执行sql并获取结果-------------四
  7. 对sql执行结果进行解析处理
  8. 释放资源(resultSet、preparedstatement、connection)
1、加载JDBC驱动程序:

Driver接口

– Driver接口由数据库厂家提供,对于java开发者而言,只需要使用 Driver接口就可以了。

– 在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。不 同的数据库有不同的装载方法。

– 驱动:就是各个数据库厂商实现的Sun公司提出的JDBC接口。 即对 Connection等接口的实现类的jar文件 。通过初始化驱动类com.mysql.jdbc.Driver装载为驱动,该类就在 mysql-connector-java-5.0.8-bin.jar中。如果你使用的是oracle数据库那么该驱动类将不同。

在连接数据库之前,首先要加载想要连接的数据库的驱动到 JVM(Java虚拟机),    
这通过java.lang.Class类的静态方法forName(String  className)实现。   

– 装载MySql驱动 Class.forName("com.mysql.jdbc.Driver");

– 装载Oracle驱动 Class.forName("oracle.jdbc.driver.OracleDriver");

//如果没有导入成功jdbc的jar包,会抛出ClassNotFoundException异常,所以我们try catch
try{
    Class.forName("com.mysql.jdbc.Driver");  //加载MySql的驱动类    
}catch(ClassNotFoundException e){    
    System.out.println("找不到驱动程序类 ,加载驱动失败!");    
    e.printStackTrace() ;    
}

执行Class.forName("com.mysql.jdbc.Driver");会加载JDBC驱动。

成功加载后,会将Driver类的实例注册到DriverManager类(驱动管理类)中。

DriverManager接口 :

  • DriverManager是JDBC的管理层,作用于用户和驱动程序之间。

  • DriverManager跟踪可用的驱动程序,并在数据库和相应的驱动程序 之间建立连接。

2. JDBC连接数据库

•要连接数据库,需要向java.sql.DriverManager请求并获得Connection对象, 该对象就代表一个数据库的连接。

Connection与特定数据库的连接(会话),在连接上下文中执行 SQL 语句并返回结果。

Connection获取方式:使用DriverManager类的

DriverManager.getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和密码来获得Connection接口 。

– 连接MYSQL数据库:

Connection con = DriverManager.getConnection("jdbc:mysql://host:port/database","user", "password"); 

– 连接ORACLE数据库:

Connection con = DriverManager.getConnection("jdbc:oracle:thin:@host:port:databse","us er","password");

例如:

String url = "jdbc:mysql://localhost:3306/test" ;     
String username = "root" ;    
String password = "root" ;    
try{    
	Connection con =     
	DriverManager.getConnection(url , username , password ) ;    
}catch(SQLException se){    
	System.out.println("数据库连接失败!");    
	se.printStackTrace() ;    
}

获取连接的适合还可以指定编码格式

Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/exam?characterEncoding=UTF-8", "root", "admin");
3、创建一个preparedStatement

这步的意义是准备好sql语句,完成对数据库的增删改查。

连接数据库成功后,要执行SQL语句,必须获得java.sql.Statement实例。把sql语句传给statement就获得了statement实例。

String sql = "insert into t_user (username,pwd,regTime) values (?,?,?)";  //?占位符,可以在下一步中填入
PreparedStatement ps = conn.prepareStatement(sql);

con对象后面可以调用三种方法:对应下面的返回值

返回值Statement实例分为以下3种类型:

  • Statement: 由createStatement创建,用于发送简单的SQL语句。(不带参数的)(有漏洞,一般不用) 。执行静态SQL语句。
  • PreparedStatement: 继承自Statement接口,由prepareStatement创建,用于发送含有一个或多个输入参数的sql语句。PreparedStatement对象比Statement对象的效率更 高,并且可以【防止SQL注入】。我们一般都用PreparedStatement.。执行动态SQL语句。
  • CallableStatement: 继承自PreparedStatement 。由方法prePareCall创建,用于调用存储过程。 执行数据库存储过程

因为语句中有占位符没填入数据,所以我们可以使用set方法填入数据。

String sql = "insert into t_user (username,pwd,regTime) values (?,?,?)";  //?占位符,可以在下一步中填入
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"用户");
ps.setInt(2,"123456");
ps.setFloat(3, "1.23");
4、执行SQL语句

​ Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate
和execute

常用的Statement类方法(执行mysql语句):

  • ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。
  • int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等。返回更新的行数。
  • boolean execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的语句,返回是否有结果集。

具体实现的代码:

ResultSet rs = stmt.executeQuery("SELECT * FROM ...") ;  //返回结果集,后面常用  
int rows = stmt.executeUpdate("INSERT INTO ...") ;    
boolean flag = stmt.execute(String sql) ;    

5、遍历结果集

两种情况:
​ 1、执行更新返回的是本次操作影响到的记录数。
​ 2、执行查询返回的结果是一个ResultSet对象。

ResultSet接口: Statement执行SQL语句时返回ResultSet结果集。

– ResultSet提供的检索不同类型字段的方法,常用的有:

  • getString():获得在数据库里是varchar、char等数据类型的对象。
  • getFloat():获得在 数据库里是Float类型的对象。
  • getDate():获得在数据库里面是Date类型的数据。
  • getBoolean():获得在数据库里面是Boolean类型的数据
while(rs.next()){    
    String name = rs.getString("name") ;    
    String pass = rs.getString(1) ; // 此方法比较高效    
}    
(列是从左到右编号的,并且从列1开始)    

6、关闭JDBC对象资源

​ 操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声明顺序相反:依序关闭使用之对象及连接: ResultSet — Statement — Connection

1、先关闭requestSet
2、再关闭preparedStatement
3、最后关闭连接对象connection

if(rs != null){   // 关闭记录集    
    try{    
        rs.close() ;    
    }catch(SQLException e){    
        e.printStackTrace() ;    
    }    
}    
if(stmt != null){   // 关闭声明   preparedStatement    
    try{    
        stmt.close() ;    
    }catch(SQLException e){    
        e.printStackTrace() ;    
    }    
}    
if(conn != null){  // 关闭连接对象    
    try{    
        conn.close() ;    
    }catch(SQLException e){    
        e.printStackTrace() ;    
    }    
}  

完整代码:

package com.iot.mybatis.jdbc;

//import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Created by Administrator on 2016/2/21.
 */
public class JdbcTest {
    public static void main(String[] args) {
        //数据库连接
        Connection connection = null;
        //预编译的Statement,使用预编译的Statement提高数据库性能
        PreparedStatement preparedStatement = null;
        //结果集
        ResultSet resultSet = null;

        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");

            //通过驱动管理类获取数据库链接
            connection =  DriverManager.getConnection("jdbc:mysql://120.25.162.238:3306/mybatis001?characterEncoding=utf-8", "root", "123");
            //定义sql语句 ?表示占位符
            String sql = "select * from user where username = ?";
            //获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "王五");
            //向数据库发出sql执行查询,查询出结果集
            resultSet =  preparedStatement.executeQuery();
            //遍历查询结果集
            while(resultSet.next()){
                System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //释放资源
            if(resultSet!=null){
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(preparedStatement!=null){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

问题总结

1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。

设想:使用数据库连接池管理数据库连接。

2.将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护。

设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。

3.向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。

设想:将sql语句及占位符号和参数全部配置在xml中。

4.从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。

设想:将查询的结果集,自动映射成java对象。(Mybatis解决)

JDBC其他内容

批处理

– Batch – 对于大量的批处理,建议使用Statement,因为PreparedStatement的预编译空间有限 ,当数据量特别大时,会发生异常。

package com.bjsxt.jdbc;
// 测试批处理的基本用法
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo05 {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try {
			//加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","123456");
			
			conn.setAutoCommit(false);  //设为手动提交
			long start = System.currentTimeMillis();
			stmt = conn.createStatement();
			
			for(int i=0;i<20000;i++){
				stmt.addBatch("insert into t_user (username,pwd,regTime) values ('gao"+i+"',666666,now())");
			}
			stmt.executeBatch();
			conn.commit();  //提交事务
			long end = System.currentTimeMillis();
			System.out.println("插入20000条数据,耗时(毫秒):"+(end-start));
			
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			//遵循:resultset-->statment-->connection这样的关闭顺序!一定要将三个trycatch块,分开写!
			try {
				if(rs!=null){
					rs.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(stmt!=null){
					stmt.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(conn!=null){
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

事务

事务基本概念

在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

事务:一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元!

事务的四大特点(ACID)
  • atomicity(原子性):表示一个事务内的所有操作是一个整体,要么全部成功,要么全失败;
  • consistency(一致性) :表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;
  • isolation(隔离性):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态, 要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
  • durability(持久性):持久性事务完成之后,它对于系统的影响是永久性的。
Mysql中的事务

在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。

  • 开启事务:start transaction
  • 结束事务:commit或rollback

在执行SQL语句之前,先执行start transaction,这就开启了一个事务(事务的起点),然后可以去执行多条SQL语句,最后要结束事务,commit表示提交,即事务中的多条SQL语句所作出的影响会持久到数据库中,或者rollback,表示回滚到事务的起点,之前做的所有操作都被撤销了。

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   | 1000.00 |
|  2 | ls   | 1000.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE account SET balance=900 WHERE name = 'zs';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   |  900.00 |
|  2 | ls   | 1000.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

mysql> UPDATE account SET balance=1100 WHERE name = 'ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   |  900.00 |
|  2 | ls   | 1100.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   | 1000.00 |
|  2 | ls   | 1000.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE account SET balance=balance-100 WHERE name = 'zs';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   |  900.00 |
|  2 | ls   | 1000.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

mysql> UPDATE account SET balance=balance+100 WHERE name = 'ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   |  900.00 |
|  2 | ls   | 1100.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.02 sec)

mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
|  1 | zs   |  900.00 |
|  2 | ls   | 1100.00 |
|  3 | ww   | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

JDBC事务

在JDBC中处理事务,都是通过Connection完成的。

同一事务中所有的操作,都在使用同一个Connection对象。

①JDBC中的事务

Connection的三个方法与事务有关:

  • setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置为false,那么相当于开启了事务了;con.setAutoCommit(false) 表示开启事务。
  • commit():提交结束事务。
  • rollback():回滚结束事务。

1、事务开始于:

  • 连接到数据库上,并执行一条DML语句(INSERT、UPDATE或DELETE)。
  • 前一个事务结束后,又输入了另外一条DML语句。

2、事务结束于:

  • 执行COMMIT或ROLLBACK语句。
  • 执行一条DDL语句,例如CREATE TABLE语句;在这种情况下,会自动执 行COMMIT语句。
  • 执行一条DCL语句,例如GRANT语句;在这种情况下,会自动执行 COMMIT语句。
  • 断开与数据库的连接。
  • 执行了一条DML语句,该语句却失败了;在这种情况中,会为这个无效的 DML语句执行ROLLBACK语句。

JDBC处理事务的代码格式

try{
     con.setAutoCommit(false);//开启事务
     ......
     con.commit();//try的最后提交事务      
} catch() {
    con.rollback();//回滚事务
}

public class AccountDao {
    /*
    * 修改指定用户的余额
    * */
    public void updateBalance(Connection con, String name,double balance) {
        try {
            String sql = "UPDATE account SET balance=balance+? WHERE name=?";
            PreparedStatement pstmt = con.prepareStatement(sql);
            pstmt.setDouble(1,balance);
            pstmt.setString(2,name);
            pstmt.executeUpdate();
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

import cn.itcast.jdbc.JdbcUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;

public class Demo1 {
    /*
    * 演示转账方法
    * 所有对Connect的操作都在Service层进行的处理
    * 把所有connection的操作隐藏起来,这需要使用自定义的小工具(day19_1)
    * */
    public void transferAccounts(String from,String to,double money) {
        //对事务的操作
        Connection con = null;
        try{
            con = JdbcUtils.getConnection();
            con.setAutoCommit(false);
            AccountDao dao = new AccountDao();
            dao.updateBalance(con,from,-money);//给from减去相应金额
            if (true){
                throw new RuntimeException("不好意思,转账失败");
            }
            dao.updateBalance(con,to,+money);//给to加上相应金额
            //提交事务
            con.commit();

        } catch (Exception e) {
            try {
                con.rollback();
            } catch (SQLException e1) {
                e.printStackTrace();
            }
            throw new RuntimeException(e);
        }
    }
    @Test
    public void fun1() {
        transferAccounts("zs","ls",100);
    }
}

事务隔离级别从低到高:

– 读取未提交(Read Uncommitted)

– 读取已提交(Read Committed)

– 可重复读(Repeatable Read)

– 序列化(serializable)

package com.bjsxt.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


/**
 * 测试事务的基本概念和用法
 * @author 高淇 www.sxt.cn
 *
 */
public class Demo06 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps1 = null;
		PreparedStatement ps2 = null;
		try {
			//加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","123456");
			
			conn.setAutoCommit(false); //JDBC中默认是true,自动提交事务
			
			ps1 = conn.prepareStatement("insert into t_user (username,pwd) values (?,?)");
			ps1.setObject(1, "高淇");
			ps1.setObject(2, "123456");
			ps1.execute();
			System.out.println("插入一个用户,高淇");
			
			try {
				Thread.sleep(6000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			ps2 = conn.prepareStatement("insert into t_user (username,pwd) values (?,?,?)");
			ps2.setObject(1, "马士兵");
			ps2.setObject(2, "123456");
			ps2.execute();			
			System.out.println("插入一个用户,马士兵");
			
			
			conn.commit();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			try {
				conn.rollback();	//回滚
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				if(ps1!=null){
					ps1.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(conn!=null){
					conn.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

五、事务隔离级别

1、事务的并发读问题

  • 脏读:读取到另外一个事务未提交数据(不允许出来的事);
  • 不可重复读:两次读取不一致;
  • 幻读(虚读):读到另一事务已提交数据。

2、并发事务问题

因为并发事务导致的问题大致有5类,其中两类是更新问题三类是读问题。

  • 脏读(dirty read):读到另一个事务的未提交新数据,即读取到了脏数据;
  • 不可重复读(unrepeatable):对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
  • 幻读(虚读)(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录。

3、四大隔离级别

4个等级的事务隔离级别,在相同的数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。

1、SERIALIZABLE(串行化)

  • 不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
  • 性能最差

2、REPEATABLE READ(可重复读)(MySQL)

  • 防止脏读和不可重复读,不能处理幻读
  • 性能比SERIALIZABLE好

3、READ COMMITTED(读已提交数据)(Oracle)

  • 防止脏读,不能处理不可重复读和幻读;
  • 性能比REPEATABLE READ好

4、READ UNCOMMITTED(读未提交数据)

  • 可能出现任何事物并发问题,什么都不处理。
  • 性能最好

六、MySQL隔离级别

MySQL的默认隔离级别为Repeatable read,可以通过下面语句查看:

SELECT @@TX_ISOLATION;

也可以通过下面语句来设置当前连接的隔离级别:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ ;//[4选1]

七、JDBC设置隔离级别

con.setTransactionIsolation(int level) :参数可选值如下:

  • Connection.TRANSACTION_READ_UNCOMMITTED;
  • Connection.TRANSACTION_READ_COMMITTED;
  • Connection.TRANSACTION_REPEATABLE_READ;
  • Connection.TRANSACTION_READ_SERIALIZABLE。JDBV
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值