Mybatis学习记录

本文深入介绍了MyBatis持久层框架的使用,包括配置文件解析、日志实现、CRUD操作、结果映射、日志配置和分页处理。重点讲解了动态SQL的IF、Foreach标签以及缓存机制,揭示了MyBatis如何通过XML或注解简化数据库操作,并提供了实际的代码示例,展示了如何在SpringBoot中集成MyBatis。
摘要由CSDN通过智能技术生成

1、简介

1.1、什么是MyBatis?

  • MyBatis 是一款优秀的持久层框架
  • 它支持定制化 SQL、存储过程以及高级映射。
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

1.2、持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转变的过程。

瞬时状态的数据是存储在内存中的,内存具有断电即失的特性,所以我们要将之持久化

1.3、持久层

Dao层,Service层,Controller层。。。

持久层:完成持久化工作的代码块

2、第一个MyBatis程序

与springboot使用的Mybatis需要写以下文件才能使用

  • 编写MyBatis的核心配置文件
  • 编写Mybatis的工具类

实现如下:

    @Test
    public void test() {
        
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();

        System.out.println(userList);

    }

3、CRUD

3.1、namespace

namespace中的包名和mapper中的接口类一致

3.2、insert

插入用户的时候关于ID的设置:由于id是自动增长的所以在新增的时候我们不能给其指定id

解决办法:mapper新增的时候id=0,后台新增的时候就会自动赋值+1的值

4、配置解析

4.1、核心配置文件

Mybatis-config.xml

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

4.2、环境配置

mybatis可以适配成多种环境

但是每个SqlSessionFactory实例只能选择一种环境(根据配置文件中的elements中的dafult进行切换)

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>
  
  
  <environment id="test">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>
</environments>

mybatis默认的事务管理器是JDBC,连接池是POOLED

4.3、属性(properties)

我们可以通过properties属性实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(db.peoperties)

创建一个配置文件(db.peoperties)

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatisStudy?useUnicode=true&characterEncoding=UTF-8&;useJDBCCompliantTimezoneShift=true&;useLegacyDatetimeCode=false&;serverTimezone=Asia/Shanghai&;allowMultiQueries=true
username = root
password = 123123123

将之引用到配置文件中去

    <!--引入外部配置文件   优先级:优先使用外部配置文件-->
    <properties resource="db.properties" />
    
    <!--或者另一种用法,外部配置文件的优先级是更高的-->
    <properties resource="db.properties">
    	<property>name="username" value="root"</property>
    	<property>name="pwd" value="111"</property>
    </peoperties>

4.4、起别名

    <!--给实体类起别名:方式一-->
    <typeAliases>
        <typeAlias type="com.psy.pojo.User" alias="User"/>
    </typeAliases>
        <!--给实体类起别名:方式二->扫描包下的实体类是否有相关的别名注解,如果没有的话该别名为小写-->
    <typeAliases>
        <package name="com.psy.pojo"/>
    </typeAliases>

4.5、Mapper映射器

注册并绑定我们的Mapper文件

<!--方式1-->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

方式二的注意点:

  • 接口必须和他的Mapper文件同名
  • 必须在一个包下面
<!--方式二-->
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

方式三的注意点和方式二的注意点一样

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

4.6、生命周期和作用域

生命周期和作用域是至关重要的,因为错误会导致非常严重的并发问题。

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory,就不再需要他了
  • 局部变量

SqlSessionFactory:

  • SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,不能丢弃或者重新创建另一个实例
  • 因此他的最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式

SqlSession

  • 连接到连接池的一个请求
  • SqlSession的实例不是线程安全的,因为不能被共享的,所以他的最佳作用域是请求或方法作用域
  • 用完之后赶紧关闭,否则资源被占用!

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

结果映射集

// 实体类属性
id username password
// 数据库字段
id username pwd

解决办法1:在查询语句中使用as

select id,username,pwd as password from users

解决办法2:将resultType替换为resultMap,此外需要写一个结果映射集

    <resultMap id="UserMap" type="User">
        <result column="id" property="id" />
        <result column="username" property="username" />
        <!--假设数据库中的密码字段名为pwd则column中的值为pwd,此外我们只需要映射字段名不匹配的就行-->
        <result column="password" property="password" />
    </resultMap>

    <select id="getUserList" resultMap="UserMap">
        select * from mybatisStudy.users
    </select>

resultType的话会自动生成一个映射但是那个映射是基于实体类的,而且column和property的值是一样的

6、日志

6.1、日志工厂

数据库出现异常的情况下我们可以通过日志去排错。

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING :控制台输出
  • STDOUT_LOGGING 【掌握】:标准日志输出
  • NO_LOGGING:没有日志输出

具体使用哪个日志由设置(没有默认值)决定。

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

6.2、log4j

什么事log4j?

  • Log4j是Apache的一个开源项目
  • 我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别(error、debug等等),我们能够更加细致地控制日志的生成过程
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

1、导包

2、log4j.properties

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=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.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{yy-MM-dd}][%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、配置log4j为日志实现

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

4、log4j的使用

public class UserDaoTest{
  // 使用的话必须获得此类的反射
  static Logger logger = Logger.getLogger(UserDaoTest.class);
  @Test
  public void testLog4j(){
    logger.info("info:进入了testLog4j");
    logger.info("debug:进入了testLog4j");
    logger.info("error:进入了testLog4j");
  }
}

7、分页

8、使用注解开发

package com.psy.dao;

import com.psy.pojo.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {
    @Select("select * from mybatisStudy.users")
    List<User> getUsers();
}

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>
    <!--引入外部配置文件   优先级:优先使用外部配置文件-->
    <properties resource="db.properties" />
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <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>
    </environments>

    <!--绑定接口-->
    <mappers>
        <mapper class="com.psy.dao.UserMapper"/>
    </mappers>
</configuration>

9、Mybatis的执行流程

在这里插入图片描述

10、多对一处理

多对一:多个学生对应一个老师,即多个学生关联一个老师(关联

一对多:一个老师对应多个学生,即一个老师有个学生集合(集合

// 学生类
class Student{
  private int id;
  private String name;
  private Teacher teacher;
}
class Teacher{
  private int id;
  private String name;
}
/**
* 实体类对应的数据库表
* 学生表字段:id,name,tid(老师的id)
* 教师表字段: id,name
*/

//接口类
public interface StudentMapper {
    public List<Student> getStudent();

    public List<Student> getStudent2();
}

如何将数据库中的Student(Teacher以JavaBean的形式)查询出来呢?

1、使用查询的嵌套处理

<?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.psy.dao.StudentMapper">
    <resultMap id="Student" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--由于最后是一个属性是Teacher对象,类似以上的写法是行不通的,所以我们要使用association-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher" />
    </resultMap>
    <select id="getStudent" resultMap="Student">
        select * from mybatisStudy.student
    </select>
    <select id="getTeacher" resultType="Teacher">
        select * from mybatisStudy.teacher where id = #{id}
    </select>
</mapper>

查询的结果如下:

Student(id=1, name=邹嘉懿, teacher=Teacher(id=1, name=洪文轩))
Student(id=2, name=蔡博文, teacher=Teacher(id=1, name=洪文轩))
Student(id=3, name=覃明哲, teacher=Teacher(id=1, name=洪文轩))
Student(id=4, name=覃绍辉, teacher=Teacher(id=1, name=洪文轩))

2、按照结果嵌套处理

    <select id="getStudent2" resultMap="Student2">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from mybatisStudy.student s, mybatisStudy.teacher t
        where s.tid = t.id
    </select>
    <resultMap id="Student2" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>

查询结果如下:

Student(id=1, name=邹嘉懿, teacher=Teacher(id=1, name=洪文轩))
Student(id=2, name=蔡博文, teacher=Teacher(id=1, name=洪文轩))
Student(id=3, name=覃明哲, teacher=Teacher(id=1, name=洪文轩))
Student(id=4, name=覃绍辉, teacher=Teacher(id=1, name=洪文轩))
Student(id=5, name=邵昊天, teacher=Teacher(id=2, name=冯思远))
Student(id=6, name=黎晓啸, teacher=Teacher(id=2, name=冯思远))
Student(id=7, name=金凯瑞, teacher=Teacher(id=2, name=冯思远))
Student(id=8, name=沈雪松, teacher=Teacher(id=2, name=冯思远))
Student(id=9, name=夏煜祺, teacher=Teacher(id=3, name=江浩轩))
Student(id=10, name=贾擎宇, teacher=Teacher(id=3, name=江浩轩))

11、一对多处理

实体类

public class Teacher {
    private int id;
    private String name;
  	// 一对多
    private List<Student> students;
}
public class Student {
    private int id;
    private String name;
    private int tid;
}

// 接口类
public interface TeacherMapper {
		//List<Teacher> getTeacher();

    //获取指定老师下的所有学生及老师的信息
    Teacher getTeacher2(@Param("tid") int id);

    Teacher getTeacher3(@Param("tid") int id);
}

1、使用查询嵌套处理

    <!--按照查询嵌套进行查询-->
    <select id="getTeacher3" resultMap="TeacherStudent3">
        SELECT * from mybatisStudy.teacher where id=#{tid}
    </select>
    <resultMap id="TeacherStudent3" type="Teacher">
        <collection property="students" javaType="Arraylist" ofType="com.psy.pojo.Student" select="getStudentByTeacherId" column="id"/>
    </resultMap>
    <!--这里的id是通过resultMap中返回的Teacher对象中的id来查询的,通过column进行传递-->
    <select id="getStudentByTeacherId" resultType="Student">
        SELECT * FROM mybatisStudy.student WHERE tid=#{id}
    </select>

2、使用结果嵌套处理

    <!--按照结果进行嵌套查询-->
    <select id="getTeacher2" resultMap="TeacherStudent">
        SELECT s.id sid,s.`name` sname,t.`name` tname,t.id tid
        FROM mybatisStudy.student s,mybatisStudy.teacher t
        where s.tid=t.id and t.id=1
    </select>
    <resultMap id="TeacherStudent" type="com.psy.pojo.Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!--
            javaType:指定属性的类型
            集合List中的泛型信息,我们使用ofType获取
        -->
        <collection property="students" ofType="com.psy.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

面试高频:

  • MySQL引擎
  • INNODB底层原理
  • 索引
  • 索引优化

12、动态SQL

什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句

环境搭建:

//实体类
@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime; //与数据库中的字段不一致-->解决办法:在config文件中开启转驼峰命名法<setting name="mapUnderscoreToCamelCase" value="true"/>
    private int views;
}


//获取ID的工具类
public class IDUtils {
    public static String getId(){
        return UUID.randomUUID().toString().replaceAll("-","");

    }
}

//接口类和xml文件正常写一个增加的接口就行

//测试
    @Test
    public void addBlogTest() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog();
        blog.setId(IDUtils.getId());
        blog.setTitle("Mybatis");
        blog.setAuthor("狂神说");
        blog.setCreateTime(new Date());
        blog.setViews(9999);

        mapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("Java");
        mapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("Spring");
        mapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("微服务");
        mapper.addBlog(blog);

        sqlSession.close();
    }

12.1、动态SQL之IF

    <select id="queryBlogIF" parameterType="map" resultType="blog">
      <!--where 1=1 这样编写是不正规的    正确的写法是使用<where>标签-->
      <!--where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。-->
        select * from mybatisStudy.blog where 1=1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>

<!--正规写法-->
    <select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatisStudy.blog
        <where>
          <if test="title != null">
            and title = #{title}
        	</if>
        	<if test="author != null">
            and author = #{author}
        	</if>
      	</where>
    </select>

12.2、动态SQL之常用标签

choose、when、otherwise

类似于Java中的switch case

    <select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from mybatisStudy.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

Set: set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

    <update id="updateBlog" parameterType="map">
        update mybatisStudy.blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="title != null">
                author = #{author},
            </if>
        </set>
        where id = #{id}
    </update>

trim:定制化的覆盖前缀和后缀,where和set的定制版

12.3、动态SQL之Foreach

SQL片段

<!--将重复的代码抽取出来-->
		<sql id="if-title-author">
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="title != null">
            and author = #{author}
        </if>
    </sql>

<!--在需要使用的时候,使用include进行引入-->
    <select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatisStudy.blog
        <where>
            <include refid="if-title-author">
            </include>
        </where>
    </select>

使用SQL片段的注意事项:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签

Foreach

对集合进行遍历(尤其是在构建 IN 条件语句的时候)

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

Item(集合项)index(索引:相当于数组的下标)list(传进来的集合名)

此外,foreach还允许我们设置语句的开头、结尾和间隔符。类似trim

13、缓存

13.1、简介

网页中大部分的操作都是读操作,也就是查询。
但是每次查询都要连接数据库,连接数据库有很耗费资源。那么,我们就将一次查询的结果暂时存起来—>内存:缓存
当我们要再次查询该数据的时候,我们直接从内存里面将值拿出来即可。

1、什么是缓存(Cache)?

  • 存在内存中的数据
  • 用户从内存中拿数据比从磁盘上拿数据要高效的多,提高了效率,解决了高并发系统的性能问题

2、为什么使用缓存?

  • 减少和数据库交互的次数,减少系统开销,提高系统效率

3、适合放到缓存中的数据?

  • 经常查询且不经常发生改变的数据

13.2、Mybatis缓存

  • 默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,开启一级缓存。(SqlSession级别的缓存,也称本地缓存)
    • 二级缓存需要手动开启和配置,是基于namespace级别的缓存
    • 通过Mybatis定义的缓存接口Cache来自定义二级缓存
  • 缓存有四种清除策略:

    • LRU
    • FIFO
    • SOFT(软引用)
    • WEAK(弱引用)
一级缓存:
    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        User user2 = mapper.queryUserById(1);
        User user3 = mapper.queryUserById(2);
        System.out.println(user);
        System.out.println("user2"+user2);
        System.out.println("user3"+user3);
    }
// 打印出来的日志

Created connection 296347592.
==>  Preparing: select * from mybatisStudy.users where id = ?
==> Parameters: 1(Integer)
<==    Columns: id, username, password
<==        Row: 1, 郑聪健, 92394
<==      Total: 1
==>  Preparing: select * from mybatisStudy.users where id = ?
==> Parameters: 2(Integer)
<==    Columns: id, username, password
<==        Row: 2, 张聪健, 9435114
<==      Total: 1
User(id=1, username=郑聪健, password=92394)
user2User(id=1, username=郑聪健, password=92394)
user3User(id=2, username=张聪健, password=9435114)

总结:对于相同的查询语句,第一次查询会连接数据库进行查询。第一次查询完成之后,会把结果缓存到内存中。之后的查询只要是和本语句一样就会从缓存里面查询结果。这就是一级查询(SQLSession级别的缓存,一旦SQLSession关闭了,缓存就没了)。

一级缓存几种情况会导致缓存失效:

1、增删改语句会刷新缓存

2、达到了缓存清除算法的某种条件,会失效

3、缓存会不定时的刷新

4、查询不同的mapper文件

5.手动清除

二级缓存:
  • 二级缓存也叫做全局缓存
  • 基于namespace
  • 工作机制:
    • 一个会话查询一条数据,这个数据会被放在当前会话的一级缓存中
    • 会话关闭后,一级缓存中的数据会被放到二级缓存中
    • 新建的会话查询信息会从二级缓存中获取内容
    • 不同的mapper会把数据放到自己对应的二级缓存中
<!--在配置文件中开启二级缓存-->
<setting name="cacheEnabled" value="true"/>

<!--mapper的xml文件中开启缓存-->
<cache/>

总结:不同SQLSession使用同一个Mapper文件中的SQL查询数据的话,会使用到二级缓存。

测试时候出现的问题:我们需要将实例类序列化,否则会报错!

package com.psy.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
    private String id;
    private String username;
    private String password;
}
缓存原理:

缓存顺序:二级缓存->一级缓存->数据库查询

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值