Lesson 8: MyBatis

目录

一、MyBatis定义

二、Mybatis开发环境的配置

2.1 引入MyBatis框架到项目中

2.2.1 新项目引入MyBatis框架

2.2.2 老项目引入MyBatis框架

2.2 配置MySQL的服务器地址、用户名和密码

2.2.1 配置数据库连接信息

2.2.2 配置MyBatis的XML保存路径(MyBatis运行时使用)

为什么使用xml?

2.2.3 总的配置信息

2.3 写代码,执行MyBatis的业务处理

2.3.1 查询数据库的示例

2.3.2 单元测试

三、实现数据库的增、删、改操作的示例

3.1 新增一条记录

3.1.1返回受影响行数的实现步骤

3.1.2 返回添加成功记录的主键的实现步骤

3.2 修改一条记录

3.3 删除一条记录

四、实现数据库的查询操作的示例

4.1 单表查询

4.1.1 $和#的区别

4.1.2 like查询(concat()函数)

4.2 多表查询

4.2.1 返回字典映射:resultMap

4.2.2 一对一的表映射

4.2.3 一对多的表映射

五、动态SQL

5.1  if标签

5.2  trim标签

5.3  where标签

5.4 set标签

5.5  foreach标签

六、总结


一、MyBatis定义

MyBatis是一款优秀的持久层框架。它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。

之前学习的JDBC,整个操作⾮常的繁琐,不仅要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等, ⽽所有的这些操作步骤都需要在每个⽅法中重复书写。

MyBatis 是一种更简单完成程序和数据库交互的⼯具,学习MyBatis可以帮助我们更简单的操作和读取数据库⼯具。

二、Mybatis开发环境的配置

2.1 引入MyBatis框架到项目中

2.2.1 新项目引入MyBatis框架

MyBatis在进行映射时,数据模型有多个,在该项目中,使用的是MySQL数据库,所以需要将MySQL的依赖也导入进来。

新建的MyBatis项目,如果没有配置数据库的连接信息,项目是启动不起来的,即需要指明连接的是哪个数据库。 如何配置将会在下文中介绍。

2.2.2 老项目引入MyBatis框架

①安装EditStartes插件

 ②添加依赖

MyBatis在映射数据模型时,可能有多个。我们使用的是MySQL,因此要指定映射的数据模型是MySQL。

这步千万不能省略哦!!!

2.2 配置MySQL的服务器地址、用户名和密码

2.2.1 配置数据库连接信息

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/messagewall?characterEncoding=utf8&useSSL=false
    username: root
    password: 1111
    driver-class-name: com.mysql.jdbc.Driver

driver-class-name:是指明数据库的类型

如果是MySQL8之前,使用com.mysql.jdbc.Driver

如果是MySQL8之后,使用com.mysql.cj.jdbc.Driver

设置好后,如果程序能够启动起来,就证明OK了。

2.2.2 配置MyBatis的XML保存路径(MyBatis运行时使用)

mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

classpath:就是指当前系统运行的根路径

所有以Mapper.xml结尾的文件都是MyBatis的匹配文件,并且放在mapper这个文件夹下。如果不写这个,就无法将Interface和xml关联起来。因为xml有多个

为什么使用xml?

因为需要写sql,放在类中写不好实现。xml更适合一点。

2.2.3 总的配置信息

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/blogsystem?characterEncoding=utf8&useSSL=false
    username: root
    password: 1111
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

2.3 写代码,执行MyBatis的业务处理

2.3.1 查询数据库的示例

以根据ID查询用户为例。

step1:先建立一个model包,里面创建一个User类,类中的属性和数据库中user表的信息一致,生成属性的get和set方法。

数据库中的user表:

package com.example.demo.model;

import lombok.Data;

@Data
public class User {
    private int userID;
    private String userName;
    private String password;
}

(step2-step3都是根据业务流程写的)

step2:建立一个包controller,创建一个类Controller,用于校验参数是否合理,参数合理则调用service包中里面的方法

package com.example.demo.controller;

import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/getuser")
    public User getUserById(Integer id){
        User user = null;
        if(id > 0 && id != null){
            user = userService.getUserById(id);
        }
        return user;
    }
}

step3:建立一个包service,里面新建一个类UserService。UserService类里面调用userMapper的getUserById方法。

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;
    public User getUserById(Integer id){
        return userMapper.getUserById(id);
    }

}

step4:建立一个包mapper,创建一个接口UserMapper,写一个getUserById的方法。这里只是定义,具体的实现在xml中。

package com.example.demo.mapper;

import com.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper{
    public User getUserById(Integer id);
}

step5:写接口的具体实现。在resources下建立一个文件夹mapper,里面创建一个UserMapper的xml文件。

问什么要建立一个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=你要实现的接口的完整包名+接口名 -->
<mapper namespace="com.example.demo.mapper.UserMapper">
<!--    id = 实现接口中的方法名    resultType = 返回的类型-->
    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
        select * from user where userID = #{id}
    </select>
</mapper>

<mapper></mapper>是程序员自定义的东西。

2.3.2 单元测试

SpringBoot默认就会添加一个单元测试的框架,因此无需添加单元测试框架。

 点击之后就会生成一个单元测试类。在生成的单元测试类中需要干三件事情:

step1:加注解@SpringBootTest,表明当前单元测试的框架是SpringBoot,不是其他

step2:将需要测试的类引入

step3:在testGetUserById()方法中调用自己要测试的方法,然后,运行testGetUserById()方法程序就可以运行起来了。

package com.example.demo.mapper;

import com.example.demo.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest // 表示当前单元测试的框架是SpringBoot,不是其他
class UserMapperTest {
    // 测试的是User Mapper的getUserById方法,需要先将类注入
    @Resource
    private UserMapper userMapper;

    // 这个就相当于一个main方法,点击这个就可以进行测试了。由于测试的是UserMapper的方法,因此,需要先将该类引入
    @Test
    void testGetUserById() {
        User user = userMapper.getUserById(2);
        System.out.println("测试结果"+user.toString());
    }

}

 采用单元测试,就可以不用通过前端来验证后端写的代码是否正确,可以使用单元测试来进行验证。

三、实现数据库的增、删、改操作的示例

3.1 新增一条记录

往数据库中新增一条记录的返回结果有两种。第一种是返回受影响的行数,第二种是返回添加成功记录的主键。

3.1.1返回受影响行数的实现步骤

step1:在UserMapper接口中声明一个方法

    /*
    添加用户,有两种情况,第一种是返回受影响的行数、第二种是添加成功记录的主键
     */
    public int addUser(String userName,String password);

step2:在UserMapper.xml中实现接口中定义的方法

    <insert id="addUser">
        insert into user(userName,password) values(#{userName},#{password})
    </insert>

step3:进行单元测试

    @Test
    void addUser() {
        int a = userMapper.addUser("wangwu","22222");
        System.out.println(a);
    }

运行结束后,查看数据库,

 可以得出该方法正确。

3.1.2 返回添加成功记录的主键的实现步骤

step1:在UserMapper接口中声明一个方法

    /*
    添加用户,返回添加成功记录的主键,如果用户的属性很多,函数的参数可以直接写一个对象
     */
    public int addUser2(User user);

step2:在UserMapper.xml中实现接口中定义的方法

useGeneratedKeys=true,表示开启自增主键,keyProperty指定自增的是哪个属性(程序的类中),keyColumn指明的数据库中表的属性

<!--    useGeneratedKeys=true,表示开启自增主键,keyProperty指定自增的是哪个属性,keyColumn指明的数据库中表的属性-->
    <insert id="addUser2" useGeneratedKeys="true" keyProperty="userID" keyColumn="userID">
<!--        传递过来的是个对象,可以直接写对象的属性-->
        insert into user(userName,password) values(#{userName},#{password})
    </insert>

step3:进行单元测试

 @Test
    void addUser2() {
        User user = new User();
        user.setUserName("xiaohong");
        user.setPassword("11111");
        int a = userMapper.addUser2(user);
        //Mybatis框架已经将id设置到user中
        System.out.println(user.getUserID());
    }

运行结束后,查看输出的UserID以及查看数据库

使用原生的Ajax,浏览器版本不同,写法会有差异。jQuery已经做了底层的兼容。

3.2 修改一条记录

 step1:在UserMapper接口中声明一个方法

public int update(User user);

step2:在UserMapper.xml中实现接口中定义的方法

    <update id="update">
        update user set userName=#{userName},password=#{password} where userID=#{userID}
    </update>

step3:进行单元测试

    @Test
    void update() {
        User user = new User();
        user.setUserID(5);
        user.setUserName("asdf");
        user.setPassword("11111");
        int a = userMapper.update(user);
        System.out.println(a);
    }

运行结束后,查看数据库

3.3 删除一条记录

 step1:在UserMapper接口中声明一个方法

public int delete(int id);

step2:在UserMapper.xml中实现接口中定义的方法

    <delete id="delete">
        delete from user where userID=#{userID}
    </delete>

step3:进行单元测试

    @Test
    void delete() {
        userMapper.delete(2);
    }

运行结束后,查看数据库

四、实现数据库的查询操作的示例

4.1 单表查询

 step1:在UserMapper接口中声明一个方法

public User getUserById(Integer id);

step2:在UserMapper.xml中实现接口中定义的方法

    <select id="getUserById" resultType="com.example.demo.model.User">
        select * from user where userID = #{id}
    </select>

4.1.1 $和#的区别

#是占位符,$是替换符,#和$的区别:

①传入的参数是int,没有影响。

  step1:在UserMapper接口中声明一个方法

public User getUserById(Integer id);

step2:在UserMapper.xml中实现接口中定义的方法

    <select id="getUserById" resultType="com.example.demo.model.User">
        select * from user where userID = #{id}
    </select>
    <select id="getUserById" resultType="com.example.demo.model.User">
        select * from user where userID = ${id}
    </select>

在xml中实现接口的方式有两种,一种是使用#{id},另一种是使用${id}。如果传入的参数是int,这两种都可以。

②传入的参数是String,$会有影响,因为直接替换,传入的String就没有带引号

  step1:在UserMapper接口中声明一个方法

public User selectByName(String userName);

step2:在UserMapper.xml中实现接口中定义的方法

    <select id="selectByName" resultType="com.example.demo.model.User">
        select * from user where userName=#{userName}
    </select>
    <select id="selectByName" resultType="com.example.demo.model.User">
        select * from user where userName=${userName}
    </select>

如果使用$,$是将参数直接替换,构造的sql语句是

aaaa没有加双引号,语句错误。 

③传入的参数是desc或者asc,$不会影响,#会有影响,加了引号后会识别不出来

 step1:在UserMapper接口中声明一个方法

public List<User> orderResult(String order);

step2:在UserMapper.xml中实现接口中定义的方法

    <select id="orderResult" resultType="com.example.demo.model.User">
        select * from user order by userID ${order}
    </select>

如果使用#,desc会变成“desc”,sql就无法识别出这个关键字。 

④使用$,会存在sql注入问题。

查询字符串也想使用$,可以加上双引号。

    <select id="selectByName" resultType="com.example.demo.model.User">
        select * from user where userName='${userName}'
    </select>

但是这样会存在sql注入问题。以用户登录代码为例,理想情况下:当sql 注入代码:“' or 1='1”,

结论:用于查询的字段,尽量使用 #{} 查询

4.1.2 like查询(concat()函数)

like查询,构造的sql语句是:

select * from user where userName like '%ing%';

在UserMapper.xml中写的sql语句,如果使用#占位,构造的sql语句是:

select * from user where userName like '%#{username}%'

 为了更方便的查看构造出的sql语句以及过滤掉springboot的启动日志,加入以下配置:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/blogsystem?characterEncoding=utf8&useSSL=false
    username: root
    password: 1111
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml
  configuration:
#    将拼装好的sql打印出来
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    root: warn
    com:
      example:
        demo: debug

进行单元测试,观察日志

我们可以看到,使用#占位,并不能将参数传递进来。

同时,为了避免SQL注入问题,也不能使用$。我们该怎么办呢?

可以使用mysql的内置函数concat()来处理,代码如下:

public User getListByName(String partname);
    <select id="getListByName" resultType="com.example.demo.model.User">
        select * from user where userName like concat('%',#{partname},'%')
<!--        select * from user where userName like concat('%','x','%')-->
    </select>

该方法的单元测试就不做赘述啦。

4.2 多表查询

4.2.1 返回字典映射:resultMap

字段名称和程序中的属性名不同的情况,可以使用resultMap配置映射。

一对一和一对多可以使用resultMap映射并查询数据。

4.2.2 一对一的表映射<association>

一篇博客只对应一个用户为例。

step1:在Blog类中加上user属性。

package com.example.demo.model;

import lombok.Data;

@Data
public class Blog {
    private int blogId;
    private String blogTitle;
    private String blogContent;
    private int authorID;
    private User user;
}

step2:构建BlogMapper接口,并写一个根据博客id查询博客信息的方法

package com.example.demo.mapper;

import com.example.demo.model.Blog;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BlogMapper {
    public Blog getBlogByID(int blogID);
}

step3:新建一个文件BlogMapper.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=要实现的接口的完整包名和类名-->
<mapper namespace="com.example.demo.mapper.BlogMapper">
<!--    id 起到一个标识的作用 type=要映射的类-->
    <resultMap id="BashMap" type="com.example.demo.model.Blog">
<!--        prpperty:类的属性 column:在表中的属性-->
        <id property="blogId" column="blogId"></id>
        <result property="blogContent" column="blogContent"></result>
        <result property="blogTitle" column="blogTitle"></result>
        <result property="authorID" column="userID"></result>
<!--        需要创建用户的resultMap-->
        <association property="user"
                     resultMap="com.example.demo.mapper.UserMapper.UserMap"
                     columnPrefix="u_"></association>
    </resultMap>
    <select id="getBlogByID" resultMap="BashMap">
        select b.*,u.userID u_userID,u.userName u_userName from blog b left join user u on b.authorID=u.userID where b.blogID=#{blogID}
    </select>
</mapper>

step4:由于在step3中,需要用到User类的映射,接下来,在UserMapper.xml中写user的映射

    <resultMap id="UserMap" type="com.example.demo.model.User">
        <id property="userID" column="userID"></id>
        <result property="userName" column="userName"></result>
        <result property="password" column="password"></result>
    </resultMap>

step5:进行单元测试,在此不做赘述。

4.2.3 一对多的表映射

一个用户可以写多篇博客。

step1:在User类中加上List<Blog>属性。

package com.example.demo.model;

import lombok.Data;

import java.util.List;

@Data
public class User {
    private int userID;
    private String userName;
    private String password;
    private List<Blog> blogList;
}

step2:在UserMapper接口,并写一个根据用户id查询用户所有博客的方法

public User getUserByID2(int userID);

step3:在UserMapper.xml的文件。

    <resultMap id="UserMap" type="com.example.demo.model.User">
        <id property="userID" column="userID"></id>
        <result property="userName" column="userName"></result>
        <result property="password" column="password"></result>
        <collection property="blogList"
                    resultMap="com.example.demo.mapper.BlogMapper.BashMap"
                    columnPrefix="b_">
        </collection>
    </resultMap>
    <select id="getUserByID2" resultMap="UserMap">
        select u.*,b.blogID b_blogID,b.blogTitle b_blogTitle,b.blogContent b_blogContent,b.authorID b_authorID from user u left join blog b on u.userID=b.authorID where
        u.userID=#{userID}
    </select>

step4:进行单元测试,在此不做赘述。

五、动态SQL

5.1  if标签

在注册时,有些字段是必须填的,如用户名、密码等。有些字段不是必须填的,比如爱好、邮箱、年龄等。因此在注册的时候,有的用户提交的信息是不确定的,有的用户提交的字段是用户名密码和爱好,有的提交的是用户密码邮箱等等,字段的排列组合很多,如何针对每一种组合写一个方法,就太麻烦了。这时,可以借助if标签。

text中的blogContent,是传入对象中的属性,不是数据库字段

    public int addBlog(String blogTitle,String blogContent,int authorID);
    <insert id="addBlog">
        insert into blog(blogTitle,
        <if test="blogContent != null">
            blogContent,
        </if>
        authorID
        ) values(
        #{blogTitle},
        <if test="blogContent">
            #{blogContent},
        </if>
        #{authorID})
    </insert>

使用单元测试:

 但是,if也有一些缺点,比如,

<insert id="addBlog">
        insert into blog(blogTitle,
        <if test="blogContent != null">
            blogContent,
        </if>
        <if test="authorID = 0">
            authorID,
        </if>
        ) values(
        #{blogTitle},
        <if test="blogContent">
            #{blogContent},
        </if>
        <if test="authorID">
            #{authorID},
        </if>
        )
    </insert>

如果只传了一个参数,会导致构造的sql多了一个逗号,导致sql语句有问题,程序报异常。使用trim标签可以帮我们解决这个多个符号或者少个符号的问题。

5.2  trim标签

<trim>标签搭配if标签,可以实现对多个字段动态查询。<trim>标签有如下属性:

prefix:表示整个语句块,以prefix的值作为前缀

suffix:表示整个语句块,以suffix的值作为后缀

prefixOverrides:表示整个语句块要去除掉的前缀

suffixOverrides:表示整个语句块要去除掉的后缀

<insert id="addBlog">
        insert into blog
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="blogTitle != null">
                blogTitle,
            </if>
            <if test="blogContent != null">
                blogContent,
            </if>
            <if test="authorID != 0">
                authorID,
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="blogTitle != null">
                #{blogTitle},
            </if>
            <if test="blogContent">
                #{blogContent},
            </if>
            <if test="authorID">
                #{authorID},
            </if>
        </trim>
    </insert>

5.3  where标签

传入的用户对象,根据属性实现where条件查询,作为查询依据的属性可能有多个。

public List<Blog> selectByInfo(Blog blog);
<select id="selectByInfo" resultType="com.example.demo.model.Blog">
        select blogId,blogTitle,blogContent,authorID from blog
        <where>
            <if test="blogId != null">
                and blogId=#{blogId}
            </if>
            <if test="blogTitle != null">
                and blogTitle=#{blogTitle}
            </if>
            <if test="blogContent != null">
                and blogContent=#{blogContent}
            </if>
            <if test="authorID != 0">
                and authorID=#{authorID}
            </if>
        </where>
    </select>

单元测试:

    @Test
    void selectByInfo() {
        Blog blog = new Blog();
        blog.setBlogId(1);
        List<Blog> blogs = blogMapper.selectByInfo(blog);
        System.out.println(blogs);
    }

where标签会自动去掉结尾的and。 

5.4 set标签

根据传入的属性动态更新数据库中的记录。

public int updateBlog(Blog blog);
<update id="updateBlog">
        update blog
        <set>
            <if test="blogId != null">
                blogId=#{blogId},
            </if>
            <if test="blogTitle != null">
                blogTitle=#{blogTitle},
            </if>
            <if test="blogContent != null">
                blogContent=#{blogContent},
            </if>
            <if test="authorID != 0">
                authorID=#{authorID},
            </if>
        </set>
        where blogId=#{blogId}
    </update>

单元测试:

    @Test
    void updateBlog() {
        Blog blog = new Blog();
        blog.setBlogId(1);
        blog.setBlogTitle("titlr11");
        blog.setBlogContent("content11");
        blogMapper.updateBlog(blog);
    }

5.5  foreach标签

在遍历集合时,可以使用该标签,他有以下属性:

collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
item:遍历时的每一个对象
open:语句块开头的字符串
close:语句块结束的字符串
separator:每次遍历之间间隔的字符串

举个例子:根据多个博客id删除博客

    public int deleteBlogByIDs(int[] IDs);
    <delete id="deleteBlogByIDs">
        delete from blog
        where blogId in
        <foreach collection="IDs" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </delete>

单元测试:

    @Test
    void deleteBlogByIDs() {
        int[] IDs = {1, 2, 3};
        int a = blogMapper.deleteBlogByIDs(IDs);
        System.out.println(a);
    }

六、总结

MyBatis是一种更简单实现程序和数据库交互的工具,通过学习MyBatis,主要了解了以下知识:
①配置一个MyBatis的开发环境。MyBatisFramework和MySQLDriver这两个缺一不可。在配置数据库的连接信息时,除了URL、用户名和密码外,还需要配置driver-class-name,指明数据库的类型。MyBatis的xml文件是增删改查方法的具体实现,需要配置xml文件的保存路径。
②MyBatis具体的业务处理。总体流程时:建立实体类,属性和数据库表中表保持一致——》建立XXXMapper接口,并在类中定义一个想要实现的方法——》在resource文件夹下建立一个文件mapper,创建XXXMapper的xml文件。在xml文件中实现刚刚接口中定义的方法——》单元测试
③增加分为返回受影响的行数和返回添加成功记录的主键这两种。第二种需要用到useGeneratedKeys和keyProperty的属性。
④ #和$的区别。传入的参数是String,对$有影响。如果传入的参数sql关键字,如desc,会对#有影响。$还存在sql注入问题。
⑤学习了like查询,需要使用mysql的内置函数concat()。需要先映射
⑥学习了多表查询。先建立实体类的映射,映射时用到resultMap标签。一对一用到的是association标签,一对多用的是collection标签。
⑦学习了动态SQL的查询。学习了<if>、<trim>、<where>、<set>和<foreach>。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘减减

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值