孟凡的博客blog.mengfanyaoapp.com
课程资料库›Spring + SpringMVC + MyBatis›第33次 · 分页与服务层封装
模块总览

第33次 · 分页与服务层封装

2026-10-01 · Spring + SpringMVC + MyBatis · 课程笔记
课堂练习资料 33-分页与服务层封装.zip
下载 ZIP 压缩包

第33次 · 分页与服务层封装

第1/7页 · 先知道今天要做什么

先从一个熟悉的场景想一想:这节课的代码在帮我们解决什么问题?

先来一个生活里的画面

图书馆有5本书,每张登记页只能写2本:第1页2本,第2页2本,第3页1本。total=5 是全部数量,records 是当前这页;拿到第2页仍有2本,不表示全部只有2本。

你已经会的知识

用前面课的类、方法、参数和返回值来读本次例子。遇到新名字先看下一页;不用一开始读完整个工程。

为什么要学这一课

列表数据多时每次只查一页。原chapter12测试只有普通查询,第5页新增pagePractice并安装分页插件,再观察total/current/records。

今天完成的事情

给原测试工程补上分页插件,再看页码、总数与本页记录。

这课在哪里做

要找什么 真实位置
下载后打开 04_源代码/chapter12/pom.xml
本次先读 src/test/java/MyBatisPlusTest.java
运行或请求入口 src/test/java/MyBatisPlusTest.java#pagePractice

看到什么才算完成一小步

  1. 完成pagePractice与插件:日志确认分页SQL,再检查total/current/records。
  2. 页码改2:数据足够时出现另一页,数据不足可能为空。
  3. 只创建Page没有分页插件:不能据此宣称SQL已分页,须检查日志和真实条数。

阅读顺序

第2页查新词,第3页读这一份工程的代码,第4页先找文件和入口。第5页在 IDEA 按步骤修改;第6页对照效果和问题;第7页用自己的话复盘。

操作位置

下载、解压、打开、修改和运行在本机 IDEA;教学网站用于看讲义、下载资源、保存草稿和提交。先请教师提供本章的独立练习数据库、已初始化的表和连接参数。按指定配置文件填写本地连接;配置未就绪时先停在这一步。读到 SQLException 不等于 Java 写错。只在自己的教学库做新增、修改和回滚练习,单独运行指定测试方法,不点整个测试类的 Run All。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)

第2/7页 · 把新词说清楚

先把本课用得到的名字认清;看到名字时回到真实文件定位。

新词与本课含义

名字 在这个例子里是什么意思
分页 每次查询一小部分,本次最多2条。
current/size 当前页与每页条数;Page<>(1,2) 是第1页2条。
total/records 全部匹配数量与当前页数据,两者不同。
分页插件 PaginationInnerInterceptor 在工厂构建前加入,需检查实际分页 SQL。

把生活画面对应到教材代码

生活里的角色或动作 本课真正的代码
第几张登记页 Page 的 current
一页能写几本 size,本课为2
所有书与本页书 total 与 records

类比怎样用

先借这个画面记住分工,再回到本课文件逐项定位。生活里的角色只是类比,程序的实际行为以教材代码、配置和本机观察为准。

先对照文件名

src/test/java/MyBatisPlusTest.java 是本次主阅读文件,位置在 04_源代码/chapter12 内。目录、类、方法和对象登记名是四种不同名字,不要随意互相替换。

读代码时只问三个问题

  1. 它从哪里得到输入:方法参数、XML、请求还是数据库?
  2. 它调用哪个具体方法:到同一个工程中查这个名字。
  3. 结果从哪里看:Run 控制台、请求响应还是数据库?本课第1页检查表给出位置。

先不用背术语

先能找到文件、指出一行的动作并描述输出。术语用来给已经看过的动作命名,不用它替代操作过程。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)

第3/7页 · 先看一个小例子

下面的教材片段来自本课下载项目;新增练习代码会另外标明。不要把节选直接当完整文件覆盖。

先猜一猜,读完代码再回答

第3页只剩1条,应该把 total 改成1吗?

三个具体例子:先给输入,再想结果

输入或改动 应该观察什么 为什么
推演:5条数据,第1页每页2条 total5、当前页2条 总数和本页条数分开计算。
同样数据,第3页 total仍5、当前页1条 最后一页可以未满。
没有分页插件 不能凭 Page 对象就说 SQL 已分页 检查插件配置和数据库实际 SQL。

把例子用起来

这些是课堂推演,帮你看清“只改一个输入,结果怎样变化”。涉及数据库的编号、余额和数量是示意,动手时换成教师提供的真实练习数据;在 IDEA 写下自己的预测,再运行核对。

打开这一份文件

04_源代码/chapter12/src/test/java/MyBatisPlusTest.java。在 IDEA 双击打开;以下只是它的第33—64行,完整类、import 和配置仍保留在项目中。

MyBatisPlusTest.java:先读这一段

04_源代码/chapter12/src/test/java/MyBatisPlusTest.java 第33—64行

    public void selectTest() {
        SqlSession sqlSession = this.createSqlSession();
        // 通过SqlSession获取Mapper
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
        // 使用Mapper进行操作
        Book book = bookMapper.selectById(1);
        System.out.println("----------查询单条图书信息------------");
        System.out.println(book);
        System.out.println("----------查询所有图书信息------------");
        List<Book> bookList = bookMapper.selectList(null);
        System.out.println(bookList);
        //创建条件构造器对象
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        //设置查询条件
        queryWrapper.gt(Book::getPrice, 60)
                .like(Book::getName, "记");
        List<Book> books = bookMapper.selectList(queryWrapper);
        System.out.println("----------查询指定条件的图书信息------------");
        System.out.println(books);
    }
    @Test
    public void insertTest() {
        SqlSession sqlSession = this.createSqlSession();
        // 通过SqlSession获取Mapper
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
        Book book = new Book();
        book.setName("史记");
        book.setPrice(new BigDecimal(88.88));
        //插入Book对象的数据
        bookMapper.insert(book);
        sqlSession.commit();
        System.out.println("----------查询所有图书信息------------");

先看第1个动作

原selectTest是准备参考,分页测试不是下载时就有的。

先看第2个动作

新增Page<>(1,2)表示第1页、最多2条;不是旧讲义的每页5条。

先看第3个动作

PaginationInnerInterceptor必须在工厂构建前加入配置。

先看第4个动作

total是符合条件的总量,records是本页集合;Service类当前仅作结构阅读,实际跑Mapper测试。

createSqlSession 中替换 factory 构建语句

课堂新增或改写:createSqlSession 中替换 factory 构建语句;按第5页指定位置添加,原下载包不含这份改动

com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor plugin =
    new com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor();
plugin.addInnerInterceptor(new com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor(
    com.baomidou.mybatisplus.annotation.DbType.MYSQL));
com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder builder =
    new com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder();
org.apache.ibatis.session.Configuration configuration =
    new com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder(reader).parse();
configuration.addInterceptor(plugin);
SqlSessionFactory sqlMapper = builder.build(configuration);

MyBatisPlusTest 类内新增完整测试方法

课堂新增或改写:MyBatisPlusTest 类内新增完整测试方法;按第5页指定位置添加,原下载包不含这份改动

@org.junit.jupiter.api.Test
public void pagePractice() {
    try (SqlSession session = this.createSqlSession()) {
        BookMapper mapper = session.getMapper(BookMapper.class);
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<Book> page =
            new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(1, 2);
        mapper.selectPage(page, null);
        System.out.println("总数=" + page.getTotal());
        System.out.println("页码=" + page.getCurrent());
        System.out.println("本页条数=" + page.getRecords().size());
        System.out.println(page.getRecords());
    }
}

先读再动手

先在 IDEA 找到以上几行,逐项说出它们做什么。第5页会明确告诉你改哪一行、加到哪个大括号里、运行哪个入口;今天只改那几处。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)

第4/7页 · 一步一步做出来

先认清项目、文件和运行入口;保持同一个例子,再去第5页做个人修改。

本课项目目录

04_源代码/chapter12/pom.xml;选择直接包含 pom.xml 和 src 的这一层。

把这条路线讲成一段小故事

  1. 按第5页装分页插件
  2. 新增 pagePractice
  3. Page(1,2) 查一页
  4. 改页码并看 SQL、total、records

先完成打开项目这一步

  1. 在讲义顶部点“下载代码资源”。等 ZIP 下载完成,在电脑文件管理器找到它,右键选择“全部提取/解压”,先解压成普通文件夹。不要直接在 ZIP 预览窗口中打开 Java 文件。
  2. 把解压目录复制一份作为自己的本课练习副本,例如 lesson-课次-学号。保留原包不改,后续课需要同章工程时可继续自己的副本,也可从原包重新开始。
  3. 按本页列出的“本课项目目录”进入到直接含 pom.xml 和 src 的那一层。整个 ZIP 解压目录、04_源代码 外层都不是这次的 Maven 项目根目录。
  4. 打开 IntelliJ IDEA。欢迎页点 Open(打开);已打开别的项目则点 File → Open。选择本课 pom.xml,出现选项时选 Open as Project(作为项目打开),或直接选它所在目录。已有项目时用 New Window,避免混淆。
  5. 导入后选择已安装的 JDK17,打开 Maven 面板 Reload All Maven Projects;依赖完成、代码无编译错误后再运行。

沿这些真实文件找代码

工程内路径 本次作用
src/test/java/MyBatisPlusTest.java 先双击定位,与第5页指定的修改步骤对照;原包已有。
src/main/java/com/itheima/service/BookService.java 先双击定位,与第5页指定的修改步骤对照;原包已有。
src/main/java/com/itheima/service/impl/BookServiceImpl.java 先双击定位,与第5页指定的修改步骤对照;原包已有。

不要急着执行

先请教师提供本章的独立练习数据库、已初始化的表和连接参数。按指定配置文件填写本地连接;配置未就绪时先停在这一步。读到 SQLException 不等于 Java 写错。只在自己的教学库做新增、修改和回滚练习,单独运行指定测试方法,不点整个测试类的 Run All。

本页先定位,第5页再修改运行

下面说明本课的运行方式,先找到对应按钮。到第5页完成指定配置、新建文件或修改代码后,再按这些步骤执行;还不存在的类或测试方法先不要运行。

本课怎样运行:只运行一个 JUnit 测试

  1. 打开 src/test/java/MyBatisPlusTest.java,找到 pagePractice 方法(pagePractice 是本课按步骤新增的测试)。它带 @Test,不需要 main。
  2. 点该方法左侧绿色三角 → Run 方法名。不要点整个类的三角,本章可能同时有删除或转账测试。运行后在 Run 窗口同时检查测试结果、控制台与数据库实际值。
  3. 每次改代码后 Save All,再点同一方法的 Run/Rerun;若没有三角,先检查 src/test/java 是否被 Maven 识别、JUnit依赖是否下载完成以及代码是否有编译错误。

本课入口是否需要新增

第1课 Main、第3课 NewsService、第14课 GreetingService、第16课 Author、第33课 pagePractice 等写明“新建”的内容,都在第5页给具体位置。未完成新增步骤前不要运行一个还不存在的方法。

完成修改后检查

操作或输入 预期观察
完成pagePractice与插件 日志确认分页SQL,再检查total/current/records。
页码改2 数据足够时出现另一页,数据不足可能为空。
只创建Page没有分页插件 不能据此宣称SQL已分页,须检查日志和真实条数。

记录一遍过程

在第5页按顺序改完,每次 Save All 再运行。写下本次实际入口、输入与结果,和表里的预期逐项对照;第一次未成功就按第6页定位。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)

第5/7页 · 换成自己的代码

下载项目后,在电脑的 IntelliJ IDEA 中一步一步修改、运行并检查;网站最后只接收你的作业。

本课在 IDEA 中完成什么

给原测试工程补上分页插件,再看页码、总数与本页记录。

今天就用这个具体版本练习

按第5页装插件并新增方法。页码从1改成2再跑,记录真实 total 与每页 records 数量;不要假定教学库正好有5条。

这七页使用同一个工程例子

前面的代码阅读和下面的练习都使用本课下载项目。表中列出的文件已在包中;写明“新建”的文件由你按步骤在 IDEA 创建。先保留本地正确包名和目录,再按真实文件名提交修改后的文本。

本课项目目录

04_源代码/chapter12;选择这一层的 pom.xml。运行入口:src/test/java/MyBatisPlusTest.java#pagePractice。# 后面的文字表示要单独运行的测试方法。

第0步:下载、解压并在 IDEA 打开项目

  1. 在讲义顶部点“下载代码资源”。等 ZIP 下载完成,在电脑文件管理器找到它,右键选择“全部提取/解压”,先解压成普通文件夹。不要直接在 ZIP 预览窗口中打开 Java 文件。
  2. 把解压目录复制一份作为自己的本课练习副本,例如 lesson-课次-学号。保留原包不改,后续课需要同章工程时可继续自己的副本,也可从原包重新开始。
  3. 按本页列出的“本课项目目录”进入到直接含 pom.xml 和 src 的那一层。整个 ZIP 解压目录、04_源代码 外层都不是这次的 Maven 项目根目录。
  4. 打开 IntelliJ IDEA。欢迎页点 Open(打开);已打开别的项目则点 File → Open。选择本课 pom.xml,出现选项时选 Open as Project(作为项目打开),或直接选它所在目录。已有项目时用 New Window,避免混淆。
  5. 确认导入方式为 Maven。若出现信任提示,先核对这确实是本课程下载的工程再信任。等右下角索引与依赖下载结束,不在下载尚未完成时急着运行。
  6. 点 File → Project Structure → Project,将 SDK 设为已安装的 JDK 17。列表没有17就按教师安装包先安装,再 Add SDK/JDK 选择实际安装目录;不要把语言级别选17当作JDK已安装。
  7. 打开 View → Tool Windows → Maven,点击 Reload All Maven Projects(重新加载全部 Maven 项目)。若没有Maven面板,右键本课 pom.xml → Add as Maven Project。红色依赖仍存在时看下载错误,先解决网络/仓库和JDK配置。
  8. 回到左侧 Project,按本课文件表展开目录并双击文件。Java包可能合并显示为 com.itheima;表中的 src/main/java 表示主代码,src/test/java 表示测试。先核对包名与文件名,再修改。

先找到这些真实文件

项目根目录下的路径 怎么找到
src/test/java/MyBatisPlusTest.java 在 IDEA 左侧 Project 按路径展开、双击;这份文件已在下载包中。
src/main/java/com/itheima/service/BookService.java 在 IDEA 左侧 Project 按路径展开、双击;这份文件已在下载包中。
src/main/java/com/itheima/service/impl/BookServiceImpl.java 在 IDEA 左侧 Project 按路径展开、双击;这份文件已在下载包中。

第1步前:准备运行依赖

先请教师提供本章的独立练习数据库、已初始化的表和连接参数。按指定配置文件填写本地连接;配置未就绪时先停在这一步。读到 SQLException 不等于 Java 写错。只在自己的教学库做新增、修改和回滚练习,单独运行指定测试方法,不点整个测试类的 Run All。 本章配置在 resources/mybatis-config.xml 的 environments/dataSource;不要把教材默认连接直接当成机房已提供的教学库。

本课怎样运行:只运行一个 JUnit 测试

  1. 打开 src/test/java/MyBatisPlusTest.java,找到 pagePractice 方法(pagePractice 是本课按步骤新增的测试)。它带 @Test,不需要 main。
  2. 点该方法左侧绿色三角 → Run 方法名。不要点整个类的三角,本章可能同时有删除或转账测试。运行后在 Run 窗口同时检查测试结果、控制台与数据库实际值。
  3. 每次改代码后 Save All,再点同一方法的 Run/Rerun;若没有三角,先检查 src/test/java 是否被 Maven 识别、JUnit依赖是否下载完成以及代码是否有编译错误。

第1步:在 IDEA 操作并核对

打开 MyBatisPlusTest.java,找到 createSqlSession() 中构造 factory 的两行,用下方完整片段替换这两行;保留后面的 openSession 与异常处理。分页插件在构建工厂前加入。

第2步:在 IDEA 操作并核对

在类内、其他方法外新增下方 pagePractice 测试;这里完整限定包名,先照着输入,避免遗漏 import。不要粘进 selectTest 方法内部。

第3步:在 IDEA 操作并核对

确认教学库 book 至少有3条记录,单独运行 pagePractice。看 total、current、records.size(),本页条数最多2;绿色图标并不证明 SQL 分页生效,还要看日志中的分页语句。

第4步:在 IDEA 操作并核对

把 Page 构造参数的页码1改成2,保存重跑,核对记录与第一页不同;页数不够则可能为空,不造更多条数。

第5步:在 IDEA 操作并核对

打开 BookService 和 BookServiceImpl,看 IService/ServiceImpl 的封装。当前包没有完整服务层测试启动环境,先说明它们的职责;本次实际运行的是 Mapper 分页测试,不能假装已测 Service。

createSqlSession 中替换 factory 构建语句

在 IDEA 中修改;不是网站运行代码

com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor plugin =
    new com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor();
plugin.addInnerInterceptor(new com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor(
    com.baomidou.mybatisplus.annotation.DbType.MYSQL));
com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder builder =
    new com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder();
org.apache.ibatis.session.Configuration configuration =
    new com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder(reader).parse();
configuration.addInterceptor(plugin);
SqlSessionFactory sqlMapper = builder.build(configuration);

MyBatisPlusTest 类内新增完整测试方法

在 IDEA 中修改;不是网站运行代码

@org.junit.jupiter.api.Test
public void pagePractice() {
    try (SqlSession session = this.createSqlSession()) {
        BookMapper mapper = session.getMapper(BookMapper.class);
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<Book> page =
            new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(1, 2);
        mapper.selectPage(page, null);
        System.out.println("总数=" + page.getTotal());
        System.out.println("页码=" + page.getCurrent());
        System.out.println("本页条数=" + page.getRecords().size());
        System.out.println(page.getRecords());
    }
}

在本地写练习记录

  1. 在 IDEA 项目根目录右键 New → File,新建 练习说明.md。填写改过的完整文件路径、方法或行位置,别只写“改了代码”。
  2. 每组检查写输入/操作、预期、实际结果;文字输出从 Run 控制台抄,接口结果从本机请求抄,数据库结果写实际查到的值。未运行就写“未实测”,并说明卡在哪一步。
  3. 临时故意写错的对象名、请求或配置按本课要求恢复,Save All。关闭再打开修改文件,确认本地改动保留;最终效果用最后一次重新运行检查。

最后才回网站交作业

本课代码的打开、修改、运行和检查都在本机 IDEA 完成。网站只用来下载讲义/资源和提交结果,不能运行 Java。完成后点讲义顶部“打开代码任务”,将自己在 IDEA 改过的文本文件复制到任务对应文件中;名称与模板不同就“+ 添加文件”填写真实文件名,不为迁就网站模板改错本地包名。保留自己的旧草稿,只提交需要交的源码和练习说明。

保存与提交

添加练习说明.md 并核对本地运行记录 → 保存草稿 → 等显示已保存 → 正式提交并核对新增版本。草稿不计入正式提交次数。项目 ZIP 留在电脑,按教师指定方式另交;不要把 ZIP 二进制内容粘进文本编辑器。提交材料去掉真实密码与登录token。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)

第6/7页 · 出错时从哪里查

先保留现象,只检查这一课的同一条调用路线。一次改一处,再重跑确认。

常见现象

现象 先做什么
IDEA 没有运行三角或依赖报红 先查已安装的 JDK17、Maven 导入与下载错误;不要给 DAO/Controller 硬加 main。
运行结果还是旧的 核对右上角入口与当前工程;main/JUnit重新 Run,Web修改后重新编译部署或重启。
连接或启动失败 按第一段关键错误定位数据库、配置、端口或存储;环境未就绪先请教师检查。
没有预期数据或文字 按下面的具体输入表查调用链,分开看控制台、响应和真实数据,不把无报错等同完成。

遇到问题,先演一遍这一条路线

按第5页装分页插件 → 新增 pagePractice → Page(1,2) 查一页 → 改页码并看 SQL、total、records

用本课的输入复查

操作或输入 对照实际结果
完成pagePractice与插件 日志确认分页SQL,再检查total/current/records。
页码改2 数据足够时出现另一页,数据不足可能为空。
只创建Page没有分页插件 不能据此宣称SQL已分页,须检查日志和真实条数。

按本课路线检查

  1. 原selectTest是准备参考,分页测试不是下载时就有的。
  2. 新增Page<>(1,2)表示第1页、最多2条;不是旧讲义的每页5条。
  3. PaginationInnerInterceptor必须在工厂构建前加入配置。
  4. total是符合条件的总量,records是本页集合;Service类当前仅作结构阅读,实际跑Mapper测试。

需要老师帮忙时交这些

工程名 chapter12;运行入口 src/test/java/MyBatisPlusTest.java#pagePractice;你改的文件;输入或请求;第一段关键错误;本次预期与实际。截图之外保留错误文字,方便找到具体行。

把预测和实测分开

检查表是预期,不是替你运行过的证明。未在本机执行就写“未实测”;原教材配置或逻辑出现问题时记录原文件与现象,不写“系统判错”。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)

第7/7页 · 用自己的话讲一遍

用真实文件和本机结果说明你完成了什么;先说动作,再说术语。

回到第3页:你刚才猜对了吗?

第3页只剩1条,应该把 total 改成1吗?

老师带你说清原因

不应该。total 表示所有匹配数据数量,本页集合大小才是1。当前教学库数量以真实查询为准。

请换一个自己的例子

照着本课三个例子的写法,说清“我只改什么、预计哪一处变化、到哪里看结果”。不需要再发明一个陌生项目;用本课同一组文件和入口。

回答本课的两个问题

问题 参考解释
pagePractice在哪? 第5页要求新建在MyBatisPlusTest类内的JUnit方法。
总数与本页条数一样吗? 通常不一样,前者是全部匹配记录,后者是这一页最多2条。

按这个顺序讲给同学听

  1. 我打开的工程是 chapter12,本次阅读文件是 src/test/java/MyBatisPlusTest.java。
  2. 我实际改了哪一行、加了哪个方法;指着 IDEA 的代码说明。
  3. 我运行或请求的入口是 src/test/java/MyBatisPlusTest.java#pagePractice,输入是什么。
  4. 我在控制台、响应或数据库看到了什么,与第1页预期有什么差异。
  5. 我还有哪步没完成,保留了什么错误信息。

交作业前最后检查

文件与练习说明在 IDEA 已保存,正式提交的是自己的最终文本。下载包原样未改的部分标为参考;配置去掉真实密码和token。原有草稿不会被起始文件覆盖,需要时在代码任务查看本次起始文件对照。

课后再做一个小对照

只换本课检查表的一项输入,按同一入口重新执行,补一行预期和实际。没有环境时写明前置条件并停在实际完成的位置;不增加下一章的陌生功能。

来源:第12章 MyBatis-Plus快速入门 · 课件第29、31、37页(课件页码,不是教材印刷页码)