JDBC概述

JDBC

一、JDBC概述

  1. Java DataBase Connectivity(Java语言连接数据库)

  2. JDBC的本质

    (1)JDBC是SUN公司制定的一套接口(interface)
    java.sql.*; (这个软件包下有很多接口。)

    (2)接口都有调用者和实现者。
    面向接口调用、面向接口写实现类,这都属于面向接口编程。

    (3)为什么要面向接口编程?
    解耦合:降低程序的耦合度,提高程序的扩展力。
    多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
    建议:
    Animal a = new Cat();
    Animal a = new Dog();
    // 喂养的方法
    public void feed(Animal a){ // 面向父类型编程。

    ​ }
    ​ 不建议:
    ​ Dog d = new Dog();
    ​ Cat c = new Cat();

    (4)为什么SUN制定一套JDBC接口?
    因为每一个数据库的底层实现原理都不一样。
    Oracle数据库有自己的原理。
    MySQL数据库也有自己的原理。
    MS SqlServer数据库也有自己的原理。

    每一个数据库产品都有自己独特的实现原理。

  3. 驱动

    (1)所有的数据库驱动都是以jar包的形式存在,jar包当中有很多.class文件,这些class文件就是对JDBC接口的实现。

    (2)驱动不是SUN公司提供的,是各大数据库厂家负责提供。

  4. 编写程序模拟JDBC本质

    /*
    SUN公司负责编写这套JDBC的接口
    */
    public interface JDBC{
    	/*
    	连接数据库的方法
    	*/
    	void getConnection();
    }
    
    /*
    MySQL的数据库厂家负责编写JDBC接口的实现类
    */
    public class MySQL implements JDBC{
    	
    	public void getConnection(){
    		
    	}
    }
    //实现类被称为驱动
    
    /*
    SQLServer的数据库厂家负责编写JDBC接口的实现类
    */
    public class SQLServer implements JDBC{
    	
    	public void getConnection(){
    		
    	}
    }
    
    /*
    Oracle的数据库厂家负责编写JDBC接口的实现类
    */
    public class Oracle implements JDBC{
    	
    	public void getConnection(){
    		
    	}
    }
    
    /*
    Java程序员
    只需要面向JDBC接口写代码
    */
    import java.util.*;
    public class JavaProgrammer{
    	
    	public static void main(String[] args){
    		
    		//JDBC jdbc = new MySQL();
    		
    		//创建对象可以通过反射机制
    		//Class c = Class.forname(className);
    		ResourceBundle bundle = ResourceBundle.getBundle("jdbc");//配置文件
    		String className = bundle.getString("className");
    		Class c = Class.forname(className);
    		JDBC jdbc = (JDBC)c.newInstance();
    		jdbc.getConnection();
    	}
    }
    
    /*
    这是配置文件 jdbc.properties
    */
    className=Oracle
    

二、JDBC编程六步

第一步:注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)

第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,使用完之后一定要关闭通道。)

第三步:获取数据库操作对象(专门执行sql语句的对象)

第四步:执行SQL语句(DQL DML…)

第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)

第六步:释放资源(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。)

1、注册驱动

在java.sql包下有个类为DriverManager中有一个静态方法:(该方法会抛出SQLException)

static void registerDriver(Driver driver) 
          向 DriverManager 注册给定驱动程序。 

需要传一个驱动对象,但是Driver是接口无法new对象,所以应该找实现类。

DriverManager.registerDriver(new com.mysql.jdbc.Driver());

将以上代码拆开写

java.sql.Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);

注册驱动的另外一种方式:

在com.mysql.jdbc.Driver()实现类的源代码中,有一段静态代码块:

static{
	try{
		java.sql.DriverManager.registerDriver(new Driver());
	}catch(SQLException E){
		throw new RuntimeException("can't register driver!");
	}
}

通过加载类,执行静态代码块——>反射机制

Class.forName("com.mysql.jdbc.Driver");

这种方式的参数是一个字符串,字符串可以写到配置文件当中,所以更为常用。

2、获取连接

在java.sql包下有个类为DriverManager中有一个静态方法:

static Connection getConnection(String url, Properties info) 
          试图建立到给定数据库 URL 的连接。 
String url ="jdbc:mysql://127.0.0.1:3306/cooler";
//Oracle的URL:jdbc:Oracle:thin:@localhost:1521:orcl
String user = "root";
String password = "3399"
Connection conn = DriverManager.getConnection(url,user,password);

url:统一资源定位符(网络中某个资源的绝对路径)

URL包括:协议、IP、port端口、资源名。

http://182.61.200.7:80/index.html

http://			通信协议——通信之前就是提前定好的数据传送格式。
182.61.200.7	服务器IP地址
80				服务器上软件的端口
index.html		服务器上某个资源名
jdbc:mysql://127.0.0.1:3306/cooler

jdbc:mysql://	协议
127.0.0.1		IP地址
3306			mysql数据库端口号
cooler			具体的数据库实例名

说明:localhost和127.0.0.1都是本机IP地址。
3、获取数据库操作对象
 Statement createStatement()           创建一个 Statement 对象来将 SQL 语句发送到数据库。 

获取数据库操作对象

Statement stmt = conn.createStatement();
4、执行sql
String sql = "insert into dept(deptno,dname,loc) values (50,'人事部',‘北京)";

//专门执行DML语句
//返回值是影响数据库中的记录条数
int count = stmt.executeUpdate(sql);
5、处理查询结果集
//int executeUpdate(insert/delete/update)
ResultSet excuteQuery(select)
String sql = "select empno,ename,sal from emp";
ResultSet rs = stmt.createQuery(sql);
while(rs.next()){
	//以列的名字获取,列名称不是表中的列名称,是查询结果集的列名称,如果对列进行了重命名,此处应该填命名后的列名称
	String empno = rs.getString("empno");
	String ename = rs.getString("ename");
	String sal = rs.getString("sal");//也可以调用getDouble方法直接返回Double类型,之后可对该数据进行运算
	System.out.println(empno + "," + ename + "," + sal);
}
6、释放资源

为了保证资源一定释放,在finally语句块中关闭资源,并且要遵循从小到大依次关闭,分别对其try…catch

7、从属性资源文件中读取连接数据库
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;

public class JDBCTest {
    public static void main(String[] args) {
        
        //使用资源绑定器绑定属性配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");

        Connection conn = null;
        Statement stmt = null;
        try {
            //1.注册驱动
            Class.forName(driver);
            //2.获取连接
            conn = DriverManager.getConnection(url,user,password);
            //3.获取数据库对象
            stmt = conn.createStatement();
            //4.执行sql
            String sql = "update dept set dname ='xiaoshoubu',loc = 'tianjin', where deptno = 20";
            int count = stmt.executeUpdate(sql);
            System.out.println(count == 1 ? "修改成功" : "修改失败");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            if (stmt == null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn == null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/cooler
user=root
password=3399

三、

(一)模拟登录功能

实现功能:

1、需求:模拟用户登录功能的实现

2、业务描述:程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码,输入之后提交信息,java程序收集用户信息。 java程序连接数据库验证用户名和密码是否合法。

​ 合法:显示登录成功

​ 不合法:显示登录失败

3、数据的准备

​ 使用建模工具进行数据库表的设计。

package com.cooler.jdbc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class JDBCTest01 {
    public static void main(String[] args) {

        //初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        //验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
    }

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return false表示失败,true表示成功
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess = false;
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        //JDBC代码
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            //1、注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cooler","root","3399");
            //3、获取数据库操作对象
            stmt = conn.createStatement();
            //4、执行sql
            String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd +"' ";
            rs = stmt.executeQuery(sql);

            if(rs.next()){
                loginSuccess = true;
            }
            //5、处理查询结果集
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //6、释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    /**
     * 初始化用户界面
     * @return 用户输入的用户名和密码等登录信息
     */
    private static Map<String, String> initUI() {

        Scanner sc = new Scanner(System.in);
        System.out.println("用户名:");
        String loginName = sc.nextLine();
        System.out.println("密码:");
        String loginPwd = sc.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        return userLoginInfo;
    }


}

当前程序存在的问题:

用户名:
fdsa
密码:
fdsa'  or  '1' ='1
登录成功

这种现象被称为SQL注入。(黑客经常使用)

(二)SQL注入现象

1、导致SQL注入的根本原因

用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的原意被扭曲,进而达到sql注入。

2、解决SQL注入问题

要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement,

PreparedStatement接口继承了java.sql.Statement

PreparedStatement属于预编译的数据库操作对象。预先对SQL语句的框架进行编译,然后再给SQL语句传“值”。

package com.cooler.jdbc;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class JDBCTest01 {
    public static void main(String[] args) {

        //初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        //验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
    }

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return false表示失败,true表示成功
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess = false;
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        //JDBC代码
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            //1、注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cooler","root","3399");
            //3、获取预编译的数据库操作对象
            String sql = "select * from t_user where loginName = ? and loginPwd = ?" ;//SQL语句的框架,其中一个问号表示一个占位符,接收一个“值”
            ps = conn.prepareStatement(sql);
            //给占位符传“值”(第一个?下标为1)
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            //4、执行sql
            rs = ps.executeQuery();

            if(rs.next()){
                loginSuccess = true;
            }
            //5、处理查询结果集
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //6、释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    /**
     * 初始化用户界面
     * @return 用户输入的用户名和密码等登录信息
     */
    private static Map<String, String> initUI() {

        Scanner sc = new Scanner(System.in);
        System.out.println("用户名:");
        String loginName = sc.nextLine();
        System.out.println("密码:");
        String loginPwd = sc.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        return userLoginInfo;
    }
}
(三)Statement和PreparedStatement对比

1、Statement和PreparedStatement对比

——Statement存在sql注入问题,PreparedStatement解决理论sql问题。

——Statement是编译一次执行一次,PreparedStatemet是编译一次,可执行N次。

——PreparedStatemet会在编译阶段做类型的安全检查。

2、在业务方面需求必须支持SQL的注入的时候,需要进行sql语句拼接的时候,必须使用Statement。

——演示Statement的用途

package com.cooler.jdbc;

import java.sql.*;
import java.util.Scanner;

/**
 * @author CoolEr
 * @date 2022/1/24 20:05
 */
public class JDBCTest02 {
    public static void main(String[] args) {
        //用户在控制台输入desc就是降序,输入asc就是升序
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入desc或者asc,desc表示降序,asc表示升序:");
        String keyWords = sc.nextLine();

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cooler","root","3399");
            stmt = conn.createStatement();
            String sql = "select ename from emp order by ename " + keyWords;
            rs = stmt.executeQuery(sql);
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3、PreparedStatemet完成增删改

package com.cooler.jdbc;

import java.sql.*;

/**
 * @author CoolEr
 * @date 2022/1/24 20:19
 * PreparedStatement完成 INSERT DELETE UPDATE
 */
public class JDBCTest03 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cooler","root","3399");

            /*
            String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,60);
            ps.setString(2,"销售部");
            ps.setString(3,"shanghai");*/

            /*String sql = "update dept set dname = ?,loc = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"研发部");
            ps.setString(2,"hangzhou");
            ps.setInt(3,60);
             */
            String sql = "delete from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,60);

            int count = ps.executeUpdate();
            System.out.println(count);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
(四)JDBC的事务自动提交机制

1、JDBC中的事务自动提交,只要执行任意一条DML语句,则自动提交一次。

​ 但是在实际业务中,通常都是N条DML语句共同联合才能完成,必须保证这些DML语句同时成功或者同时失败。

package com.cooler.jdbc;

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

/**
 * @author CoolEr
 * @date 2022/1/24 20:35
 */
public class JDBCTest04 {
    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement ps = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cooler", "root", "3399");  
            
            conn.setAutoCommit(false);//将自动提交改成手动
            
            String sql = "update t_act set balance = ? where actno = ?";
            
            ps = conn.prepareStatement(sql);
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            int count = ps.executeUpdate();

            ps = conn.prepareStatement(sql);
            ps.setDouble(1,10000);
            ps.setInt(2,222);
             count += ps.executeUpdate();
            System.out.println(count == 2 ? "转账成功" : "转账失败");
            
            //程序能运行到此处说明以上没有异常,事务结束,手动提交
            conn.commit();
        } catch (Exception e) {
            //回滚事务
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

重点的三行代码:

conn.setAutoCommit(false);
conn.commit();
conn.rollback();
(五)JDBC工具类的封装

1、JDBC工具类的封装

package com.cooler.jdbc.utils;

import java.sql.*;

/**
 * @author CoolEr
 * @date 2022/1/24 21:30
 */
public class DBUtil {

    private DBUtil() {
    }

    static{
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException {

        return DriverManager.getConnection("jdbc:mysql://localhost:3306/cooler", "root", "3399");
    }
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

2、JDBC实现模糊查询

package com.cooler.jdbc;

import com.cooler.jdbc.utils.DBUtil;

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

/**
 * @author CoolEr
 * @date 2022/1/25 15:17
 */
public class JDBCTest05 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            conn = DBUtil.getConnection();
            String sql = "select ename from emp where ename like ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"_A%");
            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            DBUtil.close(conn,ps,rs);
        }
    }
}

查询的结果:

WARD
MARTIN
JAMES

四、行级锁

1、悲观锁和乐观锁

悲观锁:事务必须排队执行,数据锁住了,不允许并发。(行级锁:select后面添加 for update)

乐观锁:支持并发,事务不需要排队,只不过需要一个版本号。

2、演示行级锁机制

package com.cooler.jdbc;

import com.cooler.jdbc.utils.DBUtil;

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

/**
 * @author CoolEr
 * @date 2022/1/25 15:39
 * 这个程序开启一个事务,专门进行查询,并且使用行级锁。
 */
public class JDBCTest06 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);

            String sql = "select ename,job,sal from emp where job = ? for update";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");
            rs = ps.executeQuery();
            while(rs.next()){
                System.out.println(rs.getString("ename") + ","
                        +rs.getString("job") + "," + rs.getDouble("sal"));
            }
            conn.commit();
        } catch (Exception e) {
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally{
            DBUtil.close(conn,ps,rs);
        }
    }
}
package com.cooler.jdbc;

import com.cooler.jdbc.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author CoolEr
 * @date 2022/1/25 15:39
 * 这个程序负责修改被锁定的记录
 */
public class JDBCTest07 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        
        try {
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);
            
            String sql = "update emp set sal = sal * 1.1 where job = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");
            int count = ps.executeUpdate();
            System.out.println(count);
            
            conn.commit();
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally{
            DBUtil.close(conn,ps,null);
        }
    }
}

如果在JDBCTest06 事务提交前打下断点,再运行JDBCTest07,JDBCTest07不会输出count,一旦JDBCTest06继续运行,事务提交,则JDBCTest07输出count为3。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值