Mybatis是一个半自动的持久化层ORM框架。
半自动化:Mybatis与Hibernate对比
持久化层:
数据可以持久化,将数据不仅仅放在内存中,而是保存在数据库中。也就是我们常说的DAO层。Mybatis可以帮助我们更方便地从数据库中取出数据、插入数据等。
ORM框架:遵循对象关系映射。
Mybatis与JDBC的对比

<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
dependencies>
位置:resources下
名称:推荐使用mybatis-config.xml
示例代码
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
mappers>
configuration>
selectEmyById。下面我们通过写映射文件与数据库进行交互。不需要我们手写DAO了。
映射文件作用:主要作用为Mapper接口书写Sql语句
映射文件位置:resources/mapper名称:XXXMapper.xml,与接口名一致。
示例代码
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.mapper.EmployeeMapper">
<select id="selectEmpById" resultType="mybatis.pojo.Employee">
SELECT
id,last_name 'lastName' ,email,salary
FROM
tbl_employee
WHERE
id = #{id}
select>
mapper>
注意:
SELECT语句中查询的属性名应该与Employee的属性一致(使用别名),如果不一致,可能出现最终查询值为null的情况。后面可以通过设置
标签,来避免设置别名的情况。
//1. 获取到sqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 使用sqlSessionFactory获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取EmployeeMapper的代理对象【com.sun.proxy.$Proxy6】
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
System.out.println("employeeMapper.getClass().getName() = " + employeeMapper.getClass().getName());
Employee employee = employeeMapper.selectEmpById(1);
System.out.println("employee = " + employee);
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
resourceslog4j.xml
DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
layout>
appender>
<logger name="java.sql">
<level value="debug" />
logger>
<logger name="org.apache.ibatis">
<level value="info" />
logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
root>
log4j:configuration>
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
configuration:根标签,没有实际语义,所有子标签需要设置在根标签内部。
properties:定义或引入外部属性文件。实现代码与数据库连接的解耦。
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties">
properties>
<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 resource="mapper/EmployeeMapper.xml"/>
mappers>
configuration>
//编写配置文件——db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
username=root
password=root
settings:这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
比如:mapUnderscoreToCamelCase:是否开启驼峰命名自动映射。默认值false,如设置true会自动将last_name对应到lastName。
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
typeAliases:类型别名可为 Java 类型设置一个缩写名字。
示例代码
<typeAliases>
<typeAlias type="mybatis.pojo.Employee" alias="employee">
typeAlias>
<package name="mybatis.pojo"/>
typeAliases>
Mybatis自定义别名
| 别名 | 类型 |
|---|---|
| _int | int |
| integer或int | Integer |
| string | String |
| list或arraylist | ArrayList |
| map或hashmap | HashMap |
environments:设置数据库连接环境。
mappers:设置映射文件路径
示例代码
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
<package name="mybatis.mapper"/>
mappers>
注意:
核心配置中的子标签,是有顺序要求的。
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。
根标签
子标签
useGeneratedKeys标签:获取主键自增的属性—true,配合keyProperty标签:设置存储属性值。
<insert id="InsertEmp" useGeneratedKeys="true"
keyProperty="id">
INSERT INTO
tbl_employee(last_name,email,salary)
VALUES(#{lastName},#{email},#{salary});
insert>
直接让接口中的方法返回一个值即可。
这一部分我们主要讲编写在映射文件中的SQL,所传入的参数问题。
INSERT INTO
tbl_employee(last_name,email,salary)
VALUES(#{lastName},#{email},#{salary})
可以任意使用,不需要考虑数据类型和参数名称。
Mybatis底层会封装成Map,key为param1、param2 或者 arg0、arg1。
传入多个普通参数时,有两个办法:
直接在参数传递处写param即可。
命名参数:在接口处的形参之前,加上注解
public void Update(@Param("id") int id, @Param("lastName") String lastName);
命名参数底层源码分析:
源码分析
//130行
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
//支持param1、param2的逻辑
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
POJO参数
当接口处的参数属于pojo时,可以直接传入pojo。其参数传递处为pojo属性名。
Map参数
当接口处的参数属于Map时,直接使用Map入参,Map的key即为传入的参数。value为传入的值。
Collection|List|Array等参数
参数名:collection、list、array
【#】:底层执行SQL使用PreparedStatement对象,入参使用SQL拼接方式,预编译SQL,相对安全。
【$】:底层执行SQL使用Statement对象,入参使用占位符方式,存在SQL注入问题。
查询SQL:
SELECT col1,col2
FROM table1
WHERE col = ? AND col2 = ?
GROUP BY ? , ORDER BY ? , LIMIT ? , ? ;
- #:sql占位符位置均可以使用# 。
- $:其余位置使用。比如FROM中的动态化表名。
查询单行数据返回单行结果
示例代码:
//根据id获取雇员信息。
public Employee selectEmpById (int id);
<select id="selectEmpById" resultType="mybatis.pojo.Employee">
SELECT
id,last_name ,email,salary
FROM
tbl_employee
WHERE
id = #{id}
select>
查询多行数据返回对象集合
如果返回的是集合,resultType应该设置为集合包含的类型,而不是集合本身的类型。
示例代码:
public List<Employee> selectTbl(String tblName);
<select id="selectTbl" resultType="mybatis.pojo.Employee">
SELECT
id,last_name ,email,salary
FROM
${tblName}
select>
查询单行数据返回Map集合
Map
示例代码:
/**
* 查询单行数据返回Map集合
*/
public Map<String,Object> selectEmpReturnMap (int id);
<select id="selectEmpReturnMap" resultType="map">
SELECT
id,last_name,email,salary
FROM
tbl_employee
WHERE
id = #{id}
select>
查询多行数据返回Map集合
Map
示例代码:
/**
* 查询多行数据返回Map
* Map
* Map
* 对象Id作为:key
* 对象作为:value
* @return
*/
@MapKey("id")
public Map<Integer,Employee> selectEmpsReturnMap();
<select id="selectEmpsReturnMap" resultType="map">
SELECT
id,
last_name,
email,
salary
FROM
tbl_employee
select>
自动映射/自定义映射可以在映射文件【XXXMapper.xml】中进行配置。
自动映射:resultType
即自动将表中的字段与类中的属性进行关联映射。后面接SQL所查询POJO类的全类名。
自定义映射:resultMap
当resultType解决不了的问题时,使用resultMap:
resultMap标签:自定义映射标签
resultMap子标签
id标签:定义主键字段与属性关联关系
result标签:定义非主键字段与属性关联关系
association标签:定义一对一的关联关系
collection标签:定义一对多的关联关系
<resultMap id="EmpAndDepResultMap" type="employee">
<id column="id" property="id">id>
<result column="last_name" property="lastName">result>
<result column="email" property="email">result>
<result column="salary" property="salary">result>
<result column="dept_id" property="dept.deptId">result>
<result column="dept_name" property="dept.deptName">result>
resultMap>
<select id="selectEmpAndDptById" resultMap="EmpAndDepResultMap">
SELECT e.email,
e.id,
e.last_name,
e.salary,
d.dept_id,
d.dept_name
FROM
tbl_employee e
JOIN
tbl_dept d
ON
e.dept_id = d.dept_id
WHERE
e.id = #{id}
select>
<resultMap id="EmpAndDepResultMap01" type="employee">
<id column="id" property="id">id>
<result column="last_name" property="lastName">result>
<result column="email" property="email">result>
<result column="salary" property="salary">result>
<association property="dept" javaType="Mybatis.pojo.Dept">
<id column="dept_id" property="deptId">id>
<result column="dept_name" property="deptName">result>
association>
resultMap>
<resultMap id="selectDepAndEmpByIdResultMap" type="dept">
<id property="deptId" column="dept_id">id>
<result property="deptName" column="dept_name">result>
<collection property="empList"
ofType="Mybatis.pojo.Employee">
<id column="id" property="id">id>
<result column="last_name" property="lastName">result>
<result column="email" property="email">result>
<result column="salary" property="salary">result>
collection>
resultMap>
需要即加载,不需要暂时不加载
优势:提升程序运行效率
全局设置
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
局部设置
为什么使用分步查询?
示例代码(一对一)
EmployeeMapper
/**
* 通过员工id获取员工信息及员工所属的部门信息【分步查询】
1. 先通过员工id获取员工信息【id、last_name、email、salary、dept_id】
*/
public Employee selectEmpAndDeptByEmpIdAssociationStep(int empId);
DeptMapper
//2. 再通过部门id获取部门信息【dept_id、dept_name】
public Dept selectDeptById(int id);
EmployeeMapper.xml
<resultMap id="EmpAndDepResultMap02" type="employee">
<id column="id" property="id">id>
<result column="last_name" property="lastName">result>
<result column="email" property="email">result>
<result column="salary" property="salary">result>
<association property="dept"
select="Mybatis.mapper.DeptMapper.selectDeptById"
column="dept_id">
association>
resultMap>
<select id="selectEmpAndDeptByEmpIdAssociationStep" resultMap="EmpAndDepResultMap02">
select
id,
last_name,
email,
salary,
dept_id
from
tbl_employee
where
id=#{empId}
select>
DeptMapper.xml
<select id="selectDeptById" resultType="dept">
select
dept_id,
dept_name
from
tbl_dept
WHERE dept_id = #{id}
select>
示例代码(一对多)
通过部门id获取部门信息,及部门所属员工信息【分步查询】
1. 通过部门id获取部门信息
2. 通过部门id获取员工信息
public Dept selectDeptAndEmpByDeptIdStep(int deptId);
<select id="selectDeptAndEmpByDeptIdStep" resultMap="deptAndEmpResultMapStep">
select
dept_id,
dept_name
from
tbl_dept
where
dept_id=#{deptId}
select>
public List<Employee> selectEmpByDeptId(int deptId);
<select id="selectEmpByDeptId" resultType="employee">
select
id,
last_name,
email,
salary,
dept_id
from
tbl_employee
where
dept_id=#{deptId}
select>
分步查询SQL中需要多个参数时,column封装为map。k=传入参数名 v=字段名。语法:{k1=v1,k2=v2}
动态SQL即SQL语句可动态化。Mybatis的动态SQL中支持OGNL表达式语言,OGNL( Object Graph Navigation Language )对象图导航语言。
if标签:用于完成简单的判断。
where标签:用于解决where关键字及where后第一个and或or的问题。
trim标签: 可以在条件判断完的SQL语句前后添加或者去掉指定的字符。
prefix: 添加前缀
prefixOverrides: 去掉前缀
suffix: 添加后缀
suffixOverrides: 去掉后缀
set标签:主要用于解决set关键字及多出一个【,】问题。
choose标签:类似java中if-else【switch-case】结构
foreach标签:类似java中增强for循环
sql标签:提取可重用SQL片段
演示if-where标签的使用,if 进行条件判断,where 解决and/or问题。
<select id="selectEmpByOpr" resultType="employee">
<include refid="select_employee">include>
<where>
<if test="id != null">
and id = #{id}
if>
<if test="lastName != null">
and last_name = #{lastName}
if>
<if test="email != null">
and email = #{email}
if>
<if test="salary != null">
and salary = #{salary}
if>
where>
select>
演示trim标签的使用,解决前后缀问题。
<select id="selectEmpByOprTrim" resultType="employee">
<include refid="select_employee">include>
<trim prefix="where" suffixOverrides="and">
<if test="id != null">
id = #{id} and
if>
<if test="lastName != null">
last_name = #{lastName} and
if>
<if test="email != null">
email = #{email} and
if>
<if test="salary != null">
salary = #{salary}
if>
trim>
select>
演示set标签的使用,解决,问题
<update id="updateEmpByOpr">
update
tbl_employee
<set>
<if test="lastName != null">
last_name=#{lastName},
if>
<if test="email != null">
email=#{email},
if>
<if test="salary != null">
salary=#{salary}
if>
set>
where
id = #{id}
update>
演示choose标签,when相当于if-else,otherwise表示否则。
<select id="selectEmpByOneOpr" resultType="employee">
select
<include refid="emp_col">include>
from
tbl_employee
<where>
<choose>
<when test="id != null">
id = #{id}
when>
<when test="lastName != null">
last_name = #{lastName}
when>
<when test="email != null">
email = #{email}
when>
<when test="salary != null">
salary = #{salary}
when>
<otherwise>
1=1
otherwise>
choose>
where>
select>
演示foreach标签,解决查询多个id的循环问题。
//foreach标签中的collection属性为集合,item属性为每次循环的对象
item collection
for(int id : ids){
}
<select id="selectEmpByIds" resultType="employee">
select
id,
last_name,
email,
salary
from
tbl_employee
<where>
id in(
<foreach collection="ids" item="id" separator=",">
#{id}
foreach>
)
where>
select>
使用foreach批量添加员工信息。
<insert id="batchInsertEmp">
INSERT INTO
tbl_employee(last_name,email,salary)
VALUES
<foreach collection="employees" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.salary})
foreach>
insert>
mapper>
sql标签的使用,提取重用的sql片段
<sql id="emp_col">
id,
last_name,
email,
salary
sql>
<sql id="select_employee">
select
id,
last_name,
email,
salary
from
tbl_employee
sql>
<include refid="emp_col">include>
使用缓存的优势
缓存的类型

一级缓存,也叫做本地缓存(sqlSession级别的缓存)
特点
一级缓存默认开启,不能关闭,但是可以清空。
缓存失效的情况
不同的sqlSession代表不同的一级缓存。
同一个sqlSession两次查询数据库进行增删改操作【默认清空了一级缓存】
同一个sqlSession但是查询条件不同。
同一个sqlSession两次查询期间手动清空了缓存。【 sqlSession.clearCache();】
同一个sqlSession两次查询期间提交了事务。 【 sqlSession.commit();】
缓存原理
二级缓存,也叫全局作用域缓存(sqlSessionFactory缓存)。
特点
二级缓存默认关闭,需要开启才能使用。
二级缓存需要提交sqlSession或关闭sqlSession时,才会缓存。
二级缓存使用步骤
二级缓存相关属性
在映射文件处的
二级缓存的失效情况
在两次查询之间,执行增删改操作,会同时清空一级缓存和二级缓存。
sqlSession.clearCache():只是用来清除一级缓存。
二级缓存底层原理
第一次获取数据时,先从数据库中获取数据,将数据缓存至一级缓存;当提交或关闭SqlSession时,将数据缓存至二级缓存。
以后再次获取数据时,先从一级缓存中获取数据,如一级缓存没有指定数据,再去二级缓存中获取数据。如二级缓存也没有指定数据时,需要去数据库中获取数据,…
第三方缓存:EhCache,是一个纯Java的进程内缓存框架。
使用步骤
maven中导入jar包
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.0.3version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.6.2version>
<scope>testscope>
dependency>
编写配置文件【ehcache.xml】
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="D:\mybatis\ehcache" />
<defaultCache
maxElementsInMemory="512"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
加载第三方缓存到映射文件
开始使用
注意事项
Mybatis Generator:简称MBG,是一个专门为Mybatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件、接口、以及POJO类。
官方文档:MyBatis Generator Core – Introduction to MyBatis Generator
使用步骤:
导入jar包
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.6version>
dependency>
编写配置文件
mbg.xmlresourcesDOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="simple" targetRuntime="MyBatis3Simple">
<property name="javaFileEncoding" value="UTF-8"/>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8"
userId="root"
password="root">
<property name="nullCatalogMeansCurrent" value="true" />
jdbcConnection>
<javaModelGenerator targetPackage="mybatis.pojo" targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>
<javaClientGenerator type="XMLMAPPER" targetPackage="mybatis.mapper" targetProject="src/main/java"/>
<table tableName="tbl_employee" domainObjectName="Employee" />
<table tableName="tbl_dept" domainObjectName="Department"/>
context>
generatorConfiguration>
运行程序,即可检查生成的代码。
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
Query By Criteria,根据标准查询。即条件都是定义好的,只需要调用相对应的方法,就可以生成标准的条件。
实现QBC风格,只需要将配置文件中的:
改为
public void test1() {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//创建条件对象
EmpExample example = new EmpExample();
// 给条件对象赋值对应的标准条件
example.createCriteria().andEmpNameEqualTo("张三");
// 在SQL语句中直接调用该对象
List<Emp> empList = mapper.selectByExample(example);
empList.forEach(emp -> System.out.println(emp));
} catch (IOException e) {
e.printStackTrace();
}
}
回顾SQL语句:
LIMIT(x,y)
x就是开始的页数。y是每页显示的数量。
如果要自己手动实现分页,则需要设计Page类。
PageHelper是Mybatis中非常方便的第三方分页插件。
官方文档:Mybatis-PageHelper/README_zh.md at master · pagehelper/Mybatis-PageHelper · GitHub
使用步骤:
导入jar包。
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.0version>
dependency>
在myabtis-config.xml中配置分页插件。
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
查询之前,使用PageHelper开启分页。
pageHelper.startPage(1,3)——>(当前页码,每页的数据量)
示例代码
public void text() throws Exception{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//使用分页
Page<Object> page = PageHelper.startPage(1, 4);
for (Employee employee : mapper.selectAll()) {
System.out.println(employee);
}
System.out.println(page.getPageNum() + "/" + page.getPages());
System.out.println("总页数:"+ page.getPages());
System.out.println("总数据量:" + page.getTotal());
System.out.println("返回结果的集合"+page.getResult());
for (Object o : page.getResult()) {
System.out.println("o = " + o);
}
}
查询之后,可以使用更强大的PageInfo实现后续分页效果。
public void text1() throws Exception{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//使用分页
Page<Object> page = PageHelper.startPage(1, 4);
List<Employee> employees = mapper.selectAll();
//设置页码
PageInfo<Employee> pageInfo = new PageInfo<>(employees,5);
//方法演示
System.out.println("导航页的第一个页码:"+pageInfo.getNavigateFirstPage());
System.out.println("导航页的最后一个页码:"+pageInfo.getNavigateLastPage());
System.out.println("导航页的总页码:"+ pageInfo.getNavigatePages());
/*
分页页码逻辑:
当前页码1:1 2 3 4 5
当前页码4:2 3 4 5 6
当前页码5:3 4 5 6 7
*/
}