JDBC快速学习入门

JDBC(Java Database Connectivity,Java数据库连接)是一组独立于任何数据库管理系统(DBMS)的API,声明在java.sqljavax.sql包中。它由SUN公司提供的一套接口规范,各个数据库厂商根据该规范提供具体的实现类,这些实现类的集合构成了数据库驱动JAR包。

1. 数据库管理系统

常见的数据库管理系统(DBMS)包括:MySQL、Oracle、DB2、SQL Server等。

2. 接口概述

Java中的接口可以包含以下内容:

  1. 公共的静态常量:由public static final修饰的变量。
  2. 公共的抽象方法:未实现的方法,需要具体的实现类来提供方法体。
  3. 默认方法:使用default关键字修饰的方法,可以在接口中提供默认的实现。
  4. 静态方法:使用static关键字修饰的方法,可以在接口中定义静态方法。

3. JDBC技术的组成

JDBC技术主要包含两个部分:

  1. Java APIjava.sqljavax.sql包):为了保证项目代码的可移植性和可维护性,SUN公司制定了Java程序连接各种数据库的统一接口规范。无论连接哪一种DBMS软件,Java代码都可以保持一致性。
  2. 数据库厂商提供的驱动JAR包:由于各个数据库厂商的DBMS软件内部实现各有不同,具体的SQL操作(增、删、改、查)只有数据库厂商最为清楚,因此接口规范的实现由各个数据库厂商自行完成。

4. JDBC的作用

使用JDBC技术,可以通过Java代码对数据库中的数据进行增、删、改、查等操作。


【重点】JDBC连接的步骤

JDBC连接数据库的步骤通常包括以下五个部分:

1. 注册驱动

  1. 引入驱动JAR包:将数据库驱动的JAR包复制到项目的lib目录中。
  2. 添加到构建路径:将驱动JAR包添加到项目的Build Path中。
  3. 加载驱动类:通过Class.forName("com.mysql.cj.jdbc.Driver")方法,将驱动类加载到内存中。
// 加载MySQL驱动类
Class.forName("com.mysql.cj.jdbc.Driver");

注意事项:对于新版的JDBC驱动,可以省略这一步,驱动程序会自动注册。但为了兼容性,建议手动加载。

2. 获取Connection连接对象

通过驱动管理器获取数据库连接对象:

// 获取数据库连接
String url = "jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username = "yourUsername";
String password = "yourPassword";
Connection conn = DriverManager.getConnection(url, username, password);
  • DriverManager的作用:用于获取Connection对象。如果成功获取到Connection,则表示已经与数据库建立了连接。
  • URL的作用:标识数据库的位置,告诉JDBC程序要连接的数据库。URL的语法如下:
jdbc:mysql://ip地址:端口号/数据库名?参数名=参数值

例如:

jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false

其中,各参数的含义如下:

    • useUnicode=true:表示使用Unicode字符集,允许使用中文等非ASCII字符。
    • characterEncoding=utf8:指定字符编码方式为UTF-8。
    • useSSL=false:禁用SSL连接,避免因为未配置SSL证书而导致连接失败。

Connection对象的常用方法

  • StatementcreateStatement():创建用于发送简单SQL语句的Statement对象。
  • PreparedStatementprepareStatement(String sql):创建用于发送预编译SQL语句的PreparedStatement对象。推荐使用。
  • CallableStatementprepareCall(String sql):创建用于调用存储过程的CallableStatement对象。
  • void setAutoCommit(boolean autoCommit):设置事务是否自动提交。如果设置为false,则需要手动提交事务。
  • void commit():提交事务。
  • void rollback():回滚事务。

注意事项:在实际开发中,通常使用PreparedStatement替代Statement,以提高性能并防止SQL注入。

3. 创建Statement,执行SQL语句

1. 编写SQL语句

主要是DML(数据操作语言,如INSERT、UPDATE、DELETE)和DQL(数据查询语言,如SELECT)语句。

2. 创建Statement或PreparedStatement对象

使用Statement对象:

// 使用Statement对象
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM users WHERE id = " + userId;
ResultSet rs = stmt.executeQuery(sql);

使用PreparedStatement对象:

// 使用PreparedStatement对象
String str5="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno = ? AND sage = ?";
PreparedStatement preparedStatement = connection.prepareStatement(str5);
// 设置SQL语句中的第一个参数(?)的值为userId,这里的1表示参数的位置索引。
//1代表第一个?
preparedStatement.setString(1,"S1002");
preparedStatement.setInt(2,14);

preparedStatement.executeQuery();

两者的区别:

  • 安全性Statement直接拼接SQL语句,容易受到SQL注入攻击;PreparedStatement使用参数占位符,能够防止SQL注入。
  • 性能PreparedStatement会预编译SQL语句,执行效率更高,尤其是在批量执行相似语句时。
  • 可读性PreparedStatement代码更清晰,参数绑定方式使代码更易维护。

建议始终使用PreparedStatement替代Statement

3. 执行SQL语句

  • 增、删、改操作:使用executeUpdate()方法,返回受影响的行数。
  • 查询:调用executeQuery方法,Statement对象调用方法,需要传递SQL参数;PreparedStatement调用上述方法,不需要传递SQL参数;
  • PreparedStatement批处理
    • addBatch(String sql):把多条sql语句放到一个批处理中。
    • executeBatch():向数据库发送一批sql语句执行。

使用Statement执行增删改:

Statement stmt = conn.createStatement();
String insertSql = "INSERT INTO users (name, email) VALUES ('张三', 'zhangsan@example.com')";
int rowsAffected = stmt.executeUpdate(insertSql);

使用PreparedStatement执行增删改:

String insertSql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(insertSql);
pstmt.setString(1, "张三");
pstmt.setString(2, "zhangsan@example.com");
int rowsAffected = pstmt.executeUpdate();
  • 查询操作:使用executeQuery()方法,返回ResultSet结果集。

使用Statement执行查询:

Statement stmt = conn.createStatement();
String selectSql = "SELECT * FROM users WHERE id = " + userId;
ResultSet rs = stmt.executeQuery(selectSql);

使用PreparedStatement执行查询:

String selectSql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(selectSql);
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();
  • PreparedStatement批处理操作
    • url添加rewriteBatchedStatements=true
    • preparedStatement.addBatch();代替原本的修改查询操作
    • preparedStatement.executeBatch();结束操作
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/studentinfodb?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&useSSL=false";
String str1="insert into student(sno,sname,ssex,sage,stel) values (?,?,?,?,?)";
Connection connection = DriverManager.getConnection(url, "root", "1234");
PreparedStatement preparedStatement = connection.prepareStatement(str1);

for (int i = 1; i <= 200; i++) {
    preparedStatement.setString(1,"S100"+i);
    preparedStatement.setString(2,"username_"+i);
    preparedStatement.setString(3,"男");
    preparedStatement.setInt(4,18);
    preparedStatement.setInt(5,123456789);
    preparedStatement.addBatch();//addBatch()代替executeUpdate()和executeQuery()
}
preparedStatement.executeBatch();

注意事项:使用PreparedStatement时,SQL语句中的参数使用?占位符,然后通过setXxx()方法设置参数值。

4. 处理执行结果

1. 处理增、删、改的结果

executeUpdate()方法返回一个整数,表示受影响的行数。

if (rowsAffected > 0) {
    System.out.println("操作成功,影响了 " + rowsAffected + " 行数据。");
} else {
    System.out.println("操作失败,未影响任何数据。");
}
2. 处理查询的结果

executeQuery()方法返回一个ResultSet对象,包含查询结果。

//1.查询返回一个ResultSet类的结果集对象
resultSet = statement.executeQuery(str4);
//resultSet.next()判断是否为空,返回布尔值
//返回一个数据  select count(1) from student
if(resultSet.next()){
    //选择get什么取决于查出的是什么
    //内容为查出表的列下标,从1开始
    int dCount  = resultSet.getInt(1);
    System.out.println("dCount = " + dCount);
}
//2.查询返回一行数据 select * from student where sno='S1006'
if(resultSet.next()){
    String sno = resultSet.getString(1);
    String sname = resultSet.getString(2);
    String ssex = resultSet.getString(3);
    int sage = resultSet.getInt(4);
}
//3.查询返回的是一个表 select * from student
while(resultSet.next()){
    //注意这里的是输出表的列名,如果起别名就是别名
    String sno = resultSet.getString("sno");
    String sname = resultSet.getString("sname");
    String ssex = resultSet.getString("ssex");
    int sage = resultSet.getInt("sage");
}
ResultSet对象的常用方法
  • boolean next():移动光标到下一行,返回true表示有数据。
  • String getString(int columnIndex) / String getString(String columnName):获取VARCHAR、CHAR等类型的数据。
  • int getInt(int columnIndex) / int getInt(String columnName):获取INT类型的数据。
  • float getFloat(int columnIndex) / float getFloat(String columnName):获取FLOAT类型的数据。
  • java.sql.Date getDate(int columnIndex) / java.sql.Date getDate(String columnName):获取DATE类型的数据。
  • boolean getBoolean(int columnIndex) / boolean getBoolean(String columnName):获取BOOLEAN类型的数据。
  • Object getObject(int columnIndex) / Object getObject(String columnName):获取任意类型的数据。

注意事项:列索引从1开始,而不是0。建议使用列名获取数据,提高代码的可读性和维护性。

5. 释放资源

按照先开后关的原则,依次关闭ResultSetStatementPreparedStatementConnection等资源。

// 关闭ResultSet
if (rs != null) {
    try {
        rs.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 关闭Statement或PreparedStatement
if (stmt != null) {
    try {
        stmt.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
if (pstmt != null) {
    try {
        pstmt.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 关闭Connection
if (conn != null) {
    try {
        conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

注意事项:为了避免资源泄漏,建议在finally块中关闭资源,或者使用try-with-resources语句。

补充注意事项

  • 防止SQL注入:始终使用PreparedStatementCallableStatement来替代Statement,避免在SQL语句中直接拼接用户输入的参数。
  • 事务管理:对于涉及多个步骤的数据库操作,考虑使用事务,确保数据的一致性。记得在操作前设置conn.setAutoCommit(false),操作完成后提交或回滚事务。
  • 异常处理:所有的数据库操作都可能抛出SQLException,需要进行适当的异常处理。
  • 数据库连接池:在实际开发中,频繁创建和关闭数据库连接会影响性能,建议使用数据库连接池(如Druid、C3P0等)来管理连接。

JDBC执行SQL事例

JDBC使用Statement

思路:

  1. 首先获取驱动
  2. 创建连接对象
  3. 获取Statement声明对象来操控数据库
  4. 使用statement对象操控数据库
  5. 关闭资源
String str2="UPDATE student SET sname='小三郎' WHERE sno='S1006'";
String str4="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno='S1006' ";

String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
//为什么提到上面
//1.因为finally要关闭,如果不定义全局finally找不到connection对象
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
try {
    //1.首先获取驱动
    Class.forName(jdbc);
    //2.创建连接对象
    connection = DriverManager.getConnection(url, username, password);
    //3.获取Statement声明对象来操控数据库
    statement = connection.createStatement();
    //4.使用statement对象操控数据库
    //4.1增删改返回被影响的数据库行数
    int i = statement.executeUpdate(str2);
    System.out.println("被影响的行数"+i);
    //4.2查询返回一个ResultSet类的结果集对象
    resultSet = statement.executeQuery(str4);
    //resultSet.next()判断是否为空,返回布尔值
    //4.2.1返回一个数据  select count(1) from student
    if(resultSet.next()){
        //选择get什么取决于查出的是什么
        //内容为查出表的列下标,从1开始
        int dCount  = resultSet.getInt(1);
        System.out.println("dCount = " + dCount);
    }
    //4.2.1查询返回一行数据 select * from student where sno='S1006'
    if(resultSet.next()){
        String sno = resultSet.getString(1);
        String sname = resultSet.getString(2);
        String ssex = resultSet.getString(3);
        int sage = resultSet.getInt(4);
    }
    //4.2.3查询返回的是一个表 select * from student
    while(resultSet.next()){
        //注意这里的是输出表的列名,如果起别名就是别名
        String sno = resultSet.getString("sno");
        String sname = resultSet.getString("sname");
        String ssex = resultSet.getString("ssex");
        int sage = resultSet.getInt("sage");
    }
} catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
} catch (SQLException e) {
    throw new RuntimeException(e);
}finally{
    //注意:先打开的后关闭
    //判断是否为空,因为有可能进入异常导致resultSet没有
    if(resultSet!=null){
        try {
            resultSet.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    if(statement!=null){
        try {
            statement.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    if(connection!=null){
        try {
            connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

PreparedStatement和事务

注意:

  1. 手动开启事务后一定要再改为自动
String str2="UPDATE student SET sname='小三郎' WHERE sno='S1006'";
String str5="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno = ? AND sage = ?";
String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
Connection connection=null;
PreparedStatement preparedStatement =null;
ResultSet resultSet=null;
try {
    Class.forName(jdbc);
    connection = DriverManager.getConnection(url, username, password);
    //开启事务====>把自动改为手动
    connection.setAutoCommit(false);
    System.out.println("事务开始手动");
    preparedStatement = connection.prepareStatement(str2);
    preparedStatement.executeUpdate();
    //开启第二条数据库操作
    preparedStatement = connection.prepareStatement(str5);
    //需要插入什么类型就set什么,下标为?的下标
    preparedStatement.setString(1,"S1006");
    preparedStatement.setInt(2,14);
    //其余与statement一样,知识方法没有参数
    resultSet = preparedStatement.executeQuery();
    if(resultSet.next()){
        String sno = resultSet.getString(1);
        String sname = resultSet.getString(2);
        String ssex = resultSet.getString(3);
        int sage = resultSet.getInt(4);
    }
    //手动提交是事务---运行到这里代表没有异常,可以提交
    connection.commit();
} catch (Exception e) {
    //开启回滚---运行到这里代表有异常,进行回滚
    connection.rollback();
    throw new RuntimeException(e);
} finally{
    if(resultSet!=null){
        try {
            resultSet.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    if (preparedStatement!=null){
        try {
            preparedStatement.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    if(connection!=null){
        try {
            //恢复事务自动提交
            connection.setAutoCommit(true);
            System.out.println("事务自动提交");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        try {
            connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

开始批处理

  1. url添加rewriteBatchedStatements=true
  2. preparedStatement.addBatch();代替原本的修改查询操作
  3. preparedStatement.executeBatch();结束操作
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/studentinfodb?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&useSSL=false";
String str1="insert into student(sno,sname,ssex,sage,stel) values (?,?,?,?,?)";
Connection connection = DriverManager.getConnection(url, "root", "1234");
PreparedStatement preparedStatement = connection.prepareStatement(str1);

long l = System.currentTimeMillis();

for (int i = 1; i <= 200; i++) {
    preparedStatement.setString(1,"S100"+i);
    preparedStatement.setString(2,"username_"+i);
    preparedStatement.setString(3,"男");
    preparedStatement.setInt(4,18);
    preparedStatement.setInt(5,123456789);
    preparedStatement.addBatch();
}
preparedStatement.executeBatch();
long l1 = System.currentTimeMillis();
System.out.println(l1-l);
preparedStatement.close();
connection.close();

DruidPooled连接池

步骤:

  1. 创建DruidDataSource连接池对象
  2. 为数据源设置参数[(1)设置基本参数]
  3. 设置连接数等参数
  4. 通过数据源获取Connection对象
  5. 关闭Connection资源,这里关闭,就相当于归还回池中
String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
String str1="insert into student(sno,sname,ssex,sage,stel) values ('S1006','小二郎','男',14,321323)";
String str2="UPDATE student SET sname='小三郎' WHERE sno='S1006";
String str3="DELETE FROM student WHERE sno='S1006'";
String str4="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno='S1006' ";
DruidPooledConnection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet=null;
try {
    //创建数据库连接池
    DruidDataSource druidDataSource = new DruidDataSource();
    //为数据源设置参数[(1)设置基本参数]
    druidDataSource.setDriverClassName(jdbc);
    druidDataSource.setUrl(url);
    druidDataSource.setUsername(username);
    druidDataSource.setPassword(password);
    //(2)设置连接数等参数
    druidDataSource.setInitialSize(5);//初始化几个连接对象
    druidDataSource.setMinIdle(3);//最少保留几个
    druidDataSource.setMaxActive(8);//最大几个
    druidDataSource.setMaxWait(1000);//最多等待多久

    //通过数据源获取Connection对象
    connection = druidDataSource.getConnection();
    connection.setAutoCommit(false);
    preparedStatement = connection.prepareStatement(str4);
    resultSet = preparedStatement.executeQuery();
    if(resultSet.next()){
        String sno=resultSet.getString(1);
        String sname=resultSet.getString(2);
        int sage=resultSet.getInt(4);
        System.out.println(sno+sname+sage);
    }
} catch (SQLException e) {
    throw new RuntimeException(e);
}finally {
    resultSet.close();
    preparedStatement.close();
    //如果这里没有关闭,就相当于没有还,这里关闭,是还回池中
    connection.close();
}

创建JDBC工具类

版本一

private static DruidDataSource druidDataSource;

static {
    String jdbc="com.mysql.cj.jdbc.Driver";
    String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
    String username="root";
    String password="1234";
    druidDataSource = new DruidDataSource();
    druidDataSource.setDriverClassName(jdbc);
    druidDataSource.setUrl(url);
    druidDataSource.setUsername(username);
    druidDataSource.setPassword(password);
    //(2)设置连接数等参数
    druidDataSource.setInitialSize(5);
    druidDataSource.setMinIdle(3);
    druidDataSource.setMaxActive(8);
    druidDataSource.setMaxWait(1000);
}

public static Connection getConnection(){
    DruidPooledConnection connection=null;
    try {
        connection = druidDataSource.getConnection();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
    return connection;
}

public static void releaseConnection(Connection connection){
    try {
        connection.close();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

版本二

缺点,pro文件的键值对名字需要和Druid包定义的一样

private static DataSource dataSource;
static {
    Properties properties = new Properties();
    try {
        properties.load(JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
        dataSource = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

}

public static Connection getConnection(){
    Connection connection=null;
    try {
        connection = dataSource.getConnection();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
    return connection;
}

public static void releaseConnection(Connection connection){
    try {
        connection.close();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/studentinfodb?
useUnicode=true&characterEncoding=utf8&useSSL=false&rewriteBatchedStatements=true
username=root
password=1234
initialSize=5
maxActive=10
minIdle=3
maxWait=1000

使用外部的QueryRunner

  1. 先导入Druid连接池的jar包
  2. 创建Druid连接池对象
  3. 给数据源设置参数(JDBC连接驱动,数据库url,username,password)
  4. 设置数据源的连接池参数(初始化连接数,最小连接数,最大连接数,最大等待时间等)
  5. 通过数据源获取connection连接对象
  6. 创建QueryRunner对象来代替原本的Statement对象和PreparedStatement对象
//获取QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
//获取connection连接对象
Connection connection = JDBC_utils2.getConnection();
//使用update增删改
String insert_sql="insert into student(sno,sname,ssex,sage,stel) values ('S1006','小二郎','男',14,321323)";
int update = queryRunner.update(connection, insert_sql);
System.out.println("受影响行数"+update);
//返回一个数据
String str4="SELECT count(1) FROM student";
Long query = queryRunner.query(connection, str4, new ScalarHandler<>());
System.out.println("总数为"+query);
//返回一行数据
String str5="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno = ? AND sage = ?";
Student s1006 = queryRunner.query(connection, str5, new BeanHandler<>(Student.class), "S1006", 14);
System.out.println(s1006);
//返回多行数据
String str6="select * from student";
List<Student> query1 = queryRunner.query(connection, str6, new BeanListHandler<>(Student.class));
query1.forEach(System.out::println);
//关闭connection连接对象
JDBC_utils2.releaseConnection(connection);

QueryRunner注意事项

queryRunner.query返回的是一个long类型

String str6="select count(1) from student";
Long query = queryRunner.query(connection, str6, new ScalarHandler<>());

QueryRunner对象原理

执行PreparedStatement需要先获取connection对象,以及sql语句,如果是query还需要返回结果集所以需要一个结果集类型,以及需要插入的值,由于插入个数不确定所以使用不定参数。方法内部通过数组的方式来在sql中插入值。

  1. 获取数据库连接:需要传入一个Connection对象。
  2. 准备SQL语句:需要传入一个SQL字符串。
  3. 设置参数:由于参数数量不确定,使用可变参数(Varargs)或参数数组。
  4. 执行语句:根据需求执行查询或更新操作。
  5. 处理结果集:对于查询操作,需要返回结果集,通常通过ResultSetHandler接口处理结果集。
public class MyQueryTool {
    public <T> List<T> query(Connection connection, String sql, Class<T> beanClass, Object... params) {
        QueryRunner runner = new QueryRunner();
        try {
            // 使用BeanListHandler将结果映射为对象
            BeanListHandler<T> handler = new BeanListHandler<>(beanClass);
            return runner.query(connection, sql, handler, params);
        } catch (SQLException e) {
            e.printStackTrace();
            return null; // 根据需要处理异常
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值