MyBatis——基础用法

MyBatis——基础用法

框架一览:
(1)解决技术整合问题的框架:Spring框架是由于软件开发的复杂性而创建的。
(2)解决WEB层问题的MVC框架:SpringMVC。
(3)解决数据的持久化问题的框架:MyBatis是一个基于Java的持久层框架,而Hibernate是一个封装程度更高的持久层框架。

持久化的方式:数据库(JDBC), IO文件(浪费资源)

为什么要进行持久化:因为内存有断电即失的特性,而有些对象我们不希望丢失,这时就需要进行持久化处理。

Mybatis官网文档:https://mybatis.org/mybatis-3/
Mybatis——github源码:https://github.com/mybatis/mybatis-3

Maven依赖:maven仓库

mybatis:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>

单元测试junit

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

mysql

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

注:
Mybatis原名iBatis

Mybatis使用步骤;

  • (1) 编写实体类
  • (2) 编写核心配置类
  • (3) 编写接口
  • (4) 编写接口对应的Mapper.xml
  • (5) 测试
1 mybatis框架(持久层框架)

mybatis是一个基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理 加载驱动、创建连接、创建statement 等繁杂的过程,从而大大简化了JDBC的一系列操作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通 Java 对象)为数据库中的记录。 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了JDBC API底层访问细节,使我们不用与JDBC API打交道,就可以完成对数据库的持久化操作。

ORM(Object Relational Mapping):对象关系映射,数据表和面向对象语言中的实体类对象互相映射转换。

注:
POJO:简单理解为不包含业务逻辑的单纯用来存储数据的 java类,POJO没有实现任何接口,没有继承任何父类.

优点:

  • 简单易学,比较灵活。
  • sql和代码分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql.

MyBatis是对JDBC的进一步封装,JDBC编程的大体流程如下:

1). 创建数据库连接Connection
2). 创建操作命令Statement
3). 使用操作命令来执行SQL
4). 处理结果集ResultSet
5). 释放资源
在这里插入图片描述
MyBatis的大体流程:
在这里插入图片描述
Mybatis工作流程:
在这里插入图片描述
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

2 Mybatis生命周期及作用域:

理解不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
在这里插入图片描述
SqlSessionFactoryBuilder

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 局部变量。

SqlSessionFactory

  • 相当于是数据库连接池。
  • 一旦被创建就应该在应用的运行期间一直存在,不需要创建多个实例
  • 最佳作用域是应用作用域。
  • 最简单的就是使用 单例模式 或者静态单例模式。

SqlSession

  • 连接到连接池的一个请求。
  • 用完之后需要关闭close,否则资源被占用
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
3 Mybatis的应用实例一
1)前置准备

创建数据库以及表,创建maven项目,并配置数据库连接。
在pom.xml中导入mybatis、mysql-connector-java、junit(单元测试)依赖包。

CREATE DATABASE `mybatis`;

USE `mybatis`;

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `user`(`id`,`name`,`pwd`) values (1,'张飞','123456'),(2,'张三','abcdef'),(3,'胡汉三','987654');

在这里插入图片描述

2)编写mybatis配置文件mybatis.xml:
<?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="development">

        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;
                useUnicode=true&amp;chracterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="mysql"/>
            </dataSource>
        </environment>

    </environments>

    <mappers>
        <mapper resource="com/glp/dao/UserMapper.xml"/>
    </mappers>

</configuration>

该mybatis配置类用于配置数据库连接,以及注册Mapper.xml(类似接口实现类)。

注意:
resource资源路径为com/glp/dao/UserMapper.xml而不能写成com.glp.dao.UserMapper.xml

3)编写mybatis工具类utils
package com.glp.utils;

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 MyUtils {
    private static SqlSessionFactory sqlSessionFactory;

    //拿到工厂类
    static{
        try {
            String resource = "mybatis.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //拿到工厂类后,获取SqlSession实例
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

通过SqlSessionFactoryBuilder().build()拿到一个工厂类SqlSessionFactory,通过这个工厂类获取SqlSession实例,用来执行后续方法的调用。

4)根据数据库表创建实体类
package com.glp.POJO;

public class User {
    private int id;
    private String name;
    private String pwd;
	//省略了 getter,setter
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

5)编写接口类
package com.glp.dao;
import com.glp.POJO.User;
import java.util.List;

public interface UserMapper {
    List<User> getUserList();
}
6) 编写UserMapper.xml
<?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">

<!--绑定一个Mapper接口-->
    <mapper namespace="com.glp.dao.UserMapper" >
        <!--id对应接口方法-->
        <select id="getUserList" resultType="com.glp.POJO.User">
           select * from mybatis.user;
        </select>
    </mapper>

Dao层查询数据,本应该定义一个实现类来完成,现在我们只需要定义一个接口,在xml中直接写sql语句就可以了,此时这个xml中的Usermapper.xml就相当于是UserMapper接口的实现类。

注:
命名空间namespace中指定的包名,要和接口类一致。

7) 编写junit包测试
package com.glp.dao;

import com.glp.POJO.User;
import com.glp.utils.MyUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {

    @Test
    public void test(){

        //获得SqlSession对象
        SqlSession sqlSession = MyUtils.getSqlSession();
        try{
            //执行SQL
            //方式一 推荐
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList  = userMapper .getUserList();

            //方式二 了解
//            List<User> userList = sqlSession.selectList("com.glp.dao.UserMapper.getUserList");

            for (User user : userList) {
                System.out.println(user);
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭sqlSession
            sqlSession.close();
        }
    }
}

注意:
在build中配置resource,来防止资源导出失败的问题,主要是默认资源目录需要放到resource目录下才可以被正常导出,而如果我们放到其他目录,此时就需要在pom.xml中手动配置资源过滤。


    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>

            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
4 Mybatis的应用实例二——增删改查CRUD

在实例一的基础上,进行操作。

1)在接口UserMapper中增加方法
package com.glp.dao;
import com.glp.POJO.User;
import java.util.List;

public interface UserMapper {
    List<User> getUserList();
	//查找
    User getUserById(int id);
	//增加
    int insertUser(User user);
	//更新
    int updateUser(User user);
	//删除
    int deleteUser(int id);
}
2)在UserMapper.xml中添加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层查询数据,本应该定义一个实现类来完成,现在我们只需要定义一个接口,在xml中直接写sql就可以了-->
<!--此时这个xml中的mapper就相当于是UserDao的实现类-->
<!--绑定一个Mapper接口-->
    <mapper namespace="com.glp.dao.UserMapper" >
<!--id对应接口方法-->
<select id="getUserList" resultType="com.glp.POJO.User">
           select * from mybatis.user;
        </select>

    <select id="getUserById" parameterType="int" resultType="com.glp.POJO.User">
           select * from mybatis.user where id = #{id};
        </select>

    <!--对象中的属性可以直接取出来-->
    <insert id="insertUser" parameterType="com.glp.POJO.User" >
        insert into mybatis.user(id,name,pwd)  values (#{id},#{name},#{pwd});
    </insert>

    <update id="updateUser" parameterType="com.glp.POJO.User">
        update mybatis.user set name =#{name},pwd=#{pwd} where id=#{id};
    </update>

    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id = #{id};
    </delete>
</mapper>

注:

  • parameterType:指定参数类型
  • resultType:返回类型
  • #{}:相当于一个占位符
  • 参数为对象时,可以直接映射为sql中的参数:
    <insert id="insertUser" parameterType="com.glp.POJO.User" >
        insert into mybatis.user(id,name,pwd)  values (#{id},#{name},#{pwd});
    </insert>
3)编写测试类
package com.glp.dao;

import com.glp.POJO.User;
import com.glp.utils.MyUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {

    @Test
    public void test(){

        //获得SqlSession对象
        SqlSession sqlSession = MyUtils.getSqlSession();

        try{
            //执行SQL
            //方式一 推荐
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList  = userMapper .getUserList();

            for (User user : userList) {
                System.out.println(user);
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭sqlSession
            sqlSession.close();
        }

    }

//查找
    @Test
    public void getUserById(){
        //获得SqlSession对象
        SqlSession sqlSession = MyUtils.getSqlSession();

        try{
            //执行SQL
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user  = userMapper .getUserById(1);
            System.out.println(user);

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭sqlSession
            sqlSession.close();
        }
    }

    //增删改需要提交事务
    @Test
    public void insertUser(){

        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int ret = mapper.insertUser(new User(5, "李朗", "131"));
        if(ret>0){
            System.out.println("插入成功");
        }
        //增删改必须要提交事务
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int ret = mapper.updateUser(new User(2,"关羽","53553"));
        if(ret>0){
            System.out.println("更新耿工");
        }
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void deleteUser(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int ret = mapper.deleteUser(3);
        if(ret>0){
            System.out.println("删除成功");
        }
        sqlSession.commit();
        sqlSession.close();
    }
}

注意:

  • 增删改,都需要用sqlSession.commit();提交事务,否则增删改不成功。
5 应用实例三——在接口方法中使用map传递参数
1)定义接口:

Map<String,Object>:

  	int insertUser2(Map<String,Object> map);
    User getUserById2(Map<String,Object> map);
2)编写UserMapper.xml文件
 <!--使用map来填充参数-->
    <insert id="insertUser2" parameterType="map" >
        insert into mybatis.user(id,name,pwd)  values (#{id},#{name},#{pwd});
    </insert>

    <select id="getUserById2" parameterType="map" resultType="com.glp.POJO.User">
        select * from mybatis.user where id=#{id};
    </select>

此时的参数类型为map.

3)测试:
    @Test
    public void insertInto2(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("id",7);
        map.put("name","刘备");
        map.put("pwd","434");
        int ret = mapper.insertUser2(map);
        sqlSession.commit();;
        sqlSession.close();
    }

    @Test
    public void selectById2(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("id",1);
        User user = mapper.getUserById2(map);
        System.out.println(user);
    }

将UserMapper.xml中的参数名,当作key,放到map中即可。

注:
如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可

6 模糊查询需要避免sql注入
1)定义接口
List<User> selectLike(String value);
2)UserMapper.xml
    <select id="selectLike" resultType="com.glp.POJO.User">
        select * from mybatis.user where name like "%"#{str}"%";
    </select>

上面这种是不推荐的,因为可能会引起sql注入(存在字符串拼接问题)。

推荐使用下面这种,用concat进行字符串拼接:

    <select id="selectLike" resultType="com.glp.POJO.User">
        select * from mybatis.user where name like concat('%',#{str},'%');
    </select>

注:
能够解决sql注入又能在配置文件中写%,可以借助mysql中的concat函数。

3)测试
  @Test
    public void selectLike(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> list = mapper.selectLike("张");
        for (User user : list) {
            System.out.println(user);
        }
    }

注意:

  • #{} 能够防止SQL注入 : 在模糊查询中,要这样写like concat(‘%'#{name}'%’) 如果不这样写,仅仅是加上单引号‘%#{name}%’,那么就当成是一个字符串。

  • #{ }是预编译处理,MyBatis在处理#{ }时,它会将sql中的#{ }替换为,然后调用PreparedStatement的set方法来赋值,传入字符串后,会在值两边加上单引号。

  • 预编译的机制: 预编译是提前对SQL编译之前进行预编译,而其后注入的参数将不会再进行编译。 因为SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。所以预编译机制则可以很好的防止SQL注入。

  • ${}无法防止SQL注入:like ‘%${name}%’ ,通过$的方式拼接的sql语句,不再是以占位符的形式生成sql,而是以拼接字符串的方式生成sql,这样做带来的问题是:会引发sql注入的问题。 ${}底层是由Statement实现,只是简单的替换,传递的参数会被当成sql语句中的一部分。

  • $ { }是字符串替换, MyBatis在处理$ { }时,它会将sql中的${ }替换为变量的值,传入的数据不会加两边加上单引号。

7 分页

方式一:推荐

select * from user limit startIndex,pageSie;
select *from user limit 3;	#[0,n]

(1) 接口

public interface UserMapper {
    //分页
    List<User> getUserByLimit(HashMap<String,Object> map);
}

(2)UserMapper.xml

  <select id="getUserByLimit" parameterType="map" resultMap="user">
        select * from mybatis.user limit #{startIndex},#{pageSize};
    </select>

(3) 测试

 @Test
    public void getUserByLimit(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        HashMap<String,Object> map = new HashMap<String,Object>();
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> list = mapper.getUserByLimit(map);

        for(User user:list){
            System.out.println(user);
        }

        sqlSession.close();
    }

方式二:RowBounds分页(了解即可)
(1)mapper接口

//选择全部用户RowBounds实现分页
List<User> getUserByRowBounds();

(2)mapper.xml

<select id="getUserByRowBounds" resultType="user">
select * from user
</select>

(3)测试

@Test
public void testUserByRowBounds() {
   SqlSession session = MybatisUtils.getSession();

   int currentPage = 2;  //第几页
   int pageSize = 2;  //每页显示几个
   RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);

   //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
   List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);

   for (User user: users){
       System.out.println(user);
  }
   session.close();
}

(3) 方式三:分页插件pagehelper
分页插件pagehelper

8 使用注解

注解开发的本质:反射
底层:动态代理

(1) 在接口上加入注解

    //注解
    @Select("select * from user")
    List<User> getUsers();

(2)需要在核心配置类中绑定接口

 <mappers>
        <mapper class="com.glp.dao.UserMapper"/>
 </mappers>

要想注解和xml配置一起使用要满足下面的条件:

  • 接口和它的Mapper配置文件必须同名。
  • 接口和它的Mapper配置文件必须在同一个包下。

(3) 测试

 @Test
    public void getUsers(){
            SqlSession sqlSession = MyUtils.getSqlSession();
            //执行SQL
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> list  = userMapper .getUsers();

            for(User user :list){
                System.out.println(user);
            }
            //关闭sqlSession
            sqlSession.close();
    }

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

(4) 设置自动提交commit
openSession(true);中传入true; 注意尽量不要设置自动提交。

public class MyUtils {
    private static SqlSessionFactory sqlSessionFactory;
    //拿到工厂类
    static{
        try {
            String resource = "mybatis.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //拿到工厂类后,获取SqlSession实例
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }
}

(5) 注解实现CURD

1)接口

    @Select("select * from user where id = #{id}")
    User getUserById2(@Param("id") int id);

    @Insert("insert into user values(#{id},#{name},#{password})")
    int addUser(User user);

    @Update("update user set name=#{name}, pwd=#{password} where id = #{id}")
    int updateUser(User user);

    @Delete("delete from user where id=#{id}")
    int deleteUser(@Param("id") int id);

注:

  • 基本类型的数据或者String类型都要加上Param注解

  • 方法包含多个参数时,每个参数必须加上Param注解,

  • 引用类型不用写@Param

  • @Param(“id”)中的属性名id就是我们在SQL中引用的属性名
    在这里插入图片描述

2 ) 测试

    @Test
    public void getUsers2(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user  = userMapper .getUserById2(1);
        System.out.println(user);
        sqlSession.close();
    }
    
    @Test
    public void insertUser(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(8,"黄忠","444"));
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(3,"吕布","444"));
        sqlSession.close();
    }


    @Test
    public void deleteUser(){
        SqlSession sqlSession = MyUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(3);
        sqlSession.close();
    }
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值