首页 > 其他分享 >PageHelper在SpringBoot中的使用和原理分析

PageHelper在SpringBoot中的使用和原理分析

时间:2024-09-17 20:45:51浏览次数:1  
标签:dialect return SpringBoot Object public 原理 PageHelper parameter class

PageHelper在SpringBoot中的使用和原理分析

在SpringBoot项目中使用Mybatis的PageHelper分页插件进行分页查询

1、导入相关依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>

2、添加相关配置信息

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true

pagehelper:
  reasonable: true
  defaultCount: true # 分页插件默认参数支持 default-count 形式,自定义扩展的参数,必须大小写一致
  helperDialect: mysql
mybatis:
  mapper-locations: classpath:/mapper/*.xml

3、编写mapper接口

@Repository
public interface UserMapper {
    List<User> getAllUser();
}

4、编写service,其中getAllUser()是普通查询,getAllUser1()和getAllUser2()是分页查询,使用PageHelper需要传入pageNum和pageSize

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public List<User> getAllUser(){
        return userMapper.getAllUser();
    }
    public List<User> getAllUser1(int pageNum,int pageSize){
        PageHelper.startPage(pageNum,pageSize);
        List<User> allUser = userMapper.getAllUser();
        return allUser;
    }

    public PageInfo<User> getAllUser2(int pageNum,int pageSize){
        PageHelper.startPage(pageNum,pageSize);
        List<User> allUser = userMapper.getAllUser();
        PageInfo<User> pageInfo = new PageInfo<>();
        pageInfo.setList(allUser);
        return pageInfo;
    }
}

5、编写Controller

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    
    @GetMapping("/users")
    public List<User> getAllUser(){
        return userService.getAllUser();
    }

    @GetMapping("/users1")
    public List<User> getAllUser1(){
        return userService.getAllUser1(1,3);
    }

    @GetMapping("/users2")
    public PageInfo<User> getAllUser2(){
        return userService.getAllUser2(1,3);
    }
}

6、编写Mybatis的mapper文件,其中namespace要与接口方法所在的包绑定,MappedStatement的id要与接口方法绑定

<?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.UserMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.example.demo.pojo.User">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="phone" property="phone" />
    </resultMap>

    <select id="getAllUser" resultType="com.example.demo.pojo.User">
        select * from user
    </select>
</mapper>

7、测试程序,测试接口

接口:http://localhost:8080/users

[{"id":"1","name":"zlw","phone":"17860397215"},{"id":"2","name":"zyx","phone":"18865986031"},{"id":"3","name":"zq","phone":"17860397216"},{"id":"4","name":"zxj","phone":"17860398476"},{"id":"5","name":"zmj","phone":"15021469872"},{"id":"6","name":"zqq","phone":"17652369421"}]

接口:http://localhost:8080/users1

[{"id":"1","name":"zlw","phone":"17860397215"},{"id":"2","name":"zyx","phone":"18865986031"},{"id":"3","name":"zq","phone":"17860397216"}]

接口:http://localhost:8080/users2

{"total":0,"list":[{"id":"1","name":"zlw","phone":"17860397215"},{"id":"2","name":"zyx","phone":"18865986031"},{"id":"3","name":"zq","phone":"17860397216"}],"pageNum":0,"pageSize":0,"size":0,"startRow":0,"endRow":0,"pages":0,"prePage":0,"nextPage":0,"isFirstPage":false,"isLastPage":false,"hasPreviousPage":false,"hasNextPage":false,"navigatePages":0,"navigatepageNums":null,"navigateFirstPage":0,"navigateLastPage":0}

接下来从代码的角度看看PageHelper是怎么实现分页的。

当导入pagehelper-spring-boot-starter依赖后,pagehelper已经自定了一个自动配置类PageHelperAutoConfiguration,可以在maven包下找到对应的spring.factories文件看一下。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration

进入这个类看一下

@Configuration
@ConditionalOnBean({SqlSessionFactory.class})
@EnableConfigurationProperties({PageHelperProperties.class, PageHelperStandardProperties.class})
@AutoConfigureAfter({MybatisAutoConfiguration.class})
@Lazy(false)
public class PageHelperAutoConfiguration implements InitializingBean {
    private final List<SqlSessionFactory> sqlSessionFactoryList;
    private final PageHelperProperties properties;

    public PageHelperAutoConfiguration(List<SqlSessionFactory> sqlSessionFactoryList, PageHelperStandardProperties standardProperties) {
        this.sqlSessionFactoryList = sqlSessionFactoryList;
        this.properties = standardProperties.getProperties();
    }

    public void afterPropertiesSet() throws Exception {
        PageInterceptor interceptor = new PageInterceptor();
        interceptor.setProperties(this.properties);
        Iterator var2 = this.sqlSessionFactoryList.iterator();

        while(var2.hasNext()) {
            SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var2.next();
            org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
            if (!this.containsInterceptor(configuration, interceptor)) {
                configuration.addInterceptor(interceptor);
            }
        }

    }

    private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) {
        try {
            return configuration.getInterceptors().stream().anyMatch((config) -> {
                return interceptor.getClass().isAssignableFrom(config.getClass());
            });
        } catch (Exception var4) {
            return false;
        }
    }
}

可以看到这个自动配置类PageHelperAutoConfiguration实现了InitializingBean接口并且重写了afterPropertiesSet()方法,所以当spring在初始化PageHelperAutoConfiguration时会执行afterPropertiesSet()进行初始化。这里创建了一个PageInterceptor分页拦截器,然后将PageInterceptor配置到Mybatis中。

这个PageInterceptor是实现分页的关键,点进去看看,先看一下类上的注解。

@Intercepts({@Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})

@Intercepts表明PageInterceptor是一个拦截器,@Signature定义了这了拦截器要拦截的类型,method = "query"也就是这个拦截器会拦截所有的sql查询方法。

回到UserService.java,在调用mapper查询的代码上打个断点看一下PageInterceptor是怎么执行的。

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public List<User> getAllUser(){
        return userMapper.getAllUser();
    }
    public List<User> getAllUser1(int pageNum,int pageSize){
        PageHelper.startPage(pageNum, pageSize);
        List<User> allUser = userMapper.getAllUser();
        return allUser;
    }

    public PageInfo<User> getAllUser2(int pageNum,int pageSize){
        PageHelper.startPage(pageNum,pageSize);
        List<User> allUser = userMapper.getAllUser();
        PageInfo<User> pageInfo = new PageInfo<>();
        pageInfo.setList(allUser);
        return pageInfo;
    }
}

程序首先会执行PageInterceptor的plugin()方法,它返回了Mybatis执行器的代理对象,跟进wrap()方法看看。

public class PageInterceptor implements Interceptor {
    //...
	public Object plugin(Object target) {
    	return Plugin.wrap(target, this);
	}
    //...
}
public class Plugin implements InvocationHandler {
    private final Object target;
    private final Interceptor interceptor;
    private final Map<Class<?>, Set<Method>> signatureMap;
    
    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }
    
    public static Object wrap(Object target, Interceptor interceptor) {
        //map的key是Mybatis的执行器,value是要执行拦截的查询方法
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //target是一个cachingExcutor类,这是Mybatis的执行器,负责执行查询操作和缓存机制
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //为cachingExcutor生成一个代理对象,看第三个参数,Plugin类自身实现了Invocation接口,这里把自身传进去了
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

    //Plugin类实现Invocation接口,并重写invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            //如果方法需要拦截,就执行PageIntercptor的intercept()方法
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
}

Plugin的wrap()方法最终返回了一个Mybatis执行器的代理对象,在执行分页查询方法时,通过invoke方法进行调用,这时会执行PageInterceptor的intercept方法。

//PageInterceptor.class
public Object intercept(Invocation invocation) throws Throwable {
    try {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement)args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds)args[2];
        ResultHandler resultHandler = (ResultHandler)args[3];
        Executor executor = (Executor)invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        if (args.length == 4) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey)args[4];
            boundSql = (BoundSql)args[5];
        }

        this.checkDialectExists();
        if (this.dialect instanceof BoundSqlInterceptor.Chain) {
            boundSql = ((BoundSqlInterceptor.Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);
        }

        List resultList;
        if (!this.dialect.skip(ms, parameter, rowBounds)) {
            this.debugStackTraceLog();
            Future<Long> countFuture = null;
            Long count;
            if (this.dialect.beforeCount(ms, parameter, rowBounds)) {
                if (this.dialect.isAsyncCount()) {
                    countFuture = this.asyncCount(ms, boundSql, parameter, rowBounds);
                } else {
                    count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
                    if (!this.dialect.afterCount(count, parameter, rowBounds)) {
                        Object var13 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
                        return var13;
                    }
                }
            }
			//分页的重点
            resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            if (countFuture != null) {
                count = (Long)countFuture.get();
                this.dialect.afterCount(count, parameter, rowBounds);
            }
        } else {
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }

        Object var17 = this.dialect.afterPage(resultList, parameter, rowBounds);
        return var17;
    } finally {
        if (this.dialect != null) {
            this.dialect.afterAll();
        }

    }
}

ExecutorUtil.pageQuery()方法是分页逻辑的重点部分,进去看看。

//ExecutorUtil.class
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {
    if (!dialect.beforePage(ms, parameter, rowBounds)) {
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    } else {
        parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
        //从mysql方言中获取分页sql,在原始sql上拼接limit
        String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
        //创建一个BoundSql对象,包含了分页sql以及其他的参数
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
        Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
        Iterator var12 = additionalParameters.keySet().iterator();

        while(var12.hasNext()) {
            String key = (String)var12.next();
            pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }

        if (dialect instanceof BoundSqlInterceptor.Chain) {
        
            pageBoundSql = ((BoundSqlInterceptor.Chain)dialect).doBoundSql(Type.PAGE_SQL, pageBoundSql, cacheKey);
        }
		//执行器去查询
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
    }
}

从dialect.getPageSql()方法一直跟进可以进行mysql方言的getPageSql()方法,可以看到这个方法在原始的sql上拼接上了limit语句

//MySqlDialect.class
public String getPageSql(String sql, Page page, CacheKey pageKey) {
    StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
    sqlBuilder.append(sql);
    if (page.getStartRow() == 0L) {
        sqlBuilder.append("\n LIMIT ? ");
    } else {
        sqlBuilder.append("\n LIMIT ?, ? ");
    }

    return sqlBuilder.toString();
}

接下来会封装一个BoundSql对象,包含分页sql和一些参数。

image-20240917204434720

取哪些数据是根据pageNum和pageSize来计算的,这些参数是在调用mapper接口的方法之前通过PageHelper的startPage方法传入的。

PageHelper.startPage(pageNum, pageSize);

最后调用执行器的query()方法从数据库中查询结果并将其返回。

标签:dialect,return,SpringBoot,Object,public,原理,PageHelper,parameter,class
From: https://www.cnblogs.com/Linwei33/p/18417484

相关文章

  • 基于SpringBoot+Vue+MySQL的网上租赁系统
    系统展示用户前台界面管理员后台界面系统背景  在当前共享经济蓬勃发展的背景下,网上租赁系统作为连接租赁双方的重要平台,正逐步改变着人们的消费观念和生活方式。通过构建一个基于SpringBoot、Vue.js与MySQL的网上租赁系统,我们旨在为用户提供便捷、高效、安......
  • 基于SpringBoot+Vue+MySQL的在线视频教育平台
    系统展示用户前台界面管理员后台界面系统背景  随着信息技术的飞速发展和互联网普及率的不断提高,传统教育模式正面临深刻变革。在线视频教育平台作为数字化教育的重要载体,以其灵活性强、资源丰富、覆盖广泛等优势,逐渐成为人们获取知识、提升技能的新途径。......
  • 数模原理精解【11】
    文章目录logistic模型多元回归分析多元回归分析概览1.多元回归的概念与重要性2.多元回归在实际应用中的例子3.多元回归在预测和解释数据中的优势和局限性4.多元回归的优缺点及改进建议多元线性回归分析详解一、原理二、性质三、计算四、例子与例题五、应用场景六、......
  • 计算机组成原理-第二章-定点数的编码表示以及扩展
    2.1定点数和浮点数表示日常生活中整数的小数点固定在最右方,可省略不写;而实数的小数点位置不固定,计算机内部数据中每一位只能是0或1,不可能出现小数点,因此计算机表示小数点通过约定小数点的位置实现。小数点位置约定在固定位置的称为定点数小数点位置可浮动的称为浮点数 因......
  • springboot交通管理在线服务系统的设计与实现
    大家好,我是永钊,一个混迹在java圈的码农,今天要和大家聊的是一款基于springboot的交通管理在线服务系统,项目源码请联系永钊,目前有各类成品毕设javawebsshssmspringboot等等项目框架,源码丰富。专业团队,咨询就送开题报告,活动限时免费,有需要的朋友可以来留言咨询。本网站系......
  • springboot网上超市系统的设计与实现
    大家好,我是永钊,一个混迹在java圈的码农,今天要和大家聊的是一款基于springboot的网上超市系统,项目源码请联系永钊,目前有各类成品毕设javawebsshssmspringboot等等项目框架,源码丰富。专业团队,咨询就送开题报告,活动限时免费,有需要的朋友可以来留言咨询。本网站系统是在MyS......
  • 最优化理论与自动驾驶(十):纯跟踪算法原理、公式及代码演示
    纯跟踪算法(PurePursuitAlgorithm)是一种用于路径跟踪的几何控制算法,广泛应用于自动驾驶、机器人导航等领域。其基本思想是通过选择预定路径上的目标点(预瞄点),并控制转向角,使车辆不断逼近并跟随该目标点,从而达到路径跟踪的效果。1.纯跟踪算法的基本原理在纯跟踪算法中,控制车......
  • 如何打造动漫天堂?宇宙动漫网站设计与实现,Java SpringBoot Vue技术揭秘
    ✍✍计算机毕业编程指导师**⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流!⚡⚡Java、Python、微信小程序、大数据实战项目集⚡⚡文末......
  • 基于SpringBoot的篮球交流APP
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我自己的网站([小蔡coding](https://xiaocaicoding.cn/))演示视频和源码展示一对一指导服务代码参考数据库参考......
  • 基于SpringBoot 汽车信息分析与可视化系统(用户、汽车店、管理员)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我自己的网站([小蔡coding](https://xiaocaicoding.cn/))演示视频和源码展示一对一指导服务代码参考数据库参考......