mybatis04-mybatis缓存、分页插件、注解开发(一对一、多对一、多对多)

mybatis04

mybatis 缓存

一、mybatis 缓存概述

1、缓存

​ 缓存 是存在于内存中的临时数据,使用缓存的目的是:减少和数据库的交互次数,提高执行效率。

2、mybatis 缓存

​ mybatis 与 大多数的持久层框架一样,提供了缓存策略,通过策略减少数据库的查询次数,从而提高性能。

3、mybatis 缓存分类
  • 一级缓存
  • 二级缓存

二、一级缓存

1、一级缓存介绍
(1)描述

​ mybatis 一级缓存,是一种 session 级别的,针对同一个会话SqlSession中,执行多次条件完全相同的同一个SQL,那么会共享这一个缓存。

(2)特点
  • 自带的,不能卸载
  • SQLSession 级别的,使用无需配置
2、一级缓存结构图

在这里插入图片描述

3、一级缓存示例代码
(1)项目结构和pom.xml、jdbc.properties、mybatisConfig.xml文件与mybatis03一样
(2)持久层接口 StudentMapper.java
package com.etime.mapper;

import com.etime.pojo.Student;

import java.util.List;

public interface StudentMapper {
    List<Student> getAllStudent();
}
(3)持久层接口映射文件 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 namespace="com.etime.mapper.StudentMapper">

 <select id="getAllStudent" resultType="Student">
     SELECT * FROM student
 </select>

</mapper>
(4)在mybatisConfig.xml文件中设置setting
<settings>
    <!--日志打印-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • 注意setting标签的位置
(5)编写测试方法
 @Test
 public void t01() {
     SqlSession sqlSession = SqlSessionUtil.getSqlSession();
     StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
     List<Student> list1 = studentMapper.getAllStudent();
     list1.forEach(System.out::println);
     System.out.println("----------------------------------");
     List<Student> list2 = studentMapper.getAllStudent();
     list2.forEach(System.out::println);
     sqlSession.close();
 }

在这里插入图片描述

4、一级缓存分析

​ 从上面的代码可以出,我们写了两次查询操作,但在访问数据时,只有一次。

​ 第一次先从一级缓存中获取,因为session是新创建的,一级缓存中没有数据,于是就查询数据获取数据,然后把查询的数据放到一级缓存中,此时一定要注意的是,一级缓存是一个Map集合,map的key是你的查询条件字符串,值就是查询出来的对象。

​ 第二次查询时,先从一缓存中获取,因为上一次查询后已经放到一级缓存中了,所以从一级缓存中获取到了,就不用访问数据库了,减少和数据次的一次交互,提高了执行效率。

5、一级缓存的清空
  • 代码
@Test
public void test01(){
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = studentMapper.getAllStudent();
    // 清理以及缓存
    sqlSession.clearCache();
    list.forEach(System.out::println);
    System.out.println("--------------------------");
    List<Student> list2 = studentMapper.getAllStudent();
    list2.forEach(System.out::println);
    sqlSession.close();
}
  • 结果
    在这里插入图片描述
6、总结一级缓存清空三种方式
  • clearCache();
  • 执行数据库的操作:delete、insert、update;
  • 手动提交事务:commit();
    • 对于查询来说,事务可以不提交。故使用该方式需考虑情况

三、二级缓存

1、二级缓存介绍
(1)描述

​ 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个
SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

(2)特点
  • 默认不开启
  • 使用需配置
2、二级缓存结构图

在这里插入图片描述

3、二级缓存的开启与关闭
(1)在mybatisConfig.xml文件中开启二级缓存
<settings>
    <!--日志打印-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • cacheEnabled
    • true:默认值,开启
    • false:关闭
(2)配置映射文件 StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--
引入dtd约束-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 是当前mapper对应的接口-->
<mapper namespace="com.etime.mapper.StudentMapper">
    <cache></cache>
    <select id="getAllStudent" resultType="Student">
        select * from student
    </select>
</mapper>
  • 注意:cache标签的位置
(3)在映射文件 StudentMapper.xml 配置statement 上面的 userCache属性
<?xml version="1.0" encoding="UTF-8" ?>
<!--
引入dtd约束-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 是当前mapper对应的接口-->
<mapper namespace="com.etime.mapper.StudentMapper">
    <cache></cache>
    <select id="getAllStudent" resultType="Student" useCache="true">
        select * from student
    </select>
</mapper>
  • useCache
    • true:开启
    • false:关闭
  • 注意:针对每次查询都需要最新的数据sql,要禁用二级缓存
(4)二级缓存测试
a.实体类需要序列化
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student implements Serializable {

    private int sid;
    private String sname;
    private int cid;
}
b.测试方法
@Test
public void test01(){
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = studentMapper.getAllStudent();
    sqlSession.clearCache();
    list.forEach(System.out::println);
    System.out.println("--------------------------");
    List<Student> list2 = studentMapper.getAllStudent();
    list2.forEach(System.out::println);
    sqlSession.close();
}

在这里插入图片描述

4、二级缓存分析

​ 从结果可以看出,第一次查询将所有学生信息存入一级缓存,然后存入二级缓存中。

​ 第二次查询,我们现将一级缓存清除,再进行查询发现是从二级缓存取出的学生信息,但它又从数据库中查询数据。

​ 可以得出结论,在没有清除一级缓存时,我们是从二级缓存中取得的数据。

mybatis 分页插件

一、什么是分页

​ 分页是将所有数据分段展示各用户的技术,用户所看到的数据只是一部分。若用户没有想要的内容,可通过指定页面、翻页的方式转换内容。

二、分页的好处

1、提高性能

​ 数据量很大,若一次性查出,浪费内存 ,降低了效率。

2、展现层面的考虑

​ 不好排版,不美观

三、分页插件的使用

1、引入依赖,在pom.xml文件中
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
2、在mybatisConfig.xml配置文件中 配置分页
  • 注意:配置分页的位置在environments标签之前
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
3、在接口 StudentMapper.java 中定义方法
package com.etime.mapper;

import com.etime.pojo.Student;

import java.util.List;

public interface StudentMapper {
    List<Student> getAllStudent();
}
4、 在映射文件 StudentMapper.xml 配置信息
<?xml version="1.0" encoding="UTF-8" ?>
<!--
引入dtd约束-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 是当前mapper对应的接口-->
<mapper namespace="com.etime.mapper.StudentMapper">
    <cache></cache>
    <select id="getAllStudent" resultType="Student" useCache="true">
        select * from student
    </select>
</mapper>
5、测试
@Test
public void test02() {
    // 设置当前页码及每页显示条数 必须在获得sqlSession对象的前面设置
    PageHelper.startPage(1,2);
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = studentMapper.getAllStudent();
    // 从查到的数据 取出当前页数据,生成pageInfo对象
    PageInfo<Student> pageInfo = new PageInfo<>(list);
    // 从pageInfo对象中获取当页数据
    List<Student> pageList = pageInfo.getList();
    System.out.println("当前页数据");
    pageList.forEach(System.out::println);
    // 获取总数据条数
    long total = pageInfo.getTotal();
    System.out.println("总数据条数 = " + total);
    // 获取总页数
    int pages = pageInfo.getPages();
    System.out.println("总页数 = " + pages);
    sqlSession.close();
}

mybatis 注解开发

一、mybatis 注解开发概述

​ 注解提供了一种简单的方式来实现 简单映射语句,而不糊引入大量的开销。

​ 能够读懂别人的代码,特别是框架相关的代码。

​ 本来是可能需要很多配置文件,需要很多逻辑才能实现事务使用一个或多个注解来替代,这样就使得编程更加简洁,代码更加清晰。

二、mybatis 注解介绍

注解描述
@Insert新增
@Update更新
@Delete删除
@Select查询
@Result结果封装集
@Results与@Result使用,封装多个结果集
@ResultMap引用@Results定义的封装
@One一对一结果封装集
@Many一对多结果哦封装集
@SelectProvider动态SQL映射
@CacheNamespace注解二级缓存的使用

三、注解实现基本增删改查

1、实体类
  • 学生类
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {

    private int sid;
    private String sname;
    private int cid;
}
2、在接口 StudentMapper中定义方法并使用注解
  • mybatisConfig.xml 中映射文件的注册信息不变
  • 将resource资源包中的 映射文件给删除
package com.etime.mapper;

import com.etime.pojo.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface StudentMapper {

    @Select("select * from student")
    List<Student> getAllStudent();
    @Insert("insert into student(sname,cid) values(#{sname},#{cid})")
    int addStudent(Student student);
    @Update("update student set sname=#{sname},cid=#{cid} where sid=#{sid}")
    int updateStudent(Student student);
    @Delete("delete from student where sid=#{sid}")
    int deleteStudentBySid(int sid);
}
3、测试
package com.etime.test;

import com.etime.mapper.StudentMapper;
import com.etime.pojo.Student;
import com.etime.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class StudentTest {

    @Test
    public void test01(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> list = mapper.getAllStudent();
        list.forEach(System.out::println);
        sqlSession.close();
    }

    @Test
    public void test02() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int res = mapper.addStudent(new Student(0, "胡神哎", 1));
        System.out.println("res = " + res);
        sqlSession.close();
    }

    @Test
    public void test03() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int res = mapper.updateStudent(new Student(8, "胡-寂", 1));
        System.out.println("res = " + res);
        sqlSession.close();
    }

    @Test
    public void test04() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int res = mapper.deleteStudentBySid(8);
        System.out.println("res = " + res);
        sqlSession.close();
    }
}

四、复杂关系的注解

1、注解介绍
(1)@Results注解
  • 代替标签:
  • 使用示例:@Results({@Result(),@Result()})或者 @Results(@Result())
  • 使用说明:该注解中可以使用单个 @Result,或 @Result集合
(2)@Result注解
  • 代替标签:、
  • @Result 属性介绍
    • id:是否 是主键字段
    • properties:需要配置实体类的属性
    • column:数据库表的列名
    • one:需使用 @One 注解
      • 例:@Result(one=@One)
    • many:需使用 @Many 注解
      • 例:@Result(many=@Many)
(3)@One注解
  • 描述:一对一,是多表插叙的关键,用来指定子查询返回单一对象。
  • 代替标签:
  • @One 属性介绍
    • select:指定多表查询的 sqlMaper。使用完全限定名确定方法的位置。
  • 示例:@Result(column=" “,property=”“,one=@One(select=”"))
(4)@Many 注解
  • 描述:多对一,是多表查询的关键,指定子查询返回对象集合
  • 代替标签:
  • 注意:聚集元素用来处理“一对多”的关系。需要执行映射的java实体类的属性,属性的javaType。
  • 示例:@Result(property=“”,column=“”,many=@Many(select=“”,javaType=“”))
2、一对一关系注解
(1)实体类
  • 以妻子表 和 丈夫表为例

  • 妻子类

package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Wife {

    private int wid;
    private String wname;
}
  • 丈夫类(主体)
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Husband {

    private int hid;
    private String hname;
    private int wid;
    private Wife wife;
}
(2)在接口 HusbandMapper.java 中使用注解方法
package com.etime.mapper;

import com.etime.pojo.Husband;
import com.etime.pojo.Wife;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface HusbandMapper {

    // 查询所有丈夫中,对应一对一的妻子
    @Select("select * from husband")
    @Results({
            @Result(property = "hid",column = "hid"),
            @Result(property = "hname",column = "hname"),
            @Result(property = "wid",column = "wid"),
            /*
                javaType:指定类型
                column:指定传入那列 为参数
                one:一对一,返回一个对象
                select:子查询的方法的位置
             */
            @Result(property = "wife",javaType = Wife.class,column = "wid",
            one = @One(select = "com.etime.mapper.WifeMapper.getWifeByWid"))
    })
    List<Husband> getAllHusband();
}
(3)在接口 WifeMapper.java 中使用注解方法,定义子查询
package com.etime.mapper;

import com.etime.pojo.Wife;
import org.apache.ibatis.annotations.Select;

public interface WifeMapper {

    @Select("select * from wife where wid=#{wid}")
    Wife getWifeByWid(int wid);
}
(4)测试
package com.etime.test;

import com.etime.mapper.HusbandMapper;
import com.etime.pojo.Husband;
import com.etime.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class HusbandTest {

    @Test
    public void test01(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        HusbandMapper mapper = sqlSession.getMapper(HusbandMapper.class);
        List<Husband> list = mapper.getAllHusband();
        list.forEach(System.out::println);
        sqlSession.close();
    }
}
3、多对一关系注解
  • 以学生和班级为例
多对一
(1) 实体类
  • 班级类
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Classes {

    private int cid;
    private String cname;

}
  • 学生类(主体)
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {

    private int sid;
    private String sname;
    private int cid;
    private Classes classes;
}
(2)在接口StudentMapper.java中使用注解方法
@Select("select * from student")
@Results({
        @Result(property = "sid",column = "sid"),
        @Result(property = "sname",column = "sname"),
        @Result(property="cid",column = "cid"),
        @Result(property = "classes",javaType = Classes.class,column = "cid",
        one = @One(select = "com.etime.mapper.ClassesMapper.getClassesByCid"))
})
List<Student> getAllStudentAndClasses();
(3)在接口ClassesMapper.java中使用注解方法
@Select("select * from classes where cid=#{cid}")
Classes getClassesByCid(int cid);
(4)测试
@Test
public void test05() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = mapper.getAllStudentAndClasses();
    list.forEach(System.out::println);
    sqlSession.close();
}
一对多
(1) 实体类
  • 学生类
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {

    private int sid;
    private String sname;
    private int cid;
}
  • 班级类(主体)
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Classes {

    private int cid;
    private String cname;
    private List<Student> studentList;
}
(2)在接口ClassesMapper.java中使用注解方法
package com.etime.mapper;

import com.etime.pojo.Classes;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface ClassesMapper {

    @Select("select * from classes where cid=#{cid}")
    Classes getClassesByCid(int cid);

    @Select("select * from classes")
    @Results({
            @Result(property = "cid",column = "cid"),
            @Result(property = "cname",column = "cname"),
            @Result(property = "studentList",javaType = List.class,column = "cid",
            many = @Many(select = "com.etime.mapper.StudentMapper.getStudentByCid"))
    })
    List<Classes> getAllClassesAndStudent();
}
(3)在接口StudentMapper.java中使用注解方法
@Select("select * from student where cid=#{cid}")
List<Student> getStudentByCid(int cid);
(4)测试
package com.etime.test;

import com.etime.mapper.ClassesMapper;
import com.etime.pojo.Classes;
import com.etime.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class ClassesTest {

    @Test
    public void test01(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        ClassesMapper mapper = sqlSession.getMapper(ClassesMapper.class);
        List<Classes> list = mapper.getAllClassesAndStudent();
        list.forEach(System.out::println);
        sqlSession.close();
    }
}
4、多对多关系注解
(1)实体类
  • 以 学生、课程、中间表为例

  • 课程类

package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Course {

    private int courseid;
    private String coursename;
}
  • 中间表类(第二主体)
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class StudentCourse {

    private int scid;
    private int sid;
    private int courseid;
    private Course course;
}
  • 学生类(第一主体)
package com.etime.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {

    private int sid;
    private String sname;
    private int cid;
    private List<StudentCourse> studentCourseList;
}
(2)在接口 StudentMapper.java中 编写使用注解方法
@Select("select * from student")
@Results({
        @Result(property = "sid",column = "sid"),
        @Result(property = "sname",column = "sname"),
        @Result(property = "cid",column = "cid"),
        @Result(property = "studentCourseList",javaType = List.class,column = "sid",
        many = @Many(select = "com.etime.mapper.StudentCourseMapper.getStudentCourseBySid"))
})
List<Student> getStudentAndCourse();
(3)在接口 StudentCourseMapper.java中 编写使用注解方法
package com.etime.mapper;

import com.etime.pojo.Course;
import com.etime.pojo.StudentCourse;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface StudentCourseMapper {

    @Select("select * from studentcourse where sid=#{sid}")
    @Results({
            @Result(property = "scid",column = "scid"),
            @Result(property = "sid",column = "sid"),
            @Result(property = "courseid",column = "courseid"),
            @Result(property = "course",javaType = Course.class,column = "sid",
            one = @One(select = "com.etime.mapper.CourseMapper.getCourseByCid"))
    })
    List<StudentCourse> getStudentCourseBySid(int sid);
}
(4)在接口 CourseMapper.java中 编写使用注解方法
package com.etime.mapper;

import com.etime.pojo.Course;
import org.apache.ibatis.annotations.Select;

public interface CourseMapper {

    @Select("select * from course where cid=#{cid}")
    Course getCourseByCid(int cid);
}
(5)测试
@Test
public void test06() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = mapper.getAllStudentAndClasses();
    list.forEach(System.out::println);
    sqlSession.close();
}
(4)在接口 CourseMapper.java中 编写使用注解方法
package com.etime.mapper;

import com.etime.pojo.Course;
import org.apache.ibatis.annotations.Select;

public interface CourseMapper {

    @Select("select * from course where cid=#{cid}")
    Course getCourseByCid(int cid);
}
(5)测试
@Test
public void test06() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = mapper.getAllStudentAndClasses();
    list.forEach(System.out::println);
    sqlSession.close();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咸鱼不咸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值