MyBatis随笔

【个人随笔仅供参考,如有出错欢迎批评】

Mybatis

Mybatis中文文档
Mybatis学习参考视频

一.什么是Mybatis

持久层框架

什么是数据持久化?即数据在瞬时状态(内存断电即失)和持久状态(数据库、io文件)之间转换

mybatis特点:

1.简单易学:导包+配置sql映射文件(Mapper.xml)即可

2.灵活解耦:sql写在XxMapper.xml里统一管理,sql与java代码解耦

3.支持动态sql

什么是动态sql?动态sql就是根据不同的条件生成不同的sql语句,包括元素:if、choose(when,otherwise)、trim(where,set)、SQL片段、foreach

二.Mybatis快速入门

思路:搭建环境->导包>编码->测试

1.搭建环境

创建maven项目,创建简单数据库表;

CREATE DATABASE mybatis;
USE mybatis;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`(
	id VARCHAR(20) NOT NULL PRIMARY KEY,
	name VARCHAR(20),
	pwd VARCHAR(20)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO `user`(id,name,pwd) VALUES (1,'张三','123456')

2.导包

	<dependencies>        
		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
	</dependencies>

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

3.编码

  • (1)目录结构

在这里插入图片描述

  • (2)编写mybatis核心配置文件mybatis-config.xml(配置数据源)

mybatis-config.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核心配置文件-->
<configuration>
    <settings>
        <!--开启驼峰命名自动映射:即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--environments(环境配置):Mybatis可以配置多种环境,通过default指定一个。-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
                <!--
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
				-->
            </dataSource>
        </environment>
    </environments>
    <!--告诉MyBatis到哪里去找映射文件-->
    <mappers>
        <mapper resource="com/cc/mapper/UserMapper.xml"/>
    </mappers>
    <!--    <mappers>
        <mapper class="com.cc.mapper.UserMapper"/>
    </mappers>-->
</configuration>
  • (3)编写db.properties(也可以写死在mybatis-config.xml中)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=root
  • (4)编写MybatisUtils工具类
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 MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取SqlSession连接
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession(true);//增删改每次都需要提交事务才生效,这里设置自动提交
    }
}

  • (5)编写 UserMapper接口
public interface UserMapper {
    List<User>getUsers();
    int addUser(User user);
    int deleteUserById(String id);
    int updateUserById(User user);
}
  • (6)编写UserMapper.xml映射文件(相当于UserMapper接口实现类UserMapperImpl,由于Mybatis通过XxMapper统一管理sql,所以用xml文件编写啦),记得要在mybatis-config.xml里注册下该映射文件

    这里先回顾下jdbc编程

public class UserMapperImpl implements UserMapper{
    @Override
    public List<User> getUsers() {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        List<User> users=new ArrayList<User>();
        try {
            //加载数据库驱动块
            Class.forName("com.mysql.jdbc.Driver");
            //通过驱动管理类获取数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "root");
            //定义sql语句
            String sql="SELECT * FROM `user`";
            //获取预处理preparedStatement
            preparedStatement=connection.prepareStatement(sql);
            //设置sql参数,index从1开始,例String sql="SELECT * FROM `user` WHERE name=?";
            //preparedStatement.setString(1,"张三");
            resultSet= preparedStatement.executeQuery();
            while(resultSet.next()){
                User user = new User();
                user.setId(resultSet.getString("id"));
                user.setName(resultSet.getString("name"));
                user.setPwd(resultSet.getString("pwd"));
                users.add(user);
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return users;
    }
}

问题分析

1.数据库连接创建、释放频繁造成系统资源浪费(连接池可解决)

2.sql语句、结果集解析存在硬编码,不易维护

引入Mybatis使用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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.UserMapper">
    <!--select查询语句-->
    <select id="getUsers" resultType="com.cc.entity.User">
        SELECT * FROM `user`
    </select>
    <!--insert-->
    <insert id="addUser" parameterType="com.cc.entity.User">
        insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
    </insert>
    <!--delete-->
    <delete id="deleteUserById" parameterType="String">
        delete from user where id = #{id}
    </delete>
    <!--update-->
    <update id="updateUserById" parameterType="com.cc.entity.User">
        update user set name=#{name},pwd=#{pwd} where id = #{id}
    </update>
</mapper>
  • (7)编写测试类
    @Test
    public void testUserMapper(){
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查
/*        List<User> users = mapper.getUsers();
        for (User user :
                users) {
            System.out.println(user);
        }*/
        User user = new User();
        user.setId("3");
        user.setName("王五");
        user.setPwd("123456");
        //增
        mapper.addUser(user);
        //删
        mapper.deleteUserById("3");
        user.setName("王五五");
        //改
        mapper.updateUserById(user);
        sqlSession.commit(); //提交事务,重点!不写的话不会提交到数据库
        sqlSession.close();
    }
  • (8)解决属性名和数据库名不一致的问题

方案一:在sql语句中为列名指定别名

方案二:使用结果集映射->ResultMap

  • (9)接口使用万能Map传参给mapper映射文件(参数过多时可以考虑该方案)

    • 在接口方法中,形参直接是Map<String,Object>map

      User selectUserByNP(Map<String,Object> map);
      
    • Map的key为sql语句的参数名即可

      @Test
      public void testUserMapper(){
          SqlSession sqlSession = MybatisUtils.getSession();
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          Map<String, Object> map = new HashMap<String, Object>();
          map.put("name","张三");
          map.put("pwd","123456");
          User user = mapper.selectUserByNP(map);
      }
      
    • 编写sql语句时,参数类型为map

      <select id="selectUserByNP" parameterType="map" resultType="com.cc.entity.User">
      select * from user where name = #{name} and pwd = #{pwd}
      </select>
      

三.日志

思考:我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率?

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:SLF4J、Apache Commons Logging、Log4j2、Log4j、JDK logging

下述通过log4j实现sql日志信息打印:

  • (1)导包
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
  • (2)通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。
<configuration>
  <settings>
    <setting name="logImpl" value="LOG4J"/>
  </settings>
</configuration>
  • (3)编写配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/sql.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

四.分页

为什么需要分页?

1.对于用户来说:一页展示所有数据眼花缭乱

2.对于mysql服务器来说:一次查询所有数据,数据量大则非常耗时。所以,后台查询部分数据而不是全部数据。

步骤

  • (1)Limit实现分页sql语句
SELECT * FROM table LIMIT stratIndex,pageSize
  • (2)编写UserMapper接口
List<User> getUsersByPage(Map<String,Object> map)
  • (3)编写UserMapper.xml映射文件
<!--分页-->
<select id="getUsersByPage" parameterType="map" resultType="com.cc.entity.User">
    SELECT * FROM `user` LIMIT #{stratIndex},#{pageSize}
</select>
  • (4)测试类
    @Test
    public void testGetUserByPage(){
        Map<String, Object> map = new HashMap<>();
        int currentPage=1;
        int pageSize=2;
        map.put("stratIndex",(currentPage-1)*pageSize);
        map.put("pageSize",2);
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUsersByPage(map);
        for (User user:
             userList) {
            System.out.println(user);
        }
    }

*起始位置=(当前页数-1)页面大小

五.多表查询

在这里插入图片描述

步骤

环境搭建

CREATE TABLE clazz(
	id VARCHAR(10) PRIMARY KEY,
	name VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE student(
	id VARCHAR(10) PRIMARY KEY,
	name VARCHAR(30) DEFAULT NULL,
	cid VARCHAR(10) DEFAULT NULL,
	FOREIGN KEY (cid)REFERENCES clazz(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO clazz VALUES('1','软件1班'),('2','软件2班');
INSERT INTO student VALUES('1','张三','1'),('2','李四','1'),('3','王五','2');

1.多对一

需求:获取所有学生信息及其对应的班级信息

@Data
public class Clazz {
    private String id;
    private String name;
}
@Data
public class Student {
    private String id;
    private String name;
    private Clazz clazz;
}
public interface StudentMapper {
    List<Student>getStudents();
    List<Student>getStudents2();
}

(1)方法一:按查询嵌套处理

<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.StudentMapper">
    <!--
   需求:获取所有学生信息及对应班级的信息
   思路:
       1. 获取所有学生的信息
       2. 根据获取的学生信息的cid->获取班级的信息
       3. 思考问题,这样学生的结果集中应该包含班级,该如何处理呢,数据库中我们一般使用关联查询
           (1). 做一个结果集映射:StudentClazz
           (2). StudentClazz结果集的类型为 Student
           (3). association – 一个复杂类型的关联;使用它来处理关联查询
   -->
    <resultMap id="StudentClazz" type="com.cc.entity.Student">
        <!--association关联属性,property属性名,javaType属性类型,column在多方的外键列名,内嵌select-->
        <association property="clazz" column="cid" javaType="com.cc.entity.Clazz" select="getClazzByid"/>
    </resultMap>

    <select id="getStudents" resultMap="StudentClazz">
        select * from student
    </select>

    <select id="getClazzByid" parameterType="String" resultType="com.cc.entity.Clazz">
        select * from clazz where id=#{id}
    </select>
</mapper>

(2)方法二:按结果嵌套处理

<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.StudentMapper">
    <!--   需求:获取所有学生信息及对应班级的信息-->
    <!--=========================按查询结果嵌套处理===========================-->
    <!--思路:直接查询出结果,进行结果集的映射-->
    <select id="getStudents2" resultMap="StudentClazz2">
        select student.id sid,student.name sname,cid,clazz.name cname from student,clazz where student.cid=clazz.id
    </select>

    <resultMap id="StudentClazz2" type="com.cc.entity.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="clazz" javaType="com.cc.entity.Clazz">
            <result property="id" column="cid"/>
            <result property="name" column="cname"/>
        </association>
    </resultMap>
</mapper>

2.一对多

需求:获取指定班级信息及其学生信息

@Data
public class Student2 {
    private String id;
    private String name;
    private String cid;
}
@Data
public class Clazz2 {
    private String id;
    private String name;
    private List<Student2> student2s;

}
public interface ClazzMapper {
    Clazz2 getClazz2ById(String id);
}

(1)方法一:按查询嵌套处理

<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.ClazzMapper">
    <!--按查询嵌套处理-->
    <select id="getClazz2ById" parameterType="String" resultMap="ClazzStudent">
        select * from clazz where id=#{id}
    </select>
    <select id="getStudentByCid" parameterType="String" resultType="com.cc.entity.Student2">
        select * from student where student.cid=#{cid}
    </select>
    <resultMap id="ClazzStudent" type="com.cc.entity.Clazz2">
        <!--column是一方主键列名-->
        <collection property="student2s" javaType="ArrayList" ofType="com.cc.entity.Student2"
                    column="id" select="getStudentByCid"/>
    </resultMap>
</mapper>

(2)方法二:按结果嵌套处理

<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.ClazzMapper">
    <!--方法2.按结果嵌套处理-->
    <select id="getClazz2ById2" resultMap="ClazzStudent2" parameterType="String">
        select cid,clazz.name cname,student.id sid,student.name sname
        from clazz,student where clazz.id=student.cid and clazz.id=#{id}
    </select>
    
    <resultMap id="ClazzStudent2" type="com.cc.entity.Clazz2">
        <result property="id" column="cid"/>
        <result property="name" column="cname"/>
        <collection property="student2s" ofType="com.cc.entity.Student2">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="cid" column="cid"/>
        </collection>
    </resultMap>
</mapper>

3.总结

  • 关联-association

  • 集合-collection

    • 所以association是用于一对一和多对一,而collection是用于一对多的关系
  • JavaType和ofType都是用来指定对象类型的

    • JavaType是用来指定pojo中属性的类型

    • ofType指定的是映射到list集合属性中pojo的类型。

六.动态SQL

什么是动态SQL?动态SQL就是根据不同的条件生成不同的SQL语句,本质还是拼接SQL语句

关于动态SQL,MyBatis需了解如下元素:

  • if

  • choose(when,otherwise)

  • trim(where,set)

  • foreach

直接看文档:动态 SQL_MyBatis中文网

七.注解开发

利用注解开发就不需要Xxmapper.xml映射文件啦(简单用注解,负责用映射文件)。 我们在XxMapper接口添加注解@Select()、@Insert()、@Delete()、@Update();此外还有@Param()

步骤:

1.在mybatis-config.xml核心配置文件编写:

    <!--告诉MyBatis到哪里去找映射文件-->
    <mappers>
        <mapper resource="com/cc/mapper/UserMapper.xml"/>
    </mappers>
<!--    <mappers>
        <mapper class="com.cc.mapper.UserMapper"/>
    </mappers>-->

2.在UserMapper编写接口

public interface UserMapper {
    //注解开发
    @Select("SELECT * FROM `user`")
    List<User>getUsersByAnnotate();
    @Insert("INSERT INTO `user`(id,name,pwd) VALUES(#{id},#{name},#{pwd})")
    int addUserByAnnotate(User user);
    @Insert("DELETE FROM `user` WHERE id=#{id}")
    int deleteUserByIdByAnnotate(@Param("id") String id);
}

3.测试

    @Test
    public void testAnnotate(){
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查
/*        List<User> userList = mapper.getUsersByAnnotate();
        for (User user:
                userList) {
            System.out.println(user);
        }*/
        
        //增
        User user = new User("4","何散","123456");
/*        mapper.addUserByAnnotate(user);*/
        //删
        mapper.deleteUserByIdByAnnotate("4");
    }

关于@Param

@Param注解用于给方法的参数命名,为sql语句中的参数赋值服务

  • 在方法参数一个(非JavaBean)的情况下,可以不使用@Param
  • 在方法参数多个的情况下,建议使用@Param给参数命名
  • 在方法参数JavaBean的情况下,则不能使用@Param;在SQL语句里会引用JavaBean的属性,而且只能引用JavaBean的属性。(所以,要确保实体类和数据库字段对应)

在Mybatis中,#{}和${}区别

#{} 是预编译语句(PrepareStatement)中的占位符?,可以防止SQL注入攻击,可自动转义

    @Select("SELECT * FROM `user` WHERE id=#{id}")
    User getUsersByIdByAnnotate(@Param("id")String id);

在这里插入图片描述

${}是普通的字符串拼接符,不能防止SQL注入攻击,若参数值包含特殊字符可能导致SQL语句失败

    @Select("SELECT * FROM `user` WHERE id='${id}'")
    User getUsersByIdByAnnotate(@Param("id")String id);

在这里插入图片描述


八.缓存

为减少与数据库交互,我们可以把经常查询且不易发生改变的数据放入缓存中

MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

1.一级缓存(默认开启)

SqlSession级别的缓存,也称本地缓存,缓存的数据只在SqlSession内有效。

    @Test
    public void testCache(){
        //一级缓存
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.getUserById("1");
        System.out.println(user1);
        System.out.println("===============================");
        User user2 = mapper.getUserById("1");
        System.out.println(user2);
        System.out.println("===============================");
        System.out.println("user1==user2?"+(user1==user2));
        sqlSession.close();
    }

在这里插入图片描述

一级缓存失效的情况:

  • 查询不同的元组(行)

  • 增删改操作后会刷新缓存

  • 查询不同的XxMapper.xml

  • 手动清理缓存

       session.clearCache();//手动清除缓存
    
2.二级缓存(手动开启)

mapper级别的缓存,也称全局缓存,同一个namespace公用这一个缓存。即多个sqlSession去操作同一个XxMapper.xml的sql语句,可以共享缓存。

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的xMapper.xml查出的数据会放在自己对应的缓存中;
  • 使用步骤

    • 开启全局缓存 【mybatis-config.xml】,默认开启
    <setting name="cacheEnabled" value="true"/>
    
    • 在要使用二级缓存的XxMapper.xml中开启
    <cache/>
    
    • 测试
            SqlSession sqlSession1 = MybatisUtils.getSession();
            UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
            User user1 = mapper1.getUserById("1");
            System.out.println(user1);
            sqlSession1.close();
            System.out.println("===============================");
            
            SqlSession sqlSession2 = MybatisUtils.getSession();
            UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
            User user2 = mapper2.getUserById("1");
            System.out.println(user2);
            sqlSession2.close();
            System.out.println("===============================");
            
            System.out.println("user1==user2?"+(user1==user2));
    

    在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值