SSM框架之mybaitis框架详解

三层架构

1、什么是三层架构?

  • 在项目开发中,遵循的一种形式模式,分为三层
    • 界面层:用啦接受客户端的输入,调用业务逻辑层进行功能处理,返回结果给客户端,过去的servlet就是界面层的功能
    • 业务逻辑层:用来进行整个项目的业务逻辑处理,向上为界面层提供处理结果,向下问数据访问层要数据
    • 数据访问层:专门用来进行数据库的增删查改操作,向上为业务逻辑层提供数据
  • 各层之间的调用顺序是固定的,不允许跨层访问
    • 界面层<—->业务逻辑层<—–>数据访问层

SSM框架

常用的况框架SSM

  • Spring:它是整合其他框架的框架,它的核心是IOC和AOP,它由20多个模块构成的,在很多领域都提供了很好的解决方案,是一个很强大的存在

  • SpriingMVC:他是Spring家族的医院,专门用来优化控制器(Servlet)的,提供了极其简单数据提交,数据携带,页面跳转等功能

  • MyBatis:是持久化层的一个框架,用来进行数据库的访问哟话,专注于SQL语句,极大的简化了JDBC的访问

什么是框架?

  • 它是一个半成品软件,将所有的公共的,重复的功能解决掉,帮助程序员快速高效的进行开发,他是可复用的,可扩展的

什么是MyBatis框架

  • 是Apache的一个开源项目iBatis

  • MyBaits最主要用来完成数据访问层的优化,它专注于sql语句,简化了过去JDBC繁琐的访问机制

  • 添加框架的步骤

    • 添加依赖
    • 添加配置
    • 创建ssm数据库,添加student表,并且插入数据
create database ssm default charset utf8;
use ssm;
create table student(
id int(11) AUTO_INCREMENT primary key,
name varchar(255) default null,
email varchar(255) default null,
age int(11) default null
) ENGINE=InnoDB default charset=utf8;
insert into student(name,email,age) values("张三",'zhangsan@126.com',22);
insert into student(name,email,age) values("李四",'lisi@126.com',21);
insert into student(name,email,age) values("王五",'wangwu@126.com',22);
insert into student(name,email,age) values("赵六",'zhaoliu@126.com',24);
select * from student;

配置SqlMapConfig.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>
    <!--读取属性文件,jdbc.properties
    属性:resource:从resource目录下找执行名称的文件加载
    url:使用绝对路径加载属性文件
    -->
    <properties resource="jdbc.properties"></properties>
    <!--配置数据库环境变量(数据库连接配置)-->
    <!--environments里面可以配置多套environment
        default:使用下面的environment标签的id属性进行执行配置
    -->
    <environments default="development">
        <!--id:就是提供给environment的default属性是有-->
        <environment id="development">
            <!--配置事务管理器
                type属性:指定事务管理的方式   
                    JDBC:事务的控制交托给程序员处理
                    MANAGED:由容器(Spring)来管理事务
            -->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源
                type:指定不同的配置方式
                    JNDI:Java命名目录接口,在服务器端进行数据库连接池的管理
                    POOLED:使用数据库连接池进行数据库的连接配置
                    UNPOLLED:不使用数据库连接池
            -->
            <dataSource type="POOLED">
                <!--配置数据库连接的基本参数-->
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>

            </dataSource>
        </environment>
    </environments>
    <!--注册mapper.xml文件
        resource:从resource目录下找指定名称文件注册
        url:使用绝对路径找文件注册
        class:动态代理方式下的注册
    -->
    <mappers>
        <mapper resource="StudentMapper.xml"></mapper>
    </mappers>

</configuration>

配置StudentMapper.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:是整个文件的大标签,用来开始和结束xml
属性:
    namespace:指定命名空间(相当于包名),用来区分不同mapper.xml文件中相同的id属性
-->
<mapper namespace="su">
    <!--
    完成查询全部学生的功能
    resultType:指定查询返回结果集的类型,如果是集合,则必须是泛型的类型
    parameterType:如果有参数,则通过它来指定参数类型
    -->
    <select id="getAll" resultType="com.bjpowernode.pojo.Student">
        select id,name,email,age
        from student
    </select>
</mapper>

测试

        //使用文件流的读取核心配置文件SqlMapConfig
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //取出SqlSession的对象
        SqlSession sqlSession = factory.openSession();
        //完成查询操作
        List<Student> list = sqlSession.selectList("su.getAll");
        list.forEach(student -> System.out.println(student));
        //关闭SqlSession
        sqlSession.close();

使用mybatis进行增删查改

SqlMapConfig配置文件

    <!--按主键id查询学生信息-->
    <select id="getById" parameterType="int" resultType="com.bjpowernode.pojo.Student">
        select id,name,email,age from student where id=#{id}
    </select>

    <!--按学生名称模糊查询-->
    <select id="getByName" parameterType="string" resultType="com.bjpowernode.pojo.Student">
        select id,name,email,age from student where name  like '%${name}%'
    </select>

    <!--增加学生-->
    <insert id="insert" parameterType="com.bjpowernode.pojo.Student">
        insert into student(name,email,age) values (#{name},#{email},#{age})
    </insert>

    <!--按主键删除学生-->
    <delete id="delete" parameterType="int">
        delete from student where id = #{id}
    </delete>

    <!--修改学生-->
    <update id="update" parameterType="com.bjpowernode.pojo.Student">
        update student set name=#{name},email=#{email},age=#{age} where id = #{id}
    </update>

测试程序

//读取核心配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出SqlSession
SqlSession sqlSession = factory.openSession();

//完成查询操作
        List<Student> list = sqlSession.selectList("su.getAll");
        list.forEach(student -> System.out.println(student));
        /*for (Student x : list){
            System.out.println(x);
        }*/
        //关闭SqlSession
        sqlSession.close();
 //按主键查询学生
        Student stu = sqlSession.selectOne("su.getById",1);
        System.out.println(stu);
        //关闭SqlSession
        sqlSession.close();
//执行模糊查询
        List<Student> list = sqlSession.selectList("su.getByName","李");
        for (Student x : list){
            System.out.println(x);
        }
        //list.forEach(student -> System.out.println(student));
        //关闭SqlSession
        sqlSession.close();
//增加学生
        int num = sqlSession.insert("su.insert", new Student("苏叶", "210469@qq.com", 21));
        sqlSession.commit();
        sqlSession.close();
//删除学生
        int delete = sqlSession.delete("su.delete", 5);
        sqlSession.commit();
        sqlSession.close();
//修改学生
        int update = sqlSession.update("su.update", new Student(2, "王麻子", "wm@126.com", 18));
        sqlSession.commit();
        sqlSession.close();

注意:执行增删改语句一定要提交事务

Mybatis对象分析

1、Resources类

  • 就是解析SqlMapConfig.xml文件,创建出相应的对象
  • InputStream in = Resources.getResourceAsStream(“SqlMapConfig.xml”);

2、SqlSessionFactoryBuilder接口

  • 使用ctrl+h查看本接口的子接口及实现类
  • DefaultSqlSessionFactory是实现类
  • SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

3、SqlSession接口

  • DefaultSqlSession实现类
  • SqlSession sqlSession = factory.openSession();

为实体类注册别名

1、单个注册

在SqlMapConfig.xml文件中properties 标签后 environments 前面加入标签,注册之后就可以在Mapper.xml文件中的resultType属性或者parameterType属性中使用这个别名

    <!--注册实体类的别名-->
    <typeAliases>
        <!--单个注册-->
        <typeAlias type="com.bjpowernode.pojo.Student" alias="student"></typeAlias>
    </typeAliases>

2、批量注册

        <!--批量注册别名
        别名是类名的驼峰命名规范
        -->
        <package name="com.bjpowernode.pojo"/>

批量把这个目录下的所有实体类都注册别名,别名是类名的驼峰命名规范

输出日志:固定的写法

    <!--设置日志输出底层执行的代码-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

动态代理

1、动态代理存在的意义

  • 在三层架构中,业务逻辑层需要通过接口访问数据访问层的功能,可以使用动态代理

2、动态代理的实现规范

  • UserMapper.xml文件与UserMapper.java的接口必须在同一个目录。
  • UserMapper.xml文件与UserMapper.java接口的文件名必须一致,后缀可以不管
  • UserMapper.xml文件中的标签id的值与UserMapper.java的接口中方法名称完全一致
  • UserMapper.xml文件中的标签的parameterType属性值与UserMapper.java的接口中方法参数类型完全一致
  • UserMapper.xml文件中标签的resultType值与UserMapper.java的接口方法的返回值类型必须一致
  • UserMapper.xml文件中标签的namespace属性必须是接口的完全限定名称,例如com.bjpowernode.mapper.UserMapper
  • 在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称,例如com.bjpowernode.mapper.UserMapper

3、动态代理的访问

InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
      SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
      sqlSession = factory.openSession();
      uMapper = sqlSession.getMapper(UsersMapper.class);
	//直接调用方法
      List<Users> list = uMapper.getAll();
      for(Users x : list){
         System.out.println(x);
      }
   }
//
//原来的方式
   public void testGetAll() throws IOException {
        //完成查询操作
       //只能通过namespace标注下id的值来调用方法,而且不同的语句使用不同的sqlsession方法
        List<Student> list = sqlSession.selectList("su.getAll");
        list.forEach(student -> System.out.println(student));
    }

使用动态代理的方式进行获取数据

#{}和${}的区别

#{}占位符

  • 传参的时候大部分使用#{}传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问,防止sql注入

  • #{}里面写什么东西,主要看parameterType参数的类型

    • 如果parameterType的类型是简单类型(8种基本(封装)+String),则#{}里面随便写
 <select id="getById" resultType="users" parameterType="int"> >入参类型是简单类型
        select id,username,birthday,sex,address from users where id = #{id} >这个里面可以随便写
    </select>
  • parameterType的类型如果是实体类型,则#{}里面只能是类中的成员变量的名称,并且区分大小写
<insert id="add" parameterType="users">
        insert into users(username,birthday,sex,address) values(#{userName},#{birthday},#{sex},#{address})
    </insert>

${}字符串拼接或字符串替换

  • 字符串拼接,一般用于模糊查询中,建议少用,因为有sql注入的风险

  • 也分为两种情况,同样看parameterType类型

    • 如果parameterType类型是简单类型,则${}里面随便写,但是分版本,如果是3.5.1及以下版本,只能写value
<select id="getByLike" parameterType="string" resultType="users">  >传参是简单类型
        select id,username,birthday,sex,address from users 
    	where username like '%${userName}%'	>${}里面可以随便写
    </select>
  • parameterType的类型如果是实体类型,则${}里面只能是类中的成员变量的名称(溴铵在已经很少用了)

优化模糊查询,因为直接使用${}这个进行拼接会有sql注入危险,所以以后我们使用concat()函数来进行模糊查询

<select id="getByNameGood" parameterType="string" resultType="users">
        select id,username,birthday,sex,address from users where username like concat('%',#{name},'%')
    </select>

字符串替换使用${}

在有的时候我们需要进行模糊查询的时候,不仅仅是根据用户名,有的时候根据地址进行模糊查询,这个时候我们肯定是不能写两个不同的sql语句,这样太麻烦了,所以这个时候我们就要需要用到注解

List<Users> getByNameOrAddress(
            @Param("columnName") String columnName,
            @Param("columnValue") String columnValue); 
<!---->    
    <!--如果参数超过一个则parameterType不用写-->
    <select id="getByNameOrAddress" resultType="users">
        select id,username,birthday,sex,address from users where
        ${columnName} like concat('%',#{columnValue},'%')
    </select>

然后进行测试,要查询哪个字段,就靠自己传参

@Test
   public  void  testGetByNameGoodOrAddress(){
      List<Users> list = uMapper.getByNameOrAddress("address","南");
      for (Users x:list){
         System.out.println(x);
      }
   }

返回主键业务

假如在有的时候,我们插入一行数据,但是主键是自增的,并且关联了其他表,且也要为其增加数据,这样我们还要先查询自增的主键是多少再进行增加另一个表的数据,这样很麻烦,所以我们可以在插入数据的时候返回主键

 <insert id="add" parameterType="users">
    <selectKey keyProperty="id" resultType="int" order="AFTER">
        select last_insert_id()
    </selectKey>
        insert into users(username,birthday,sex,address) values(#{userName},#{birthday},#{sex},#{address})
    </insert>
  • selectKey标签的详解:
    • keyProperty:users对象的哪个属性来返回主键值
    • resultType:返回主键的类型
    • order:在插入语句前还是后返回主键的值

动态SQL

  • 什么是动态sql

    • 可以定义代码片段,可以进行逻辑判断,可以进行循环处理(批量处理),使条件判断更简单
  • :用来定义代码片段,可以将所有的列名,或者复杂的条件定义为代码片段,供使用时调用

  • :用来引用标签定义的代码片段

    <!--定义代码片段-->
    <sql id="allColumns">
        id,username,birthday,sex,address
    </sql>
 	<!--在sql语句中引用-->
    <select id="getAll" resultType="users">
        select <include refid="allColumns"></include> from users
    </select>
  • 进行条件判断

  • 进行多条件拼接,在查询,删除,更新中使用

    • 强化select查询,使用if进行判断
    <select id="getByCondition" parameterType="users" resultType="users">
        select <include refid="allColumns"></include> from users
        <where>
            <if test="userName != null and userName !=''">
                and username like concat('%',#{userName},'%')
            </if>
            <if test="birthday != null">
                and birthday = #{birthday}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="address != null and address != ''">
                and address like concat('%',#{address},'%')
            </if>
        </where>
    </select>

测试

   @Test
   public void testGetByCondition(){
      Users users = new Users();
      users.setUserName("小");
      List<Users> list = uMapper.getByCondition(users);
      for (Users x : list){
         System.out.println(x);
      }
   }
  • :有选择的进行更新处理,至少更新一列
    <update id="updateBySet" parameterType="users" >
        update users
        <set>
            <if test="userName != null and userName != ''">
                username = #{userName},
            </if>
            <if test="birthday != null">
                birthday = #{birthday},
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex},
            </if>
            <if test="address != null and address != ''">
                address = #{address},
            </if>
        </set>
            where id = #{id}
    </update>

测试

   @Test
   public void testUpdateBySet(){
      Users users = new Users();
      users.setId(6);
      users.setUserName("TheShy");
      int i = uMapper.updateBySet(users);
      sqlSession.commit();
   }

:用来进行循环遍历,完成循环条件查询,批量删除,批量增减,批量更新

  • collection:用来指定入参的类型,如果是List集合则为小写的list,Map为map,数组Array是array

  • item:每次循环遍历出来的值或者对象

  • separator:多个值或者多个对象之间的分隔符

  • open:整个循环前的前括号

  • close:整个循环的后括号

    <select id="getByIds" resultType="users">
        select <include refid="allColumns"></include>
        from users
        where id in (
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
         )
    </select>

使用foreach实现批量删除

    <delete id="deleteBatch">
        delete from users
        where id in
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

测试

   @Test
   public void testDeleteBatch(){
      Integer[] array = {4,7};
      int i = uMapper.deleteBatch(array);
      sqlSession.commit();
   }

注意:如果要使用批量更新,需要再properties配置文件中的url后加上 &allowMultiQueries=true

指定参数位置

如果入参是多个,可以通过指定参数位置进行传参,是实体类包含不住的条件,实体类只能封装住成员变量的条件,如果某个成员变量需要有区间范围之内的判断,或者两个值进行处理,则实体类包不住

  • 例如:查询指定日期范围内的用户信息
  • 首先定义接口中的方法,因为是在一个区间之内,所以需要两个参数的方法
List<Users> getByBirthday(Date beigin,Date end);
  • 然后在Mapper.xml文件中编写sql语句
    <select id="getByBirthday" resultType="users">
        select <include refid="allColumns"></include>
        from users
        where birthday between #{arg0} and #{arg1}
    </select>

在进行多个参数查询的时候,我们可以用 arg0 开始指定,从零开始,依次递增

  • 测试
   @Test
   public void testGetByBirthday() throws ParseException {
      Date begin = sf.parse("2001-01-1");
      Date end = sf.parse("2003-01-1");
      List<Users> byBirthday = uMapper.getByBirthday(begin, end);
      for (Users x : byBirthday){
         System.out.println(x);
      }
   }

入参为Map

  • 如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更明确
List<Users> getByMap(Map map);
    <!--
    #{birthdayBegin}  为map集合的key
    #{birthdayEnd}     为map集合的value
    -->
    <select id="getByMap" resultType="users">
        select <include refid="allColumns"></include>
        from users
        where birthday between #{birthdayBegin} and #{birthdayEnd}
    </select>
   @Test
   public void testGetByMap() throws ParseException {
      Date begin = sf.parse("2001-01-1");
      Date end = sf.parse("2003-01-1");
      Map map = new HashMap<>();
      map.put("birthdayBegin",begin);
      map.put("birthdayEnd",end);
      List<Users> byBirthday = uMapper.getByMap(map);
      for (Users x : byBirthday){
         System.out.println(x);
      }
   }

返回值是map

  • 如果返回的数据实体类无法包含,可以使用map返回多张表中若干的数据,然后后这些数据之间没有任何关系,就是Object类型,map的key就是列名或者别名
    //返回值是map(一行)
    Map getReturnMap(Integer id);
    //返回值为多行map
    List<Map> getAllMap();
    <!--返回值为一行map-->
    <select id="getReturnMap" parameterType="int" resultType="map">
        select username,address from users
        where id = #{id}
    </select>
    <!--返回值为多行map-->
    <select id="getAllMap" resultType="map">
        select username,address from users
    </select>
   //返回值为一行map
   @Test
   public void testGetReturnMap(){
      Map returnMap = uMapper.getReturnMap(2);
      System.out.println(returnMap.get("username"));
      System.out.println(returnMap.get("address"));
   }
   //返回多行Map
   @Test
   public void testGetAllMap(){
      List<Map> allMap = uMapper.getAllMap();
      for (Map x : allMap){
         System.out.println(x);
      }
   }

resultMap的用法

  • 在有的时候实体类的属性和数据库表的字段不一样的情况下,可以使用resultMap进行关系映射
    <!--实体类和表中的字段不一样的情况下,可以使用resultMap手工完成映射-->
    <resultMap id="bookmap" type="book">
        <!--
		id是你在sql语句中设置的id type是你映射的类型
		-->
        <!--主键绑定-->
        <!--property为你实体类中的属性,column为你数据库中对应的字段名-->
        <id property="id" column="bookid"></id>
        <!--非主键绑定-->
        <result property="name" column="bookname"></result>
    </resultMap>
    <select id="getAllBook" resultMap="bookmap"> <!--设置返回值为resultMap,并且设置名字,为你resultMap中的id-->
        select bookid,bookname from book
    </select>

表的关联关系

关联关系是有方向的

  • 一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方向,就是一对多关联

  • 多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生的方向,这就是多对一的关系

  • 一对一关联:一个老师辅导一个学生,一个学生只请教一个老师,学生和老师一对一

  • 多对多关联:园区划线的车位和园区的任意一辆车,一个车位可以停任意一辆车,任意一辆车可以停在任意的一个车位。

  • 一对多的关联管理

    • 客户和订单就是典型的一对多关联关系,一个客户名下可以有多个订单,客户表是一方,订单表是多方
  • 使用一对多的关联关系,可以按组查询客户的同时查询该客名下所有的订单

    • 把两个表创建实体类,客户表可以创建一个订单类型的属性
//客户表
    private Integer id;
    private String name;
    private Integer age;
    private List<Orders> ordersList;
//订单orders表
    private Integer id;
    private String orderNumber;
    private Double orderPrice;

因为返回的数据不仅仅有客户表中的信息,还有订单表中的信息,所以数据库要多表连接查询,查询的数据的不同,所以要使用resultMap进行一个绑定

    <resultMap id="customermap" type="customer">
        <id property="id" column="cid"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <!--orders属性绑定-->
        <collection property="ordersList" ofType="orders">
            <id property="id" column="oid"></id>
            <result property="orderNumber" column="orderNumber"></result>
            <result property="orderPrice" column="orderPrice"></result>
        </collection>
    </resultMap>
    
    <select id="getById" parameterType="int" resultMap="customermap">
        select c.id cid,name,age,o.id oid,orderNumber,orderPrice,customer_id
        from customer c left join orders o on c.id = o.customer_id
        where c.id = #{id}
    </select>

多对一关联关系:订单和客户就是多对一关联,站在订单的方向查询同时将客户信息查询出来这就是多对一查询,同理可以在订单的实体类中设置用户customer为属性,然后创建订单的接口和其Mapper.xml文件

总结:无论什么关联关系,如果某一方持有另一方的集合,则使用标签完成映射,如果某方持有另一方对象,则使用标签完成映射

事务

多个操作同时完成,或者同时失败成为事务处理

  • 事务有四个特性:一致性,持久性,原子性,隔离性
  • 在mybatis中是设置事务,设置为程序员自己处理的提交和回滚
<transactionManager type="JDBC"></transactionManager>
  • 也就可以设置为自动提交
sqlSession = factory.openSession(); //默认是手工提交事务,设置为false也是手动提交事务,设置为true为自动提交事务
sqlSession = factory.openSession(true);//设置为自动提交,在增删改后面不需要commit()

缓存

  • mybatis框架提供了两级缓存,一级缓存和二级缓存,默认开启一级缓存
  • 缓存就是为了提高查询效率,如果数据库中发生改变,那么将会清空缓存
  • 使用缓存后查询的流程:
  • 查询时先到缓存里面查,如果没有则去数据库查询,放在缓存一份,然后返回给客户端,下次再查询的时候,不再访问数据库,直接在缓存里面拿。如果数据库发生了commit()操作,那么将会清空缓存。
  • 一级缓存,使用的是sqlSession的作用域,同一个sqlSession共享一级缓存的数据
  • 二级缓存使用的是mapper的作用域,不同的sqlSession只要访问的是同一个mapper.xml文件,则共享二级缓存作用域

ORM

  • ORM(Object Relational Mapping):对象映射关系
  • mybatis框架是ORM非常优秀的框架
  • Java语言中以对象的方式操作数据,存到数据库中是以表的方式进行存储,对象中的成员变量与表中的列之间的数据互换成为映射,整个这套操作就是ORM
  • 持久化的操作:将对象保存到关系型数据库中,将关系型数据库中的数据读出来以对象的形式封装
  • mybatis是持久化层优秀的框架
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值