Mybatis最详解!!

目录

一,概念

二,准备工作及基本流程

1,引入依赖

2,配置数据库连接的字符串

3,写持久层代码

4,单元测试

三,基本操作

1,打印日志

2,注解实现

(1)传递参数

(2)insert增

(3)主键的获取

(4)删delete

(5)改update

(6)查select

法一:运算SQL语句中的重命名的写法:

法二:结果映射

法三:通过配置完成

3,Mybatis XML配置文件 实现

(1)引入依赖(同上)

(2)配置连接字符串和MyBatis(同上)

(3)写持久层代码(同上)

(4)添加UserInfoXMLMapper.xml

(5)标签

(6)主键获取

(6)标签

(7)标签

(8)标签4,多表查询5,#{}和${}的区别(1)基本概念(2)预编译SQL优点(3)排序中的${}(4)模糊查询中的${}(5),#和$的使用原则:6,数据库连接池:7,进阶标签(1),标签:(2),标签(3),标签(4),标签(5),标签(6),标签


一,概念

MyBatis是⼀款优秀的持久层框架,⽤于简化JDBC的开发.简单来说MyBatis是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库⼯具 

二,准备工作及基本流程

1,引入依赖

可以在创建项目的时候就引入依赖

也可以在已有的代码上,在pom文件中手动引入依赖

  <dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>3.0.3</version>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>

2,配置数据库连接的字符串

#数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

一般情况下,直接复制上述代码就可以了,但是要注意更改:你的mysql的端口号,还有你有引入的库名,登录名,和密码(如果密码为纯数字,需要将密码加上引号,避免后续出错)

注意:如果使⽤MySQL是5.x之前的使⽤的是"com.mysql.jdbc.Driver",如果是⼤于5.x使⽤的 是“com.mysql.cj.jdbc.Driver”

3,写持久层代码

创建一个mapper文件,并创建一个持久层的接口UserInfoMapper,

package com.ABdolphin.mybatis.demo.mapper;
import com.ABdolphin.mybatis.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface UserInfoMapper {
    @Select("select * from user_info")
    List<UserInfo> selectAll();
}

@Mapper注解:表示Mybatis中的Mapper接口,程序运⾏时,框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IoC容器管理

4,单元测试

我们在上述代码中右击鼠标,点击generate,然后选择Test,然后选择你要测试的方法,将其进行勾选,然后点击确定,就会在src下test路径下自动生成测试类

@SpringBootTest
class UserInfoMapperTest {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Test
    void selectAll() {
        userInfoMapper.selectAll().forEach(x-> System.out.println(x));
    }
}

需要注意的是,如果需要注入之前创建类对象,就需要使用注解@SpringBootTest,如果只是单纯的写个Main方法,也没有类对象的调用,就可以不加该注解.

三,基本操作

1,打印日志

在Mybatis当中我们可以借助⽇志,查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果,直接复制下述代码即可:

# 配置打印 MyBatis⽇志
mybatis:
  configuration: 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2,注解实现

在实现之前我们首先要了解一下数据库的表结构,并创建对应字段的类.

以下是数据库的构造代码:

-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;

CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
USE mybatis_test;

-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
        `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
        `username` VARCHAR ( 127 ) NOT NULL,
        `password` VARCHAR ( 127 ) NOT NULL,
        `age` TINYINT ( 4 ) NOT NULL,
        `gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
        `phone` VARCHAR ( 15 ) DEFAULT NULL,
        `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 

-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

我们根据字段,在Java中创建一个类

import lombok.Data;

import java.util.Date;
@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;

}

(1)传递参数

@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容,所以如果我们想要根据id查询来进行查询的时候,就可以直接在注解中写对应的SQL语句

@Select("select * from user_info where id=2")
    List<UserInfo> selectAllById();

但是这是一个死代码,只可以查询id=2的数据,所以我们就需要进行传参,从而达到想要id为几,就可以传相应的id,@Select里面接收参数的方法为,数据库的字段名=#{参数名称}

 @Select("select * from user_info where id=#{id}")
    UserInfo selectAllById2(Integer id);

当参数有一个的时候,<数据库的字段名=#{参数名称}>中的参数名称可以跟真正的参数名称不一致,但是不推荐这样写

@Select("select * from user_info where id=#{aa}")
    //单个参数赋值可以随便写
    UserInfo selectAllById2(Integer id);

当有多个参数的时候:

//顺序可以颠倒
@Select("select * from user_info where age=#{age} and gender=#{gender}; ")
List<UserInfo> selectAllByAgeAndGender( Integer gender, Integer age);


//可以正确执行,但是不推荐
@Select("select * from user_info where age=#{param2} and gender=#{param1}; ")
List<UserInfo> selectAllByAgeAndGender(Integer gender, Integer age);

也可以对参数进行重命名,使用注解@param:但是建议第二种写法

//参数重命名 
@Select("select * from user_info where age=#{age1} and gender=#{gender1}; ")
List<UserInfo> selectAllByAgeAndGender(@Param("gender1") Integer gender, @Param("age1") Integer age);


//建议参数和注解保持一致,即:
@Select("select * from user_info where age=#{age} and gender=#{gender}; ")
List<UserInfo> selectAllByAgeAndGender(@Param("gender") Integer gender, @Param("age") Integer age);

(2)insert增

使用@Insert标签

有以下两种方法:

//法一:
    @Insert("insert into user_info (username,`password`,age,gender)"+
            "values(#{username},#{password},#{age},#{gender})")
    Integer insertUser(UserInfo userInfo);
  
//法二:
    @Insert("insert into user_info (username,password,age,gender)"+
            "values(#{userInfo1.username},#{userInfo1.password},#{userInfo1.age},#            
             {userInfo1.gender})")
    Integer insertUserByParam(@Param("userInfo1") UserInfo userInfo);

如果对对象进行重命名的话,就需要用:重命名的名字.属性的方式去获得参数

(3)主键的获取

如果想要拿到⾃增id,需要在Mapper接⼝的⽅法上添加⼀个Options的注解,useGeneratedKeys:这会令MyBatis使⽤JDBC的getGeneratedKeys⽅法来取出由数据库内 部⽣成的主键,默认值:false.

keyProperty:指定能够唯⼀识别对象的属性,MyBatis会使⽤getGeneratedKeys的返回值或 insert 语句的selectKey⼦元素设置它的值,默认值:未设置(unset)

@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user_info (username,password,age,gender)"+
        "values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#                      
         {userInfo.gender})")
Integer insertUserByParam(@Param("userInfo") UserInfo userInfo);

(4)删delete

 @Delete("delete from user_info where id=#{id}")
    Integer delete(Integer id);

(5)改update

 @Update("update user_info set password=#{password} where id=#{id}")
    Integer update(Integer id,String password);

(6)查select

我们之前已经见过,select的查询结果,可以发现其中有些值是空的,但是数据库中是存在数据的

这是因为数据库中的字段和Java类中定义的属性是不一致的,这就导致无法实现两者的映射,所以无法调用出deleteFlag,createTime,updateTime的值(这是由于数据库和Java字段命名的方法不同所导致的,数据库单词之间是使用_来连接的,但是Java是用小驼峰的形式)

我们有三种解决办法:

法一:运算SQL语句中的重命名的写法:

@Select("select id, username, password, age, gender, phone,"+
        " delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info")
List<UserInfo> selectAll();
法二:结果映射
    @Results(id = "BaseMap",value = {
            @Result(column = "delete_flag",property = "deleteFlag"),
            @Result(column = "create_time",property = "createTime"),
            @Result(column = "update_time",property = "updateTime")
    })
    @Select("select id, username, password, age, gender, phone,"+
            " delete_flag , create_time , update_time  from user_info")
    List<UserInfo> selectAll3();

    @ResultMap("BaseMap")
    @Select("select id, username, password, age, gender, phone,"+
            " delete_flag , create_time , update_time  from user_info where id=2")
    List<UserInfo> selectAllById3();

我们也可以对@result设置id中,从而用@ResultMap注解实现复用对应的@resul

法三:通过配置完成

前面陈述了字段不同是由于,数据库和Java不同命名规范引起的,因此两者转换也是有一定规范的,所以我们就可以通过配置来进行自动映射

mybatis:
  configuration: 
    map-underscore-to-camel-case: true #配置驼峰⾃动转换

3,Mybatis XML配置文件 实现

(1)引入依赖(同上)

(2)配置连接字符串和MyBatis(同上)

(3)写持久层代码(同上)

(4)添加UserInfoXMLMapper.xml

 # 配置mybatis xml的⽂件路径,在resources/mapper创建所有表的xml⽂件
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml
 

数据持久成的实现,MyBatis的固定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.example.demo.mapper.UserInfoMapper">


 </mapper>

注意: mapper namespace 写的是实现接口的全限定名称

(5)<insert>标签

Integer insertUser(UserInfo userInfo);

Integer insertUser2(@Param("userInfo") UserInfo userInfo);
    //法一:
    <insert id="insertUser" >
         insert into user_info(username,`password`,age,gender)
        values (#{username},#{password},#{age},#{gender})
    </insert>

    //法二:
    <insert id="insertUser2">
        insert into user_info(username,`password`,age,gender)
        values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#    
         {userInfo.gender})
    </insert>

(6)主键获取

 <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        <include refid="inserCol"></include>
        values (#{username},#{password},#{age},#{gender})
    </insert>

(6)<update>标签

Integer updateUser2(UserInfo userInfo);

Integer updateUser(Integer id,String password);
 <update id="updateUser">
        update  user_info set password=#{password} where id=#{id}
    </update>

(7)<delete>标签

 <delete id="deleteUser">
        delete from user_info where id=#{id}
    </delete>

(8)<select>标签

注意:<select>标签,需要写返回值类型(类的全限定名称),

这时也会遇到同样的问题,解决方法也同样是上述三种,这里就不过多赘述了

 //法一:   
    <select id="selectAll2" resultType="com.ABdolphin.mybatis.demo.model.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag as deleteFlag,
        create_time as createTime,
        update_time as updateTime
        from user_info
    </select>

//法二:
    <resultMap id="BaseMap" type="com.ABdolphin.mybatis.demo.model.UserInfo">
        <id property="id" column="id"></id>
        <result property="deleteFlag" column="delete_flag"></result>
        <result property="createTime" column="create_time"></result>
        <result property="updateTime" column="update_time"></result>
    </resultMap>
    <select id="selectAll2" resultMap="BaseMap">
        select id, username, password, age, gender, phone, delete_flag ,
        create_time , update_time from user_info
    </select>

//法三:配置

4,多表查询

我们创建一个,文章类的表

-- 创建⽂章表
 
DROP TABLE IF EXISTS articleinfo;
 CREATE TABLE articleinfo (
 id INT PRIMARY KEY auto_increment,
 title VARCHAR ( 100 ) NOT NULL,
 content TEXT NOT NULL,
 uid INT NOT NULL,
 delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
 create_time DATETIME DEFAULT now(),
 update_time DATETIME DEFAULT now() 
) DEFAULT charset 'utf8mb4';-- 插⼊测试数据
 
INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正⽂', 1 );
package com.ABdolphin.mybatis.demo.model;

import lombok.Data;
import org.apache.ibatis.annotations.Mapper;

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer uid;
    private String deleteFlag;
    private String createTime;
    private String updateTime;
}
@Mapper
public interface ArticleInfoMapper {
    @Select("select ta.id,tb.username,tb.`password`,tb.phone,"+
           " ta.content,ta.title,ta.uid, ta.create_time,ta.delete_flag,ta.update_time " +
            "from article_info ta " +
            "left join user_info tb on  ta.id=tb.id " +
            "where ta.id =#{id}")
    ArticleInfo selectArticleInfoAndUserInfo(Integer id);
}

多表查询大多慢SQL,在企业开发中要避免使用多表查询,慢SQL会影响集体性能,进而影响其他项目的使用!!

5,#{}和${}的区别

(1)基本概念

#{}的值是使⽤ MySQL ? 进⾏占位. 这种SQL我们称之为"预编译SQL",${}参数是直接拼接在SQL语句中了,我们称之为"即时SQL"

@Select("select * from user_info where username=#{userName}")
    UserInfo selectAllByName(String userName);

@Select("select * from user_info where username=${userName}")
    UserInfo selectAllByName(String userName);

参数依然是直接拼接在SQL语句中了,但是字符串作为参数时,需要添加引号,⽤ ${} 不会拼接引号 '' ,导致程序报错,以下为正确写法(手动加上单引号):

 @Select("select * from user_info where username='${userName}'")
    UserInfo selectAllByName(String userName);

(2)预编译SQL优点

1. 性能更⾼ 绝⼤多数情况下,某⼀条SQL语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同.如果每次都需要 经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了.预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译 (只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率

2. 更安全(防⽌SQL注⼊) SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。

例如:

一个小小的栗子(登录页面)

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/selectUserList")
    public List<UserInfo> selectUserList(){
        return userService.selectUserList();
    }
    @RequestMapping("/login")
    public boolean login(String userName,String password){
        UserInfo userInfo = userService.selectUserNameAndPassword(userName, password);
        if (userInfo==null){
            return false;
        }else {
            return true;
        }
    }

}
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public List<UserInfo> selectUserList() {
        return userInfoMapper.selectAll();
    }

    public UserInfo selectUserNameAndPassword(String userName, String password) {
        List<UserInfo> userInfos = userInfoMapper.selectAllByNameAndPassword(userName, password);
        if (userInfos.size()>0){
            System.out.println("========"+userInfos.get(0)+"===========");
            return userInfos.get(0);
        }else {
            return null;
        }
    }
}
   
@Mapper
public interface UserInfoMapper { 
@Select("select * from user_info where username='${userName}' and password='${password}'")
    List<UserInfo> selectAllByNameAndPassword(String userName,String password);
}

sql 注⼊代码: ' or 1='1.这种情况无论你的密码是否正确都可以登录

(3)排序中的${}

//排序
    @Select("select * from user_info order by id #{order}")
    List<UserInfo> selectUserByOrder(String order);

//测试代码
@Test
    void selectUserByOrder() {
        userInfoMapper.selectUserByOrder("desc").forEach(x-> System.out.println(x));
    }

如果参数是字符串,用井号来写,会将参数自动加上单引号放到SQL语句中,如下图所示:

所以这里我们就需要使用$符号来实现该功能:

@Select("select * from user_info order by id ${order}")
    List<UserInfo> selectUserByOrder(String order);

 @Test
    void selectUserByOrder() {
        userInfoMapper.selectUserByOrder("desc").forEach(x-> System.out.println(x));
    }

实现排序只能使用$符号,但是使用$符号,就要考虑到SQL注入的问题,可以是使用枚举(desc/asc),参数校验

(4)模糊查询中的${}

 @Select("select* from user_info where username like '%#{name}%'")
    List<UserInfo> selectUserByLike(String name);

//测试代码
 @Test
    void selectUserByLike() {
        userInfoMapper.selectUserByLike("ABDol");
    }

可以使用MySQL的内置函数进行拼接字符的方式:

运用上述函数的写法,这样就可以使用#符合的写法,他会自动加上单引号,代码如下:

@Select("select* from user_info where username like CONCAT('%',#{name},'%')")
    List<UserInfo> selectUserByLike(String name);

//测试代码
 @Test
    void selectUserByLike() {
        userInfoMapper.selectUserByLike("ABDol");
    }

(5),#和$的使用原则:

1.,能使用#,使用#

2,不能使用#,使用$时,一定要规避SQL注入的问题

6,数据库连接池:

程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,当客⼾ 请求数据库连接池,会从数据库连接池中获取Connection对象,然后执⾏SQL,SQL语句执⾏完,再把Connection归还给连接池

优点:减少了网络开销,资源重用,提升了系统性能

常见数据库连接池:C3P0,DBCP,Druid,Hikari

(1)Hikari是Spring-Boot默认的数据库连接池

(2)Druid

如果想要把连接池更改成Druid,只需要添加相关的依赖就可以了.

 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid-spring-boot-3-starter</artifactId>
     <version>1.2.21</version>
 </dependency>

7,进阶标签

(1),<if>标签:

//SQL

insert into user_info (username,`password`,age,gender)
values ('test','1111',12,1);

insert into user_info (username,`password`,age)
values ('test','1111',12);

insert into user_info (username,`password`,age,gender)
values ('test','1111',12,NULL);

想要实现,有的参数可填可不填的情况,就可以使用if标签,这样就可以对SQL语句进行拼接

<insert id="insertUserByCondition">
        insert into user_info(username,`password`,age
        <if test="gender!=null">
            , gender
        </if>
        )
        values (#{username},#{password},#{age}
        <if test="gender!=null">
            , #{gender}
        </if>
        )
    </insert>

(2),<trim>标签

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

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

suffixOverrides:表⽰整个语句块要去除掉的前缀

prefixOverrides:表⽰整个语句块要去除掉的后缀

 Integer insertUserByCondition(UserInfo userInfo);
<insert id="insertUserByCondition">
        insert into user_info
        <trim  prefix="(" suffix=")" suffixOverrides="," >
            <if test="username!=null">
                username,
            </if>
            <if test="password!=null">
                `password`,
            </if>
            <if test="age!=null">
                age,
            </if>
            <if test="gender!=null">
                gender
            </if>
        </trim>
        values
        <trim suffixOverrides="," prefix="(" suffix=")">
            <if test="username!=null">
                 #{username},
            </if>
            <if test="password!=null">
                #{password} ,
            </if>
            <if test="age!=null">
                 #{age},
            </if>
            <if test="gender!=null">
                 #{gender}
            </if>
        </trim>
    </insert>

(3),<where>标签

当where后面没有其他语句的时候,,<where>标签就可以将where也自动去掉

 List<UserInfo> selectUserByCondition(UserInfo userInfo);
//法一:

<select id="selectUserByCondition" resultType="com.ABdolphin.mybatis.demo.model.UserInfo">
        select * from user_info where
        1=1
        <trim suffixOverrides="and">
            <if test="age!=null">
                and age=#{age}
            </if>
            <if test="deleteFlag!=null">
                and delete_flag=#{deleteFlag}
            </if>
        </trim>
    </select>

//法二:

<select id="selectUserByCondition" resultType="com.ABdolphin.mybatis.demo.model.UserInfo">
        select * from user_info
        <where>
            <trim suffixOverrides="and">
                <if test="age!=null">
                     age=#{age} and
                </if>
                <if test="deleteFlag!=null">
                     delete_flag=#{deleteFlag}
                </if>
            </trim>
        </where>

    </select>

(4),<set>标签

添加set关键字,去除末尾的逗号

 Integer updateUserByCondition(UserInfo userInfo);
<update id="updateUserByCondition">
        update user_info
        <set>
            <if test="username!=null">
                username=#{username},
            </if>
            <if test="password!=null">
                `password`=#{password},
            </if>
            <if test="gender!=null">
                gender=#{gender}
            </if>
        </set>
        <where>
            id=#{id}
        </where>
    </update>

(5),<foreach>标签

可用于批量删除

Integer deleteUserByCondition(List<Integer> ids);
<delete id="deleteUserByCondition">
        delete from user_info where id in
        <foreach collection="ids" separator="," item="id" open="(" close=")">
            #{id}
        </foreach>
 </delete>

(6),<include>标签

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into user_info(username,`password`,age,gender)
        values (#{username},#{password},#{age},#{gender})
    </insert>

当我们有很多共性代码的时候,就可以将这样的代码打包,之后复用的时候用include标签进行复用,上述代码可以写成以下代码

<sql id="inserCol">
        insert into user_info(username,`password`,age,gender)
    </sql>
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        <include refid="inserCol"></include>
        values (#{username},#{password},#{age},#{gender})
    </insert>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值