数据库访问技术-JDBC&Mybatis&Mybatis-Plus及非Spring环境下代码实现

目录

JDBC

(1)TestStatement

(2)TestPreparedStatement

(3)对JDBC进行封装,并实现从.properties配置文件中读取连接信息

Mybatis持久层框架

(1)Mybatis持久层框架的好处:

(2)Mybatis代码实现

(3)Mybatis的一些知识点

Mybatis-Plus框架

Mybatis-Plus代码实现(非Spring环境):

JDBC

Java Database Connect,SUN公司官方提供的Java访问数据库的接口。

编程前:

在项目中新建一个lib目录,将mysql-connector-java的jar包复制到lib下,并将lib设置为库,be like:

 编程案例:

第一个使用Statement,第二个使用PreparedStatement

Statement跟PreparedStatement的对比:

①Statement执行静态SQL语句,它的执行方式是直接将SQL语句发送给数据库,由数据库执行和解析,存在SQL注入的风险

②PreparedStatement的执行方式是先将参数化的SQL语句发送给数据库进行预编译,再将参数传递给预编译后的SQL语句执行,可以有效防止SQL注入,提升程序安全性。

(1)TestStatement

由下方代码,Statement类执行的sql命令是由字符串直接拼接而成的。

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

        Connection conn = null;
        try {
            //加载驱动mysql8.0版本
            Class.forName("com.mysql.cj.jdbc.Driver");
            //建立连接,连接到myschool库
            conn = DriverManager.getConnection("jdbc:mysql:///myschool", "root", "");
            //编写SQL语句
            String name = "Saynoon";
            String password = "hello";
            String sql = "insert into `users`(`username`,`password`) values('" + name + "','" + password + "')";
            //创建命令对象
            Statement statement = conn.createStatement();
            //执行命令对象
            statement.executeUpdate(sql);

            System.out.println("success");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                conn.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}

(2)TestPreparedStatement

而PreparedStatement执行的sql命令则是将值以参数传输到预编译命令中

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

        Connection conn = null;
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql:///myschool","root","");
            String sql = "insert into `users`(`username`,`password`) values (?,?)";
            //预编译命令,用?代替未知项
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            //给未知项赋值,索引从1开始
            preparedStatement.setString(1,"Saynoon2");
            preparedStatement.setString(2,"hello");
            //执行命令对象
            preparedStatement.execute();
            System.out.println("success");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                conn.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}

(3)对JDBC进行封装,并实现从.properties配置文件中读取连接信息

以下代码中,关于Object类的一些小知识点:

①Object类是每个类的直接或间接父类;Object类的引用可以存储任意类型的对象;Object类的方法是每个类的默认功能方法。

②Object类的类型转换:

以Object类转为int/Integer为例:

一是强制类型转换:(Integer) object,此时兼容对象object为null的情况

二是先用toString方法将Object对象转为String类型,再用Integer的valueof/parseInt方法;此时object不能为null

将object转为Integer对象:Integer.valueof(object.toString())

将object转为int类型:Integer.parseInt(object.toString())

关于ResultSet类的小知识点:

next()方法:第一次执行时,读取ResultSet第一行的结果;next方法执行成功时,就会返回true,失败则返回false。

get***()方法:和next()方法结合使用,获取ResultSet某行数据某个属性的值;该方法参数有两种:get***(int columnIndex)、get***(String columnLabel),即列序号、属性名。

public class JDBCConfigUtil {

    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;
    protected Connection conn;

    //静态代码块
    static {
        try{

            //获取当前对象所属的class对象的类加载器,使用类加载器的方法加载资源作为输入流
            InputStream inputStream = JDBCConfigUtil.class.getClassLoader().getResourceAsStream("database.properties");

            //使用Properties类,从.properties文件输入流中,加载属性列表到Properties对象中
            Properties properties = new Properties();
            properties.load(inputStream);

            //通过getProperty方法在属性列表中搜索属性,动态获取配置文件的数据
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");

            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }

    public boolean executeUpdate(String sql, Object[] params){
        try {
            conn = getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try{
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            preparedStatement = matchType(preparedStatement, params);
            if (preparedStatement.executeUpdate() > 0){
                return true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }

    public ResultSet executeQuery(String sql, Object[] params){
        try {
            conn = getConnection();
        }catch (SQLException e){
            e.printStackTrace();
        }
        try {
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            preparedStatement = matchType(preparedStatement, params);
            return preparedStatement.executeQuery();
        }catch (SQLException e){
            e.printStackTrace();
        }
        return null;
    }

    public void close(){
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public PreparedStatement matchType(PreparedStatement preparedStatement, Object[] params)  {
        try {
            for (int i = 0; i < params.length; i++) {
                Object param = params[i];

                if (param instanceof Integer) {
                    //强制类型转换,兼容param为null的情况
                    //preparedStatement.setInt(i + 1, (Integer) param);

                    //不兼容param为null的情况:
                    //一是转为Integer对象
                    //preparedStatement.setInt(i + 1, Integer.valueOf(param.toString()));
                    //二是转为int变量类型
                    preparedStatement.setInt(i + 1, Integer.parseInt(param.toString()));
                } else if (param instanceof Double) {
                    //preparedStatement.setDouble(i + 1, (Double) param);
                    preparedStatement.setDouble(i + 1, Double.parseDouble(param.toString()));
                } else if (param instanceof Float) {
                    //preparedStatement.setFloat(i + 1, (Float) param);
                    preparedStatement.setFloat(i + 1, Float.parseFloat(param.toString()));
                } else if (param instanceof String) {
                    //preparedStatement.setString(i + 1, (String) param);
                    preparedStatement.setString(i + 1, param.toString());
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return preparedStatement;
    }
}
public class TestConfig {
    public static void main(String[] args) {

        //创建JDBCConfigUtil对象
        JDBCConfigUtil jdbcConfig = new JDBCConfigUtil();

        //写SQL更新语句
        String updateSql = "insert into `users`(`username`,`password`) values (?,?)";

        //封装参数
        Object[] updateParams = {"Saynoon", "hello2"};

        //调用JDBCConfigUtil中的写方法,执行命令,输出更新结果
        System.out.println(jdbcConfig.executeUpdate(updateSql, updateParams));

        //写SQL查询语句
        String querySql = "select * from `users` where `username` = ?";

        //封装参数
        Object[] queryParams = {"Saynoon"};

        //调用JDBCConfigUtil中的查方法,执行命令,输出查询结果
        ResultSet res = jdbcConfig.executeQuery(querySql, queryParams);
        try {
            while (res.next()){
                //用列索引接收结果
//                int id = res.getInt(1);
//                String username = res.getString(2);
                
                //用列属性名接收结果
                int id = res.getInt("id");
                String username = res.getString("username");
                System.out.println(id + "\t" + username);
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
}

Mybatis持久层框架

Mybatis是一种支持动态SQL语句的持久层框架,封装了JDBC操作,可以将SQL语句写在.xml文件中,使SQL语句和Java代码实现分离,方便系统维护和开发。

(1)Mybatis持久层框架的好处:

①封装了JDBC操作,简化代码,实现了SQL语句和Java代码的分离;

②自身支持连接池,可以提高销量,无需反复地创建连接、关闭连接;

ps:连接池相关知识点可见什么是连接池?为什么需要连接池呢?连接池的组成原理又是什么呢?_路遥叶子的博客-CSDN博客

③Mybatis会将查询结果转换为Java对象,不需要手动处理(JDBC返回的是ResultSet对象)。

(2)Mybatis代码实现

①此处我们使用Maven在pom文件中引入Mybatis依赖,当然也可以通过手动下载Mybatis复制到项目lib目录下实现Myabtis导入。

Maven的使用:Maven项目管理工具 简单使用_Sayatnoon的博客-CSDN博客

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.11</version>
        </dependency>

②主配置文件mybatis-config.xml

部分标签知识点:

transcationManager标签:事务管理,可选类型有JDBC、MANAGED,选择JDBC是指将事务交给JDBC管理,使用JDBC的提交、回滚,选择MANAGED是自己管理事务

dataSource标签:选择数据源(也就是连接池),可选JNDI、POOLED、UNPOOLED,一般选择POOLED,即使用连接池。

property标签:是连接数据库的一系列信息,也可以写在专门的数据库配置信息文件中,再在这里引用

mappers标签:管理每一个数据表对应的SQL映射配置文件;每一次新增时一定要在这个标签下添加,否则无法实现SQL语句。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- 配置环境 -->
    <environments default="mysql">
        <environment id="mysql">
            <!-- 事务管理可选类型:JDBC(使用JDBC的提交、回滚方式)、MANAGED(自己管理事务)-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源,可选类型:JNDI/POOLED(使用连接池)/UNPOOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///myschool"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>

    <!-- 管理SQL映射配置文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>

</configuration>

③实体类

public class User {

  private long id;
  private String username;
  private String password;


  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }


  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }


  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

}

④SQL映射配置文件***Mapper.xml及Dao层(数据访问层)的接口***Mapper

注意:这两个文件需要一一对应绑定,.xml文件的namespace需要写dao层接口信息;两个文件的方法名要一致,且输入的参数名要一致(或者用序号代替);

关于两个文件的参数名绑定,可见:Mybatis中的Mapper配置文件_mybatismapper配置文件_Sayatnoon的博客-CSDN博客

Mapper配置文件的参数使用的#、$的区别在于:

使用格式上,#{**}、'${**}';#{**}是将输入作为字符串,会自动加上双引号,使用了JDBC的preparedStatement预编译命令,所以比‘${**}'更安全,可以防止SQL注入

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 绑定到dao层的接口 -->
<mapper namespace="org.example.dao.UserMapper">

    <!-- 绑定方法,必须同名 -->
    <select id="getUserById" resultType="org.example.entity.User" parameterType="Integer">
        SELECT * FROM users WHERE id = #{id}
    </select>

</mapper>
import org.example.entity.User;

public interface UserMapper {

    User getUserById(int id);

}

⑤MybatisUtil类,用于获取/关闭SqlSession(会话)

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;


public class MybatisUtil {
    static SqlSessionFactory sqlSessionFactory = null;

    //获取sqlSession
    public static SqlSession getSession(){
        SqlSession session = null;
        InputStream ins = null;

        try{
            //加载主配置文件
            ins = Resources.getResourceAsStream("mybatis-config.xml");
            //获取Mybatis的SqlSession工厂
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(ins);
            //通过工厂获取session
            session = sqlSessionFactory.openSession(true);//true表示自动提交事务
            //调用session的查询集合方法
            return session;
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                ins.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return null;
    }

    //关闭SqlSession
    public static void closeSession(SqlSession session){
        if (session != null){
            session.close();
        }
    }
}

⑥使用方法连接数据库并执行SQL语句

btw,这里用了junit,在pom.xml中也要引入依赖

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
import org.apache.ibatis.session.SqlSession;
import org.example.dao.UserMapper;
import org.example.entity.User;
import org.example.util.MybatisUtil;
import org.junit.Test;

public class testMybatis {
    @Test
    public void test() {
        SqlSession session = MybatisUtil.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.getUserById(1);
        if (user != null) {
            System.out.println(user.getUsername());
        }
    }
}

(3)Mybatis的一些知识点

①SqlSessionFactory

SqlSessionFactory是Mybatis框架的重要接口,用于生产SqlSession,即会话。而SqlSessionFactory对象的实例则是由SqlSessionFactoryBuilder读取主配置文件进行创建的。

SqlSessionFactory是线程安全的,一旦被创建,在应用执行期间都应该存在,不建议重复多次创建,建议使用单例模式。

②SqlSession

SqlSession是Mybatis最核心的组件,可以看作是数据库操作的一次会话。

SqlSession对象的实例是由SqlSessionFactory的openSession方法创建的,并且可以在该方法选择是否自动提交事务。

SqlSession封装了对数据库的操作,可以直接调用SqlSession的对象实例的insert、selectOne、selectList等各类方法,执行mapper中定义的方法。可以通过它的getMapper方法获得Mapper对象(原理是动态代理),然后直接执行Mapper中的方法。

SqlSession不是线程安全的,需要保证每个线程都有自己的SqlSession实例,否则容易出现数据混乱的情况。

③Mybatis缓存

一级缓存:指在同一个SqlSession中,如果执行了相同的SQL语句,则Mybatis会将查询结果缓存在内存中,下次查询可以直接从缓存中获取。

二级缓存:指在不同SqlSession中,如果执行了相同的SQL语句,Mybatis会将查询结果缓存在内存中,下次直接从缓存中获取。也就是所有的SqlSession共享同一个Mapper的二级缓存;如果数据被更新,则二级缓存会清空。

在Mybatis中,缓存可以通过配置文件中的标签进行配置。也支持通过插件自定义缓存实现。但缓存也可能导致数据不一致,需要合理配置。

官方不提供三级缓存,不过可以通过自定义插件实现三级缓存。

Mybatis-Plus框架

顾名思义,Mybatis-Plus是Mybatis的plus版。Mybatis-Plus内置Mapper,无需编写mapper.xml文件就能实现大部分的增删查改操作。

Mybatis-Plus代码实现(非Spring环境):

①在Maven中引入Mybatis-Plus依赖,方便后续测试,也顺了个遍引入Junit

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>TestMybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.4.1</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

②主配置文件mybatis-config.xml

这里与Mybatis使用的不同点在于:Mybatis-Plus大多数时候无需编写Mapper.xml,所以Mappers标签下不使用<mapper resource>,可以用<mapper class>指定Mapper接口,或者<package name>指定存放mapper的包。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- 配置环境 -->
    <environments default="mysql">
        <environment id="mysql">
            <!-- 事务管理可选类型:JDBC(使用JDBC的提交、回滚方式)、MANAGED(自己管理事务)-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源,可选类型:JNDI/POOLED(使用连接池)/UNPOOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///myschool"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>

    <!-- 管理SQL映射配置文件 -->
    <mappers>
<!--       <package name="org.example.mapper"/>-->
        <mapper class="org.example.mapper.UserMapper"/>
    </mappers>

</configuration>

③实体类

注意注意,Mybatis-Plus下实体类名称一定要与数据表名称一致(大小写差异不影响),否则匹配时会找不到表

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;

public class Users extends Model<Users> {

  @TableId(type = IdType.AUTO)
  private Integer id;
  private String username;
  private String password;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }


  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

}

④数据访问层Mapper接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.example.entity.Users;

public interface UserMapper extends BaseMapper<Users> {
}

⑤MybatisPlusUtil

注意:这里的会话工厂建造类不是SqlSessionFactoryBuild,而是MybatisSqlSessionFactoryBuild

public class MybatisPlusUtil {
    
    static SqlSessionFactory sqlSessionFactory = null;
    //获取sqlSession
    public static SqlSession getSession(){
        SqlSession session = null;
        InputStream ins = null;

        try{
            //加载主配置文件
            ins = Resources.getResourceAsStream("mybatis-config.xml");
            //获取Mybatis的SqlSession工厂
            sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(ins);
            //通过工厂获取session
            session = sqlSessionFactory.openSession(true);//true表示自动提交事务
            //调用session的查询集合方法
            return session;
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                ins.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return null;
    }

    //关闭SqlSession
    public static void closeSession(SqlSession session){
        if (session != null){
            session.close();
        }
    }
}

⑥Test

package org.example;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.session.SqlSession;
import org.example.mapper.UserMapper;
import org.example.entity.Users;
import org.example.util.MybatisPlusUtil;
import org.junit.Test;

import java.util.List;


public class testMybatisPlus {
    @Test
    public void test(){
        SqlSession session = MybatisPlusUtil.getSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);

        Users user = new Users();
        user.setUsername("Mybatis-Plus");
        user.setPassword("Hello");

        if (userMapper.insert(user) > 0){
            System.out.println("success");
        }

//        QueryWrapper queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("username", "Mybatis-Plus");
//        List<Users> users = userMapper.selectList(queryWrapper);
//        for (Users user1: users){
//            System.out.println(user1.getId() + "\t" + user1.getUsername());
//        }
    }
}

在许多场景下,比如SpringBoot项目中,Mybatis-Plus框架的使用贯穿实体类、数据访问层、服务层,在每一层都提供了相应的接口给项目中的类继承;在用户端大多时候是调用服务层的方法实现功能。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!关于Spring Boot、MyBatis Plus和Sharding-JDBC的集成问题,我可以提供一些基本指导。首先,确保您已经在Spring Boot项目中成功集成了MyBatis Plus和Sharding-JDBC的依赖。 在pom.xml文件中添加MyBatis Plus和Sharding-JDBC的依赖: ```xml <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Sharding-JDBC --> <dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-jdbc.version}</version> </dependency> ``` 接下来,您需要配置Sharding-JDBC。在application.yml(或application.properties)文件中添加相应的配置信息。以下是一个示例: ```yaml spring: shardingsphere: datasource: names: ds0, ds1 # 数据源名称 ds0: url: jdbc:mysql://localhost:3306/db0?serverTimezone=UTC username: root password: root driver-class-name: com.mysql.jdbc.Driver ds1: url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC username: root password: root driver-class-name: com.mysql.jdbc.Driver sharding: tables: user: actualDataNodes: ds$->{0..1}.user tableStrategy: inline: shardingColumn: id algorithmExpression: user$->{id % 2} keyGenerator: type: SNOWFLAKE ``` 这是一个简单的配置示例,其中包含了两个数据源(ds0和ds1),以及一个名为user的分片表的配置。您可以根据您的实际需求进行调整。 最后,您可以在MyBatis Plus的Mapper接口中使用Sharding-JDBC提供的分片功能。例如: ```java @Mapper public interface UserMapper extends BaseMapper<User> { // 自定义SQL查询方法 List<User> selectUserById(@Param("id") Long id); } ``` 这只是一个简单的示例,您可以根据自己的业务需求进行扩展和定制。 希望这些信息对您有所帮助!如果您还有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值