学习笔记mybatis

学习视频
学习内容会引用部分狂神的微信公众号笔记

随看随记

  • mybatis官方文档
  • POJO (Plain Old Java Objects)老式java对象
  • Data Access Objepts (DAOs ) 数据持久层对象
  • ${}是statement #{} 是preparationstation
  • 避免sql注入
  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
    1. INSERT INTO user (name) VALUES (#{name});
    2. INSERT INTO user (name) VALUES (?);
  • ${} 的作用是直接进行字符串替换
    1. INSERT INTO user (name) VALUES (’${name}’);
    2. INSERT INTO user (name) VALUES (‘kuangshen’);

Mybatis

  • MyBatis 是一款优秀的持久层框架
  • 持久化是将程序数据在持久状态和瞬时状态间转换的机制。
    • 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)
    • 为什么需要持久化服务呢?那是由于内存本身的缺陷引起的
    • 内存断电后数据会丢失 内存过于昂贵,
  • 持久层 完成持久化工作的代码块 . ----> dao层 【DAO (Data Access Object) 数据访问对象】

第一个Mybatis

maven中 注意加上以下代码 可以把资源加载成class

	<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>
  • 步骤

    1. 写一个工具类,同时引用第二步骤的配置文件
    package com.paleatta;
    
    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 mybatis_util {
    
        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();
            }
        }
    
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
    
    1. 写一个配置文件
    	<?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.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?
                    useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="com/paleatta/dao/UserMapper.xml"/>
        </mappers>
    </configuration>
    
    1. 写实体类
    package com.paleatta.pojo;
    
    public class User {
        private int id;
        private String name;
        private String pwd;
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        public User() {
        }
    }
    
    1. 写接口
    	package com.paleatta.dao;
    
    	import com.paleatta.pojo.User;
    
    	import java.util.List;
    
    	public interface UserDao {
        List<User> getUserList();
    	}
    
    1. 写mapper.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 namespace="com.paleatta.dao.UserDao">
        <select id="getUserList" resultType="com.paleatta.pojo.User">
            select * from mybatis.user
      </select>
    </mapper>
    
    1. 写测试类
    @test
    public void test(){
        SqlSession sqlSession = null;
        try {
            sqlSession = mybatis_util.getSqlSession();
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            List<User> userList= userDao.getUserList();
            for (User user : userList) {
                System.out.println(user);
    
            }
        } catch (Exception e) {
    
    
        } finally {
            sqlSession.close();
        }
    
  1. 写一个工具类
  2. 写一个配置文件
    • 通过jdbc连接数据库
    • 注意 每有一个接口有实现的mapper.xml文件 都需要在这里添加命名空间
  3. 写一个实体类
    • 主要写数据中每一个对象都有什么样的属性
  4. 写一个接口
    • 接口主要得到我们需要的结果集
  5. 写一个mapper.xml
    • mapper.xml主要写sql语句 以及返回的结果集合
  6. 写一个测试类
    • 对编写的dao进行测试
      注意:
  • 关闭sqlSession很重要
  • 在resulttype 和 parametertype 要写数据类型的全限定名

增删改查的实现

  • 主要通过修改mapper中的配置文件来实现多种数据库的处理
  • 根据接口的多态性,通过在测试函数中调用特定功能的接口函数,来实现特定功能的数据库操作
  • 在通关增删改对数据库进行操作时,一定要sqlsession.commit() 进行事务提交
  • 增删改查等语法的详细内容见Mysql学习笔记
    • insert into tableName ([字段1],[字段2],[字段3]) values (value1_1,value1_2,value1_3),(value2_1,value2_2,value2_3),(value3_1,value3_2,value3_3) ;
    • updata tableName set ([字段1]=value1 ,[字段2]=value2,[字段3]=value3) values where 条件;
    • delete from tableName where 条件;
    • 	SELECT
      	DISTINCT <select_list> #(去重 注意取别名)
      	FROM <left_table>
      	<join_type> JOIN <right_table>
      	ON <join_condition>
       	WHERE <where_condition> #(具体的值,子查询语句)
       	GROUP BY <group_by_list> #(通过什么字段进行分组)
       	HAVING <having_condition> #(过滤后的分组信息)
      	ORDER BY <order_by_condition> #(排序顺序 ASC 升序 DESC 降序)
       	LIMIT <limit_number> #(从哪条开始 罗列多少条数据)
      
    • 可能会出现的问题
      • 在mybatis-config.xml中<mapper>标签是需要使用路径即\而不能使用点
      • 在增加中文注释后 出现的bug介绍 :1 字节的 UTF-8 序列的字节 1 无效。将所在的xml的编码方式改成utf8即可。

map方式的参数传递

  • 思想:将原有的通过特定参数类型的方式全部转换成使用map(键值对)形式进行存储和传输,通过对键值对中的集合进行赋值,来实现对特定字段进行sql操作。
  • 以添加为例
 int insertUserByMap(Map<String,Object> map);
 <insert id="insertUserByMap" parameterType="map">
        insert into mybatis.user (id , pwd , name ) values (#{id} , #{possward} , #{name});
    </insert>
SqlSession sqlSession = mybatis_util.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        User user = new User();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("id",6);
        map.put("possward","qwerty");
        map.put("name","行号");
        mapper.insertUserByMap(map);
        sqlSession.commit();


        sqlSession.close();

将一个对象的多个字段转化成一个map集合中的多个数据类型和数据,将整个map对象进行sql语句进行操作 其优点在于不需要考虑传递对象的数据类型,只需要在map中设置好数据类型即可。
通过对象传递参数 sql会获取其对象的属性 [parameterType=“Object”]
通过map传递参数 sql会获取其key值 [parameterType=“map”]

模糊搜索

  • java 代码执行时传递通配符

      map.put("name", "%行%");
    
  • sql拼接中使用通配符

    select * from mybatis.user where name like "%"#{name}"%";
    

属性优化 外部引入db.properties属性

  • 在mybatis-config.xml中的各种标签是有一定顺序的
    在这里插入图片描述

  • 通过在外部db.properties文件中进行配置,然后导入到xml中进行配置

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/jdbcstudy?
    useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username=root
    password=123456
    
    	<properties resource="org/mybatis/example/config.properties">
    	  <property name="username" value="dev_user"/>
    	  <property name="password" value="F2Fa3!33TYyg"/>
    	</properties>
    	<environment id="test">
                <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}"/>
                </dataSource>
        </environment>
    
  • 如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

    1. 首先读取在 properties 元素体内指定的属性。
    2. 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
    3. 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
    4. 因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

别名优化

  • 实体类优化形式
        <typeAliases>
            <package name="com.paleatta.pojo"/>
        </typeAliases>
    
    • 但是只有实体类的优化形式无法对实体类进行其他别名的命名,可以通过在实体类上加@Alias(“user”) 可以对该实体类进行自定义别名限定
	@Alias("user")
	public class User {

映射器说明(mapper)

使用mapper标签进行映射器配置,除此之外还有包名和类名两种方法,但不推荐

<mappers>
    <mapper resource="com/paleatta/dao/Mapper.xml"/>
</mappers>

解决属性名和字段名不一致的问题 resultMap

  • 通过在resultmap中将数据库中的pwd字段映射到User实体类中的possword字段上,对于字段和属性相同的部分可以不予以设定
	<resultMap id="userMap" type="user">
        <result column="pwd" property="possword"/>
    </resultMap>
    <select id="getUserList" resultMap="userMap">
        select * from mybatis.user;
 	</select>

日志

  • 标准日志输出 STDOUT_LOGGING 可以输出所有的日志数据,但是有很多事我们不需要的,可以直接在xml中作为属性名直接用
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

Log4j日志输出

  1. 导入依赖

    <dependency>
       <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
       <version>1.2.17</version>
    </dependency>
    
  2. 配置文件(先用着狂神的,稍微改了一下,以后再看看别的配置)

    	#将等级为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=[%p][%d{yyyy-MM-dd HH:mm:ss}][%c]-%m%n
    	
    	#文件输出的相关设置
    	log4j.appender.file = org.apache.log4j.RollingFileAppender
    	log4j.appender.file.File=./log/paleatta.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{yyyy-MM-dd HH:mm:ss}][%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
    
  3. setting设置日志实现

    	<settings>
    	   <setting name="logImpl" value="LOG4J"/>
    	</settings>
    
  4. 在程序中使用Log4j进行输出

    //注意导包:
    	import org.apache.log4j.Logger
    	public class test {
    
        static Logger logger = Logger.getLogger(test.class);
    
        @Test
        public void test1(){
            SqlSession sqlSession = mybatis_util.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            for (User user : userList) {
                logger.info(user);
            }
            sqlSession.close();
        }
    }
    
  • 注意:有时idea无法打开生成的日志文件 这是因为在日志文件中存在乱码所致,而日志文件中的乱码时 由于在<mapper> 或<typeAliases>中使用package 扫描形式导致的,但是网上也没有比较好的解决方案,因此如果想在idea上看log文件加就不可以使用 package扫描的方法。如果必须要使用的话可以在普通的记事本文件中进行查看。
  • 在以后的输出过程中 一般会以debug为主体,在某些特定的位置输出info或error信息来进行提示或警示客户。

分页

  • sql语句limit进行分页
SELECT  *  FROM TABLE_NAME LIMIT #{START_PAGE},#{PAGE_SIZE}
SELECT  *  FROM TABLE_NAME LIMIT #{PAGE_SIZE}

通过在test中的map来对其进行赋值从而实现分页

  • 分页插件mybatis-PageHelper

mybatis流程图

在这里插入图片描述

  • 自动事务提交开启,可以通过在 getSqlSession() 中将其参数放置为true即可自动实现事务提交。
  • 在这里插入图片描述

注解开发

  • 一般是使用注解配合XML文件一同开发

  • 在选用mapper映射器的时候,有注解,需要使用类映射器,而不能和使用xml映射器

    
        @Results(id = "resultMap" , value = {
                @Result(property = "possword",column ="pwd")})
        @Select("select * from mybatis.user")
        List<User> getUserList();
    
        @ResultMap(value = "resultMap")
        @Select("select * from mybatis.user limit #{startPage},#{pageSize};")
        List<User> getUserListByLimit(Map<String,Integer> map);
    
  • @results== resultMap @result== result通过定义在result中的映射关系来进行映射,当其他接口也组要这么映射时,通过定一个id,并通过resultMap其寻找其id,即可避免重复设置映射关系,上述注解开发与下述xml表达的是同一个意思。

     <resultMap id="userMap" type="user">
            <result column="pwd" property="possword"/>
        </resultMap>
        <select id="getUserList" resultMap="userMap">
            select * from mybatis.user;
        </select>
        <select id="getUserListByLimit" parameterType="map" resultMap="userMap">
            select * from mybatis.user limit #{startPage},#{pageSize};
        </select>
    

注解增删改查

  1. 当传递多个参数时,除了像上一个通过map键值对的形式进行传输,还可以通过直接传参数的形式,在查找参数时,mybatis会寻找param中的变量名,而在主函数中显示的提示信息依旧是int后的变量名,这样就可以实现sql语句中的变量名和测试函数中传递的变量名相分离的功能进行解耦。
    @ResultMap(value = "resultMap")
    @Select("select * from mybatis.user limit #{startPage},#{pageSize};")
        List<User> getUserListByLimit1(@Param("startPage") int startPage1, 
                                   @Param("pageSize") int pageSize1);
    
    在这里插入图片描述
  • @Param注解用于给方法参数起一个名字。以下是总结的使用原则:
    1. 在方法只接受一个参数的情况下,可以不使用@Param。
    2. 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
    3. 如果参数是 JavaBean , 则不能使用@Param。
    4. 不使用@Param注解时,参数只能有一个,并且是Javabean。

Lombok

  • 常用注解
    1. @data 自动生成无参构造、get、set函数、tostring、equals和hashcode
    2. @AllArgsConstructor自动生成全参构造
    3. @NoArgsConstructor
    4. @EqualsAndHashCode
    5. @ToString
    6. @Setter
    7. @Getter
  • 如果想偷懒的话,可以只使用三个加深的注释即可实现所有的配置,但是如果只需要部分进行get或set的,则需要将这些注解放置到这些成员变量即可。注意,对于@data来说,不仅把private的成员变量进行处理,还会对public的成员变量进行get和set处理。

多对一与一对多

多对一

  • 一般一对多的关系都有多端进行关系维护 谁建外键谁维护
  1. 按照查询嵌套处理(子查询)

    <select id="studentListByTid2" resultMap="studentTeacher2">
        select  *    from    student;
    </select>
    <resultMap id="studentTeacher2" type="student">
        <result property="id" column="id" />
        <result property="name" column="name" />
        <association property="teacher" column="tid" 
        javaType="teacher" select="getTeacher"/>
    </resultMap>
    
    <select id="getTeacher" resultType="teacher">
        select  *   from    teacher where id = #{id}
    </select>
    

    相当于对一个sql语句进行了拆分,在查询Student中又进行了Teacher的子查询

  2. 按照结果嵌套处理(关联查询)
    注释中没有@association,因此其只能在xml中使用

      <resultMap id="studentTeacher" type="student">
            <result column="id" property="id" />
            <result column="sname" property="name" />
            <association property="teacher" javaType="Teacher">
                <result column="tname" property="name" />
            </association>
        </resultMap>
    
        <select id="studentListByTid" resultMap="studentTeacher">
            select  s.id id, s.name sname, t.name tname
            from    student s,
                    teacher t
            where   s.tid = t.id;
        </select>
    
    • 注意在按照结果嵌套处理时,当去了别名以后,要把其别名作为数据库的字段进行处理
    • association标签相当于把在teacher表格中寻找字段tname,映射到实体类中的name属性
    • 其中的javaTpye有点类似与进行表关联时的关联的另一张表

一对多

个人认为按照结果查询的代码可读性更高,因此在此仅对按结果进行记录

    <select id="getTeacherByTid" resultMap="TeacherStudent">
        select s.id id , s.name sname, t.name tname, t.id tid
        from student s , teacher t
        where s.tid = t.id;
    </select>
    <resultMap id="TeacherStudent" type="teacher">
        <result property="teacherId" column="tid" />
        <result property="name" column="tname" />
        <collection property="studentList"  ofType="student">
            <result property="id" column="id" />
            <result property="name" column="sname" />
            <result property="tid" column="tid" >
        </collection>
    </resultMap>
  • 其中ofType与javaType功能相似,可以认为是与需要关联的另一个表。

小思考

  • 在什么情况下实体类中使用列表作为成员变量,而什么时候在接口中的方法返回值中使用列表?
  • 对于多对一 通过多个学生的id去查找他们的老师 其输出结果为多个学生,多个学生则需要一个列表来对其进行储存。学生作为一个实体类,老师为其一个成员变量。
  • 对于一对多 通过一个老师的id去查找他的学生 其输出结果为一个老师,老师只有一个,因此不需要列表作为返回值。老师作为一个实体类,有多个学生,需要一个列表作为老师的成员变量。

动态sql

if

  • 通过可以动态添加SQL的方式避免写多种重载的接口函数
  • 注意where 1=1 很重要 这可以保证无论是否在什么条件下都可以进行查询,而且在后面的if标签中的and都要加,否则就会在and加或不加出现问题
  • 通过在test中添加我们要判断的条件即可
    <select id="getBlog" parameterType="map" resultType="blog">
        select * from blog where 1=1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test=" author != null">
            and author = #{author}
        </if>
    </select>
    

choose (when, otherwise)

  • 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。choose只能执行一个条件

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • 因为choose类似一个带有break的switch语句,一次执行的过程中只能执行一个when或者otherwise因此and 加或不加都可以

     <select id="getBlog3" parameterType="map" resultType="blog">
            select * from blog
            <where>
                <choose>
                    <when test="title != null">
                        title = #{title}
                    </when>
                    <when test=" author != null">
                        and author = #{author}
                    </when>
                    <otherwise>
                        and views = #{views}
                    </otherwise>
                </choose>
            </where>
        </select>
    

trim (where, set)

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

    <select id="getBlog2" parameterType="map" resultType="blog">
            select * from blog
            <where>
                <if test="title != null">
                    title = #{title}
                </if>
                <if test=" author != null">
                    and author = #{author}
                </if>
            </where>
        </select>
    
    • 当map中只执行map.put(“author” , “狂神说”); 时 其拼接的sql语句自动去除and为
     select * from blog WHERE author = ?
    
    • set与where类似,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
     <update id="updateBlog" parameterType="map">
            update mybatis.blog
            <set>
                <if test="title != null">
                    title = #{title},
                </if>
                <if test="author != null">
                    author = #{author}
                </if>
            </set>
            where id = "cca2ae70 6ca5 44d4 b0b0 d5588e6d8896"
        </update>
    
    • 当map中只执行map.put(“title” , “Mybatis如此简单2”) 时 其拼接的sql语句自动去除“,“为
     update mybatis.blog SET title = ? where id = "cca2ae70 6ca5 44d4 b0b0 d5588e6d8896"
    

SQL片段

  • 作用帮助实现代码复用

  • 在上面的演示中会发现很多重复代码,可以通过sql标签将其打包,在后面通过include标签从而来实现sql标签中sql语句的复用。

    <sql id="if-title-author">
            <if test="title != null">
               title = #{title}
            </if>
            <if test=" author != null">
                and author = #{author}
            </if>
        </sql>
    
        <select id="getBlog2" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <include refid="if-title-author"></include>
        </where>
    </select>
    

foreach

  • 其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。

     select * from mybatis.blog where id=1 or id=2 or id=3
    
    <select id="getBlogByForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <foreach item="id" collection="ids" open="(" separator=" or " close=")">
                id = #{id}
            </foreach>
        </where>
    </select>
    
  • 需要注意一下,在foreach中多了一个集合变量,这个集合变量也需要在建立好以后,在map中put进去。

    ArrayList<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);
    
    map.put("ids", ids);
    
  • 我觉得可以更加深入的一下map输入,类似与vue框架中的双向绑定,只不过在mybatis中只能将test函数中的map值绑定到sql区域中的指定#{ }中的区域,将map中的右侧绑定到sql语句中的#{ }的区域。

缓存先不看了 感觉可能不太会用到 等到后面有需求再看吧

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值