mybatis笔记

一、ssm技术体系

框架:具有约束性的支撑某些功能实现的半成品的项目(没有业务逻辑)

ssm:springMVC+spring+mybatis

springMVC:MVC(控制层)框架

spring:整合型框架

mybatis:持久层框架

二、mybatis简介

1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录

4) Mybatis 是一个 半自动的ORM(Object Relational Mapping)框架

三、常用的持久化技术比较

1、jdbc,纯手动的持久化技术

2、mybatis,半自动的持久层框架

3、hibernate,全自动的持久层框架

执行效率:jdbc>mybatis>hibernate

开发效率:hibernate>mybatis>jdbc

四、mybatis搭建过程

1、创建Java工程

2、导入jar

junit:junit-4.12.jar hamcrest-core-1.3.jar

mybatis的核心包:mybatis-3.4.1.jar

mysql的驱动包:mysql-connector-java-5.1.37-bin.jar

3、创建表和实体类

4、在src下创建mybatis的核心配置文件mybatis-config.xml,作用是设置连接数据库的信息以及操作数据库的基本配置

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- xml中的约束,dtd文件就是一个标签库,规定了当前xml中所能够使用的标签,以及标签的作用 -->

5、在src下创建映射文件,一个映射文件代表一张表的操作,因此命名为XxxMapper.xml,其中写的内容是操作某张表数据的sql,因此作用是操作表中的数据

6、创建实现持久化的mapper,命名和映射文件的名字保持一致XxxMapper。

7、创建mybatis中提供的操作数据库的对象SqlSession

//读取mybatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取操作数据库的对象SqlSession
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();

8、通过SqlSession对象中getMapper()获取mapper接口的代理实现类对象,调用接口中的方法,就可以自动匹配到映射文件中的某一个sql进行执行。注意:必须满足两个条件,映射文件的namespace和mapper接口的全类名保持一致,sql语句的id和mapper接口中的方法名保持一致

注意:

A:同一个映射文件中,sql语句的id不能重复

B:加入日志功能,首先下入jar(log4j.jar),还需要将配置文件log4j.xml放在src下

五、核心配置文件详解

<?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>

	<!--
		mybatis核心配置文件中的标签必须按照顺序:
        properties?,settings?,typeAliases?,
        typeHandlers?,objectFactory?,objectWrapperFactory?,
        reflectorFactory?,plugins?,environments?,
        databaseIdProvider?,mappers?
        常用的标签的顺序
        properties?,settings?,typeAliases?,plugins?,environments?,mappers?
	
	-->

    <!--引入jdbc.properties,就可以通过${key}的方式获取资源文件中key所对应的值-->
    <properties resource="jdbc.properties"></properties>

	<settings>
        <!--将下划线转换为驼峰-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--
        typeAliases:设某个类型设置别名,在mybatis的使用范围之内,若使用此类型,都可以使用其对应的别名代替
        typeAlias:设置某个类型的别名
        属性:
            type:需要设置别名的类型
            alias:为类型所设置的别名,若不设置别名,则默认的别名为类名,且不区分大小写
        package:为指定的包下所有的类型设置默认的别名,即类名,且不区分大小写
    -->
    <typeAliases>
        <!--<typeAlias type="com.atguigu.mybatis.bean.User" alias="User"></typeAlias>-->
        <package name="com.atguigu.mybatis.bean"/>
    </typeAliases>

    <!--
        environments:设置多个连接数据库的环境
        属性:
            default:设置默认使用的数据库环境的id
     -->
    <environments default="mysql">
        <!--
            environment:设置一个具体的数据库连接环境,可以设置多个
            属性:
                id:设置数据库环境的唯一标示,不能重复
        -->
        <environment id="mysql">
            <!--
                transactionManager:设置事务管理方式
                属性:
                    type:设置事务管理的方式,值有"JDBC|MANAGED"
                    type="JDBC":使用最原始的JDBC中的事务管理方式,即事务的提交和回滚都必须手动处理
                    type="MANAGED":被管理,例如spring中AOP
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:设置数据源,用来管理数据库连接
                属性:
                    type:设置数据源的类型,值有"POOLED|UNPOOLED|JNDI"
                    POOLED:使用数据库连接池,会将所使用的数据库连接对象进行缓存,可以重复使用
                    UNPOOLED:不使用数据库连接池,即每一次操作数据库都要创建一个新的连接对象
                    JNDI:使用上下文中的数据源
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 引入映射文件 -->
    <mappers>
        <!--<mapper resource="UserMapper.xml"/>-->
        <!--
            package:将制定的包下的所有映射文件引入到核心配置文件中
            注意:必须保证映射文件和mapper接口在同一个包下
        -->
        <package name="com.atguigu.mybatis.mapper"/>
    </mappers>

</configuration>

六、mybatis获取参数值的两种方式

#{}:PreparedStatement,在为字符串类型的数据拼接到sql中时,会自动加单引号

${}:Statement,在为字符串类型的数据拼接到sql中时,需要自己手动加单引号

1、若参数为一个字面量(String和基本数据类型),则通过#{}和${}都可以获取参数值

#{}可以通过任意的名字获取参数值,但是建议使用参数名

只 能 通 过 两 种 固 定 写 法 , {}只能通过两种固定写法, {value}和${_parameter},注意单引号问题

2、若参数为一个实体类对象,则通过#{}和${}都可以获取参数值

#{}和 都 可 以 直 接 将 实 体 类 对 象 中 的 属 性 名 放 在 中 , 就 可 以 获 取 属 性 值 , 但 是 {}都可以直接将实体类对象中的属性名放在{}中,就可以获取属性值,但是 {}要注意单引号问题

3、若参数为多个时,mybatis会自动将这些参数放在一个map集合中

void checkLogin(String username, String password);

userMapper.checkLogin(“admin”,“123456”);

map:

map.put(“0”, “admin”); map.put(“1”, “123456”);

map.put(“param1”, “admin”); map.put(“param2”, “123456”);

#{}:可以将map集合的键放在{}中,就可以获取键所对应的值,即参数值,#{0}、#{1}、#{N-1}或#{param1}、#{param2}、#{paramN}

: 因 为 在 {}:因为在 {}中具有运算功能,因此只能使用 p a r a m 1 、 {param1}、 param1{param2}、${paramN},注意单引号问题

4、将所有的数据手动存储在map中,就可以使用自己设置的键获取参数值

#{}和 都 可 以 直 接 将 m a p 的 键 放 在 中 , 就 可 以 获 取 键 所 对 应 的 值 , 但 是 {}都可以直接将map的键放在{}中,就可以获取键所对应的值,但是 map{}要注意单引号问题

5、可以再每个参数上加注解@Param,此时mybatis同样会将参数放在map中

void checkLoginByParam(@Param(“username”)String username, @Param(“password”)String password);

userMapper.checkLoginByParam(“admin”,“123456”);

map:

map.put(“param1”, “admin”); map.put(“param2”, “123456”);

map.put(“username”,“admin”); map.put(“password”,“123456”);

#{}和KaTeX parse error: Expected 'EOF', got '#' at position 23: …map的键获取键所对应的值,即#̲{param1}、#{para…{param1}、 p a r a m 2 、 {param2}、 param2{paramN},或将所设置的@Param注解的值放在{}中,就可以获取相应的参数值,注意${}的单引号问题

6、若参数只有一个List集合或数组时,mybatis会自动将集合或数组放在map中,List集合以list为键,数组以array为键

七、CRUD

mybatis在实现增删改时,可以获取sql语句执行的结果,即受影响的行数,只需要在mapper接口中,将实现增删改的方法的返回值设置为int或Integer,就可以直接获取受影响的行数

1、添加

获取自动生成的主键,需要在添加的sql中,设置属性useGeneratedKeys="true"和keyProperty=“id”

useGeneratedKeys=“true”:设置当前的主键生成策略,主键自动递增

keyProperty=“id”:将自动递增的主键放在所传输的参数的某个属性中,不能作为返回值返回的,增删改的返回值是固定的,受影响的行数

<!--void addUser(User user);-->
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values(null, #{username}, #{password}, #{age}, #{sex}, #{email})
</insert>

2、修改

<!--Integer updateUser(User user);-->
<update id="updateUser">
    update t_user set username = #{username}, password = #{password}, age = #{age}, sex =#{sex}, email = #{email} where id = #{id}
</update>

3、删除

A:删除一条数据

<!--Integer deleteUser(@Param("id") Integer id);-->
<delete id="deleteUser">
    delete from t_user where id = #{id}
</delete>

B:批量删除

<!--Integer deleteMoreUser(@Param("ids") String ids);-->
<delete id="deleteMoreUser">
    delete from t_user where id in (${ids})
</delete>

注意:

批量删除,即对于delete from t_user where id in(),不能使用#{},因为#{}会在字符串上自动加上单引号,就变成了delete from t_user where id in(‘1,2,3’),我们需要的是delete from t_user where id in(1,2,3),在mysql5.7版本,delete from t_user where id in(‘1,2,3’)会直接报错:Truncated incorrect DOUBLE value: ‘4,5,6’,因此在mybatis中实现批量删除时,只能使用${}

4、查询

注意:查询的sql必须要设置属性resultType或resultMap

查询时,要将数据库表中的数据自动和实体类中的属性创建映射关系,进行赋值,就必须要保证字段名和属性名保持一致。若不一致,有三种解决方案:

A:通过别名,将表中的字段名设置别名的方式和属性名保持一致

B:resultMap

C:若字段名和属性名不一致,但是表示的含义一致,各自使用了各自的特性,例如:字段名为user_name,属性名为userName,这种情况,就可以在mybatis的核心配置文件中,设置全局配置mapUnderscoreToCamelCase为true,即自动将字段名中的下划线转换为驼峰,例如user_name–>userName

各种查询方式:

A:查询一个对象

<!--User getUserById(@Param("id") Integer id);-->
<select id="getUserById" resultType="User">
    select id,user_name,password,age,sex,email from t_user where id = #{id}
</select>

B:查询一个list集合

<!--List<User> getUserList();-->
<select id="getUserList" resultType="User">
    select * from t_user
</select>

注意:

当查询的数据只有一条,可以以一个对象或一个list集合作为结果,但是若查询出的数据条数是大于一条,则只能通过list集合获取,否则会出现以下异常:

org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 6

C:查询单个的数据

<!--Integer getCount();-->
<select id="getCount" resultType="java.lang.Integer">
    select count(*) from t_user
</select>

只要将select标签中的resultType设置为所需要的类型的全类名或别名即可

mybatis内部,为Java中的一些类型设置了别名:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCbzsHZu-1639490874072)(E:\资料\0720\9月21日\img\mybatis中设置的别名.png)]

D:将一条数据查询为一个map集合

<!--Map<String, Object> getUserByMap(@Param("id") String id);-->
<select id="getUserByMap" resultType="map">
    select * from t_user where id = #{id}
</select>

E:将多条数据查询为一个map集合

<!--@MapKey("id")Map<String, Object> getMoreUserByMap();-->
<select id="getMoreUserByMap" resultType="map">
    select * from t_user
</select>

当把一条数据转换为map,会将字段作为键,把字段所对应的值作为值,存储在map中

当把多条数据转换为map,必须要设置注解@MapKey(“id”),就可以将查询出的每一条数据,先转换为map,再以注解MapKey的值(即查询出的某一个字段)所对应的字段值作为键,把每一条数据转换之后的map作为值,存储在一个新的map中

八、多表联查(多对一、一对多)

多对一和一对多的表关系,对一需要在实体类中创建一个对象,表示映射关系;对多需要在实体类中创建一个集合,表示映射关系。例如,员工实体类中需要有一个表示部门的对象,部门实体类中需要有一个表示员工的集合。因此通过sql查询出的字段和实体类中的属性无法映射,因此需要通过resultMap实现自定义映射

resultMap自定义映射属性:

id:设置主键的映射规则
result:设置普通字段的映射规则
属性:
    property:设置映射关系中实体类的属性名
    column:设置映射关系中sql查询出的字段名
association:处理多对一关系中的对象属性
属性:
    property:设置映射关系中实体类的属性名
    javaType:设置association要处理的对象属性的类型
collection:处理一对多关系中的集合属性
属性:
    property:设置映射关系中实体类的属性名
    ofType:设置集合属性中的数据的类型
分步查询的属性:
	select:分步查询所对应的sql(namespace.id)
	column:分步查询的条件,必须是sql查询出的某一个字段
分步查询当设置了mybatis允许懒加载之后,对于每一个分步查询都有效,若需要将某个分步查询设置为立即加载,则需要设置属性fetchType="eager"

1、多对一:

A:级联

<resultMap id="empDeptMap" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="ename" column="ename"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="dept.did" column="did"></result>
    <result property="dept.dname" column="dname"></result>
</resultMap>

<!--List<Emp> getEmpDeptList();-->
<select id="getEmpDeptList" resultMap="empDeptMap">
    SELECT emp.eid,emp.ename,emp.age,emp.sex,dept.did,dept.dname FROM t_emp emp LEFT JOIN t_dept dept ON emp.did = dept.did
</select>

B:association

<resultMap id="empDeptAssociationMap" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="ename" column="ename"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <association property="dept" javaType="Dept">
        <id property="did" column="did"></id>
        <result property="dname" column="dname"></result>
    </association>
</resultMap>

<!--List<Emp> getEmpDeptList();-->
<select id="getEmpDeptList" resultMap="empDeptAssociationMap">
    SELECT emp.eid,emp.ename,emp.age,emp.sex,dept.did,dept.dname FROM t_emp emp LEFT JOIN t_dept dept ON emp.did = dept.did
</select>

C:分步查询:将两表联查通过多个sql语句查询出所有的数据,特点是可以延迟加载(懒加载),即需要用到什么数据,相应的sql才会执行。但是必须在mybatis中进行配置,使允许使用懒加载,在mybatis的核心配置文件中设置一下配置:

<!--设置允许使用懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--设置按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>

<resultMap id="empDeptStepMap" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="ename" column="ename"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <association property="dept" select="com.atguigu.mybatis.mapper.DeptMapper.getDeptByDid" column="did"></association>
</resultMap>

<!--List<Emp> getEmpDeptListByStep();-->
<select id="getEmpDeptListByStep" resultMap="empDeptStepMap">
    select * from t_emp
</select>

<!--Dept getDeptByDid(@Param("did") Integer did);-->
<select id="getDeptByDid" resultType="Dept">
	select * from t_dept where did = #{did}
</select>

2、一对多

A:collection

<resultMap id="deptEmpMap" type="Dept">
    <id property="did" column="did"></id>
    <result property="dname" column="dname"></result>
    <collection property="emps" ofType="Emp">
        <id property="eid" column="eid"></id>
        <result property="ename" column="ename"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
    </collection>
</resultMap>

<!--List<Dept> getDeptEmpList();-->
<select id="getDeptEmpList" resultMap="deptEmpMap">
    select dept.did,dept.dname,emp.eid,emp.ename,emp.age,emp.sex from t_dept dept left join t_emp emp on dept.did = emp.did
</select>

B:分布查询

<resultMap id="deptEmpStepMap" type="Dept">
    <id property="did" column="did"></id>
    <result property="dname" column="dname"></result>
    <collection property="emps" select="com.atguigu.mybatis.mapper.EmpMapper.getEmpByDid" column="did"></collection>
</resultMap>
<!--List<Dept> getDeptEmpByStep();-->
<select id="getDeptEmpByStep" resultMap="deptEmpStepMap">
    select did,dname from t_dept
</select>
<!--List<Emp> getEmpByDid(@Param("did") Integer did);-->
<select id="getEmpByDid" resultType="Emp">
	select * from t_emp where did = #{did}
</select>

注意:

特殊的SQL:

批量删除只能使用${}

delete from t_user where id in (${id})

模糊查询可以使用${}实现效果,但是若通过#{}实现,则需要使用mysql中concat()函数

select * from t_user where user_name like '%${mohu}%'
select * from t_user where user_name like concat('%',#{mohu},'%')

九、动态SQL

分析多条件查询:

A、页面中所设置的多个条件,在sql语句中是并且的关系,因为条件越多,则查询的数据越精确

B、页面中若未设置此条件,则绝对不能将其拼接到sql中

多条件查询的实现:

A:通过特殊的恒成立条件1=1去拼接各个条件

<select id="getUserByTJ" resultType="User">
    select * from t_user
    where 1=1
    <if test="username!=null and username!=''">
        and username = #{username}
    </if>
    <if test="password!=null and password!=''">
        and password = #{password}
    </if>
    <if test="age!=null">
        and age = #{age}
    </if>
    <if test="sex!=null and sex!=''">
        and sex = #{sex}
    </if>
    <if test="email!=null and email!=''">
        and email = #{email}
    </if>
</select>

B:where标签

<select id="getUserByTJ" resultType="User">
    select * from t_user
    <where>
        <if test="username!=null and username!=''">
            username = #{username}
        </if>
        <if test="password!=null and password!=''">
            and password = #{password}
        </if>
        <if test="age!=null">
            and age = #{age}
        </if>
        <if test="sex!=null and sex!=''">
            and sex = #{sex}
        </if>
        <if test="email!=null and email!=''">
            and email = #{email}
        </if>
    </where>
</select>

C:trim标签

<select id="getUserByTJ" resultType="User">
    select * from t_user
    <trim prefix="where" suffixOverrides="and">
        <if test="username!=null and username!=''">
            username = #{username} and
        </if>
        <if test="password!=null and password!=''">
            password = #{password} and
        </if>
        <if test="age!=null">
            age = #{age} and
        </if>
        <if test="sex!=null and sex!=''">
            sex = #{sex} and
        </if>
        <if test="email!=null and email!=''">
            email = #{email}
        </if>
    </trim>
</select>

1、if标签:通过其中的test属性,选择是否拼接if标签中的内容
2、where标签:可以生成where关键字,并去掉条件开始时多余的and或or关键字,但是条件结束时多余的and或or无效
3、trim标签:对标签中的内容添加指定前缀或后缀,或者去掉指定前缀或后缀

属性:

​ suffix:在trim标签中的内容之后加上指定后缀

​ suffixOverrides:在trim标签中的内容之后去掉指定后缀

​ prefix:在trim标签中的内容之后加上指定前缀

​ prefixOverrides:在trim标签中的内容之后去掉指定前缀

4、choose、when、otherwise:相当于if…else if…else
5、foreach:对数组或集合进行循环

属性:

​ collection:要循环的数组或集合

​ item:设置一个属性名表示循环的集合或数组中的每一个元素

​ separator:循环体之间的分隔符

​ open:循环体以什么开始

​ close:循环体以什么结束

6、sql片段:记录一段sql,以方便在需要的地方进行引用

设置方式:

<sql id="userColumns">id,username,password,age,sex,email</sql>

引用方式:

<include refid="userColumns"></include>

十、mybatis的缓存机制

mybatis的缓存:mybatis会将查询出的结果进行缓存,当再次访问相同数据(即缓存中拥有的数据)时,就不需要再次从数据库中获取,直接从缓存中取

mybatis的缓存分为一级缓存和二级缓存

1、一级缓存:基于SqlSession,即通过同一个SqlSession查询出的数据会被进行缓存。mybatis的一级缓存是默认开启的

使一级缓存失效的情况:

A:使用的是不同的SqlSession

B:在两次查询中间执行了任何一次增删改的操作都会清空一级缓存

C:手动清空缓存,sqlSession.clearCache();

2、二级缓存:基于namespace(映射文件),在同一个映射文件中查询出的数据就会被缓存,就算是不同的SqlSession也可以从二级缓存中能够获取数据。mybatis的二级缓存必须在SqlSession关闭或提交之后才有效。mybatis的二级缓存必须手动开启

开启mybatis二级缓存的步骤:

1、在核心配置文件中设置,默认是开启。

2、在需要实现二级缓存的映射文件中添加标签

3、缓存所保存的数据,即表所对应的实体类必须实现序列化

使二级缓存失效的情况:

A:使用不同的SqlSession不会使二级缓存失效,但是必须要保证SqlSession是由同一个SqlSessionFactory所提供的

B:在两次查询中间执行了任何一次增删改的操作都会清空一级缓存和二级缓存

C:sqlSession.clearCache();只能清空一级缓存

D:在增删改查的标签中有属性flushCache,在增删改的操作中默认为true,即执行增删改之后刷新以及缓存和二级缓存;但是在查询的标签中flushCache默认值为false,即执行查询之后不刷新缓存。因此也可以在查询的标签中手动设置flushCache=“true”,当执行查询操作也刷新缓存

E:在查询标签中有属性useCache,默认值为true,可以手动设置为false,代表不使用二级缓存

3、第三方缓存:mybatis提供了实现缓存的接口Cache,因此存在第三方缓存,用来替代二级缓存

第三方缓存使用ehcache

步骤:

A:导入jar

ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar

slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar

B:导入ehcache的配置文件

C:设置二级缓存的类型

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

十一、mybatis逆向工程(MBG)

使用mybatis逆向工程的步骤:

1、导入jar

mybatis-generator-core-1.3.2.jar

2、导入mybatis逆向工程的配置文件

3、使用逆向的代码实现功能

十二、分页插件PageHelper

1、导入jar

jsqlparser-0.9.5.jar pagehelper-5.0.0.jar

2、在核心配置文件中引入此插件

<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>

3、使用

//指定当前访问的页码以及每页显示的条数,必须在查询之前调用
PageHelper.startPage(9, 3);
//查询
List<Emp> emps = empMapper.selectByExample(null);
//获取分页相关数据
PageInfo<Emp> page = new PageInfo<>(emps, 5);

PageInfo{

pageNum=1, pageSize=3, size=3, startRow=1, endRow=3, total=27, pages=9, list=Page{count=true, pageNum=1, pageSize=3, startRow=0, endRow=3, total=27, pages=9, reasonable=false, pageSizeZero=false}, prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=5, navigateFirstPage1, navigateLastPage5, navigatepageNums=[1, 2, 3, 4, 5]}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值