PageHelper学习使用

news2024/12/26 10:54:05

基于mybatis源码和PageHelper源码进行的测试

版本

mybatis3.5.0pageHelper6.0.0

测试用例

依赖
<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.15</version>
</dependency>
<dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.11</version>
     <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>6.0.0</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.0</version>
</dependency>

如果引入上面这些依赖运行报下面这样的错误
在这里插入图片描述
需要再引入两个依赖,这个两个依赖直接去mybatis源码的pom.xml中找即可

<dependency>
      <groupId>ognl</groupId>
      <artifactId>ognl</artifactId>
      <version>3.2.10</version>
      <scope>compile</scope>
      <optional>true</optional>
</dependency>
<dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.24.1-GA</version>
      <scope>compile</scope>
      <optional>true</optional>
</dependency>

如果还报错,是下面这种错误,也是一样的从mybatis源码的pom.xml中找到依赖引用到自己的项目中
在这里插入图片描述

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
    <optional>true</optional>
</dependency>
日志文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
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=%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=[%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
数据库配置文件
jdbc.type=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:6608/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
jdbc.username=mybatis
jdbc.password=mybatis
mybatis配置文件
<?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="jdbc.properties"/>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
<!--分页拦截器-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
<!--环境配置-->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC" />
            <!-- 配置数据库连接信息 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

    <!-- 映射器:指定映射文件或者映射类 -->
    <mappers>
        <mapper resource="CmpRecordMapper.xml"/>
    </mappers>

</configuration>
映射文件
<?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="org.mybatis.domain.CmpRecordMapper">
    <select id="getCmpRecordBy" parameterType="java.util.Map" resultType="org.mybatis.domain.CmpRecord">
        <include refid="selectColumnSql"/>
        <if test="createTimeBegin != null">
            and create_time &gt;= #{createTimeBegin}
        </if>
        <if test="createTimeEnd != null">
            and create_time &lt;= #{createTimeEnd}
        </if>
    </select>

    <sql id="selectColumnSql">
        select
            id as "id",
            create_time as "createTime",
            valid as "valid",
            create_org_id as "createOrgId",
            create_org_name as "createOrgName",
            create_dept_id as "createDeptId",
            create_dept_name as "createDeptName",
            creator_id as "creatorId",
            creator_name as "creatorName",
            limit_time as "limitTime",
            category_id as "categoryId",
            category_name as "categoryName",
            cmp_reason_id as "cmpReasonId",
            cmp_reason_name as "cmpReasonName",
            content_abstract as "contentAbstract"
        from t_cmp_record where valid = 1
    </sql>
</mapper>
实体模型
public class CmpRecord {

    private String id;
    private Date createTime;
    private int valid = 1;
    private String createOrgId;
    private String createOrgName;
    private String createDeptId;
    private String createDeptName;
    private String creatorId;
    private String creatorName;
    private Date limitTime;
    private String categoryId;
    private String categoryName;
    private String cmpReasonId;
    private String cmpReasonName;
    private String contentAbstract;

    public CmpRecord() {
    }

    public CmpRecord(String id, Date createTime, int valid, String createOrgId, String createOrgName, String createDeptId, String createDeptName, String creatorId, String creatorName, Date limitTime, String categoryId, String categoryName, String cmpReasonId, String cmpReasonName, String contentAbstract) {
        this.id = id;
        this.createTime = createTime;
        this.valid = valid;
        this.createOrgId = createOrgId;
        this.createOrgName = createOrgName;
        this.createDeptId = createDeptId;
        this.createDeptName = createDeptName;
        this.creatorId = creatorId;
        this.creatorName = creatorName;
        this.limitTime = limitTime;
        this.categoryId = categoryId;
        this.categoryName = categoryName;
        this.cmpReasonId = cmpReasonId;
        this.cmpReasonName = cmpReasonName;
        this.contentAbstract = contentAbstract;
    }

    //省略get/set方法

    @Override
    public String toString() {
        return "CmpRecord{" +
                "id='" + id + '\'' +
                ", valid=" + valid +
                ", createOrgId='" + createOrgId + '\'' +
                ", createOrgName='" + createOrgName + '\'' +
                ", createDeptId='" + createDeptId + '\'' +
                ", createDeptName='" + createDeptName + '\'' +
                ", creatorId='" + creatorId + '\'' +
                ", creatorName='" + creatorName + '\'' +
                ", categoryId='" + categoryId + '\'' +
                ", categoryName='" + categoryName + '\'' +
                ", cmpReasonId='" + cmpReasonId + '\'' +
                ", cmpReasonName='" + cmpReasonName + '\'' +
                ", contentAbstract='" + contentAbstract + '\'' +
                '}';
    }
}
Mapper接口
public interface CmpRecordMapper {

    void saveCmpRecord(CmpRecord record);

    CmpRecord getCmpRecordId(String id);

    List<CmpRecord> getCmpRecordBy(Map<String, Object> params);
}
测试方法
public static void testPageHelper() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        CmpRecordMapper mapper = sqlSession.getMapper(CmpRecordMapper.class);
        Map<String,Object> params = new HashMap<>();
        params.put("createTimeBegin","2023-10-01 00:12:02");
        params.put("createTimeEnd","2023-11-01 00:12:02");
        PageHelper.startPage(1,2);
        List<CmpRecord> records = mapper.getCmpRecordBy(null);
        PageInfo<CmpRecord> datas = new PageInfo<>(records);
        System.out.println(datas.toString());
}

public static void main(String[] args) throws IOException {
        testPageHelper();
}

在这里插入图片描述

PageHelper源码分析

拦截器PageInterceptor
//这里执行拦截Executor接口的两个query方法
@SuppressWarnings({"rawtypes", "unchecked"})
@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}),
        }
)
public class PageInterceptor implements Interceptor {
    private static final Log log = LogFactory.getLog(PageInterceptor.class);
    private static boolean debug = false;
    protected Cache<String, MappedStatement> msCountMap = null;
    protected CountMsIdGen countMsIdGen = CountMsIdGen.DEFAULT;
    private volatile Dialect dialect;//这里是方言 是 PageHelper
    private String countSuffix = "_COUNT";
    private String default_dialect_class = "com.github.pagehelper.PageHelper";

    public PageInterceptor() {
        String bannerEnabled = System.getProperty("pagehelper.banner");
        if (StringUtil.isEmpty(bannerEnabled)) {
            bannerEnabled = System.getenv("PAGEHELPER_BANNER");
        }

    @Override
    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;
            //参数个数的不同对应上面不同的query方法
            //由于逻辑关系,只会进入一次
            if (args.length == 4) {
                //4 个参数时
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 个参数时
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            //这里会使用默认方言 PageHelper
            checkDialectExists();
            //对 boundSql 的拦截处理
            if (dialect instanceof BoundSqlInterceptor.Chain) {
            //这里会调用PageHelper.doudSql方法,返回还是原来的BoundSql对象
                boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
            }
            List resultList;
            //调用方法判断是否需要进行分页,如果不需要,直接返回结果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                Future<Long> countFuture = null;
                //判断是否需要进行 count 查询
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                //这里会判断是否异步进行count查询,一般都是同步count查询,所以会走else
                    if (dialect.isAsyncCount()) {
                        countFuture = asyncCount(ms, boundSql, parameter, rowBounds);
                    } else {
                        //查询总数
                        Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
                        //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                        if (!dialect.afterCount(count, parameter, rowBounds)) {
                            //当查询总数为 0 时,直接返回空的结果
                            return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                        }
                    }
                }
                //这里是分页查询数据
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
                if (countFuture != null) {
                    Long count = countFuture.get();
                    dialect.afterCount(count, parameter, rowBounds);
                }
            } else {
                //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if (dialect != null) {
                dialect.afterAll();
            }
        }
    }

    /**
     * 异步查询总数
     */
    private Future<Long> asyncCount(MappedStatement ms, BoundSql boundSql, Object parameter, RowBounds rowBounds) {
        Configuration configuration = ms.getConfiguration();
        //异步不能复用 BoundSql,因为分页使用时会添加分页参数,这里需要复制一个新的
        BoundSql countBoundSql = new BoundSql(configuration, boundSql.getSql(), new ArrayList<>(boundSql.getParameterMappings()), parameter);
        //异步想要起作用需要新的数据库连接,需要独立的事务,创建新的Executor,因此异步查询只适合在独立查询中使用,如果混合增删改操作,不能开启异步
        Environment environment = configuration.getEnvironment();
        TransactionFactory transactionFactory = null;
        if (environment == null || environment.getTransactionFactory() == null) {
            transactionFactory = new ManagedTransactionFactory();
        } else {
            transactionFactory = environment.getTransactionFactory();
        }
        //创建新的事务
        Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), null, false);
        //使用新的 Executor 执行 count 查询,这里没有加载拦截器,避免递归死循环
        Executor countExecutor = new CachingExecutor(new SimpleExecutor(configuration, tx));

        return dialect.asyncCountTask(() -> {
            try {
                return count(countExecutor, ms, parameter, rowBounds, null, countBoundSql);
            } finally {
                tx.close();
            }
        });
    }

    /**
     * Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
     * <p>
     * 因此这里会出现 null 的情况 fixed #26
     */
    private void checkDialectExists() {
        if (dialect == null) {
            synchronized (default_dialect_class) {
                if (dialect == null) {
                    setProperties(new Properties());
                }
            }
        }
    }

    private Long count(Executor executor, MappedStatement ms, Object parameter,
                       RowBounds rowBounds, ResultHandler resultHandler,
                       BoundSql boundSql) throws SQLException {
        String countMsId = countMsIdGen.genCountMsId(ms, parameter, boundSql, countSuffix);
        Long count;
        //先判断是否存在手写的 count 查询
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
        } else {
            if (msCountMap != null) {
                countMs = msCountMap.get(countMsId);
            }
            //自动创建
            if (countMs == null) {
                //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                if (msCountMap != null) {
                    msCountMap.put(countMsId, countMs);
                }
            }
            count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //缓存 count ms
        msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
        String dialectClass = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClass)) {
            dialectClass = default_dialect_class;
        }
        Dialect tempDialect = ClassUtil.newInstance(dialectClass, properties);
        tempDialect.setProperties(properties);

        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }

        // debug模式,用于排查不安全分页调用
        debug = Boolean.parseBoolean(properties.getProperty("debug"));

        // 通过 countMsId 配置自定义类
        String countMsIdGenClass = properties.getProperty("countMsIdGen");
        if (StringUtil.isNotEmpty(countMsIdGenClass)) {
            countMsIdGen = ClassUtil.newInstance(countMsIdGenClass, properties);
        }
        // 初始化完成后再设置值,保证 dialect 完成初始化
        dialect = tempDialect;
    }

}
工具类 PageHelper
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {
	private PageAutoDialect autoDialect;//数据库方言 根据你配置的数据库类型选择对应的方言  这里选择的是MySqlDialect.class
	private ForkJoinPool asyncCountService;//需要异步执行sql的线程池 初始化在当前类的 setProperties方法中
	@Override
    public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        Page page = pageParams.getPage(parameterObject, rowBounds);
        if (page == null) {
            return true;
        } else {
            //设置默认的 count 列
            if (StringUtil.isEmpty(page.getCountColumn())) {
                page.setCountColumn(pageParams.getCountColumn());
            }
            //设置默认的异步 count 设置
            if (page.getAsyncCount() == null) {
                page.setAsyncCount(pageParams.isAsyncCount());
            }
            autoDialect.initDelegateDialect(ms, page.getDialectClass());
            return false;
        }
    }
	@Override
    public <T> Future<T> asyncCountTask(Callable<T> task) {
        //异步执行时需要将ThreadLocal值传递,否则会找不到
        AbstractHelperDialect dialectThreadLocal = autoDialect.getDialectThreadLocal();
        Page<Object> localPage = getLocalPage();
        String countId = UUID.randomUUID().toString();
        return asyncCountService.submit(() -> {
            try {
                //设置 ThreadLocal
                autoDialect.setDialectThreadLocal(dialectThreadLocal);
                setLocalPage(localPage);
                return task.call();
            } finally {
                autoDialect.clearDelegate();
                clearPage();
            }
        });
    }
    @Override
    public void afterAll() {
        //这个方法即使不分页也会被执行,所以要判断 null
        AbstractHelperDialect delegate = autoDialect.getDelegate();
        if (delegate != null) {
            delegate.afterAll();
            autoDialect.clearDelegate();
        }
        clearPage();
    }
    @Override
    public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
        Page<Object> localPage = getLocalPage();
        BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
        if (chain == null) {
            BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
            BoundSqlInterceptor.Chain defaultChain = pageBoundSqlInterceptors != null ? pageBoundSqlInterceptors.getChain() : null;
            if (boundSqlInterceptor != null) {
                chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
            } else if (defaultChain != null) {
                chain = defaultChain;
            }
            if (chain == null) {
                chain = DO_NOTHING;
            }
            if (localPage != null) {
                localPage.setChain(chain);
            }
        }
        return chain.doBoundSql(type, boundSql, cacheKey);
    }
    //处理分页参数
    @Override
    public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
        return autoDialect.getDelegate().processParameterObject(ms, parameterObject, boundSql, pageKey);
    }
    //TODO 其他方法省略
}
AbstractHelperDialect
@Override
    public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
        //处理参数
        Page page = getLocalPage();
        //如果只是 order by 就不必处理参数
        if (page.isOrderByOnly()) {
            return parameterObject;
        }
        Map<String, Object> paramMap = null;
        if (parameterObject == null) {
            paramMap = new HashMap<String, Object>();
        } else if (parameterObject instanceof Map) {
            //解决不可变Map的情况
            paramMap = new HashMap<String, Object>();
            paramMap.putAll((Map) parameterObject);
        } else {
            paramMap = new HashMap<String, Object>();
            // sqlSource为ProviderSqlSource时,处理只有1个参数的情况
            if (ms.getSqlSource() instanceof ProviderSqlSource) {
                String[] providerMethodArgumentNames = ExecutorUtil.getProviderMethodArgumentNames((ProviderSqlSource) ms.getSqlSource());
                if (providerMethodArgumentNames != null && providerMethodArgumentNames.length == 1) {
                    paramMap.put(providerMethodArgumentNames[0], parameterObject);
                    paramMap.put("param1", parameterObject);
                }
            }
            //动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
            //TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
            boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
            MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
            //需要针对注解形式的MyProviderSqlSource保存原值
            if (!hasTypeHandler) {
                for (String name : metaObject.getGetterNames()) {
                    paramMap.put(name, metaObject.getValue(name));
                }
            }
            //下面这段方法,主要解决一个常见类型的参数时的问题
            if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
                for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                    String name = parameterMapping.getProperty();
                    if (!name.equals(PAGEPARAMETER_FIRST)
                            && !name.equals(PAGEPARAMETER_SECOND)
                            && paramMap.get(name) == null) {
                        if (hasTypeHandler
                                || parameterMapping.getJavaType().equals(parameterObject.getClass())) {
                            paramMap.put(name, parameterObject);
                            break;
                        }
                    }
                }
            }
        }
        //调用具体数据库方言进行处理 这里是调用的MySqlDialect
        return processPageParameter(ms, paramMap, page, boundSql, pageKey);
    }

项目中使用PageHelper遇到的问题

分页不正确
原因:PageMethod.LOCAL_PAGE没有及时删除

PageMethod
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
    protected static boolean DEFAULT_COUNT = true;

    /**
     * 设置 Page 参数
     *
     * @param page
     */
    public static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

    /**
     * 获取 Page 参数
     *
     * @return
     */
    public static <T> Page<T> getLocalPage() {
        return LOCAL_PAGE.get();
    }

    /**
     * 移除本地变量
     */
    public static void clearPage() {
        LOCAL_PAGE.remove();
    }
    //TODO 省略其他方法
}

而且并不是每一次都有问题,这个其实取决于我们启动服务所使用的容器,比如tomcat,在其内部处理请求是通过线程池的方式。甚至现在的很多容器是基于netty的,都是通过线程池,复用线程来增加服务的并发量。
假设线程1持有没有被清除的page参数,不断调用同一个方法,后面两个请求使用的是线程2和线程3没有问题,再一个请求轮到线程1了,此时就会出现问题了。

解决方案

手动调用PageHelper.clearPage方法

public static void testPageHelper() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        CmpRecordMapper mapper = sqlSession.getMapper(CmpRecordMapper.class);
        Map<String,Object> params = new HashMap<>();
        params.put("createTimeBegin","2023-10-01 00:12:02");
        params.put("createTimeEnd","2023-11-01 00:12:02");
        PageHelper.startPage(1,2);
        List<CmpRecord> records = mapper.getCmpRecordBy(null);
        PageInfo<CmpRecord> datas = new PageInfo<>(records);
        //TODO 手动调用
        PageHelper.clearPage();
        System.out.println(datas.toString());
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1415411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

权威媒体报道 | 百分点科技谈“数据要素×”

近日&#xff0c;国家数据局等17部门联合印发《“数据要素”三年行动计划&#xff08;2024—2026年&#xff09;》&#xff0c;引起广泛关注&#xff0c;作为数据要素技术厂商代表&#xff0c;百分点科技CTO刘译璟接受经济日报、中国高新技术产业导报采访&#xff0c;结合产业现…

pysot中eval多种算法比较和画图

安装miktex和Texwork&#xff0c;记得更新miktex&#xff0c;链接https://miktex.org/download&#xff0c; 参考https://blog.csdn.net/weixin_42495721/article/details/110855071 我用的是pysot官方的库&#xff0c;里面包括eval和test、train等py文件。 路径结构为&#x…

【C++修行之道】STL(初识list、stack)

目录 一、list 1.1list的定义和结构 以下是一个示例&#xff0c;展示如何使用list容器: 1.2list的常用函数 1.3list代码示例 二、stack 2.1stack的定义和结构 stack的常用定义 2.2常用函数 2.3stack代码示例 一、list 1.1list的定义和结构 list的使用频率不高&#…

【JAVA】什么是自旋

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在计算机科学的领域中&#xff0c;多线程和并发编程已成为处理复杂任务和提高系统性能的不可或缺的手段。…

行测-言语:3.逻辑填空

行测-言语&#xff1a;3.逻辑填空&#xff08;刷题积累&#xff0c;国考省考&#xff09; 1. 词的辨析 1.1 词义侧重 C&#xff0c;心酸&#xff1a;内心难过&#xff1b;辛酸&#xff1a;辛苦&#xff1b;刻画&#xff1a;雕刻和绘画&#xff1b;塑造&#xff1b;描绘&#x…

MC3172 初探

感芯科技第一款32位 RISC处理器MC3172&#xff0c;业内首个64线程同步并行运行&#xff0c;线程资源可按需配置&#xff0c; 共享代码段空间与数据段空间&#xff0c;硬件级实时响应&#xff0c;无需中断服务程序&#xff0c;无需实时操作系统。 基于RISC-V RV32IMC 指令集&…

STM32实现软件IIC协议操作OLED显示屏(2)

时间记录&#xff1a;2024/1/27 一、OLED相关介绍 &#xff08;1&#xff09;显示分辨率128*64点阵 &#xff08;2&#xff09;IIC作为从机的地址0x78 &#xff08;3&#xff09;操作步骤&#xff1a;主机先发送IIC起始信号S&#xff0c;然后发送OLED的地址0x78&#xff0c;然…

java servlet运输公司管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web运输公司管理系统是一套完善的java web信息管理系统 serlvetdaobean mvc 模式开发 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主 要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5…

Linux服务器配置与管理(第二次实验)

实验目的及具体要求 目的 1.掌握基于命令行的文件操作 2.掌握基于命令行的目录操作 3.掌握用户账户的命令行操作 4.掌握组账户的命令行操作 5.熟悉磁盘分区操作 6.掌握调整优先级的方法 具体要求 1.掌握基于命令行的文件和目录操作 ①创建测试目录 ②创建文件 ③复…

3DGS 其二:Street Gaussians for Modeling Dynamic Urban Scenes

3DGS 其二&#xff1a;Street Gaussians for Modeling Dynamic Urban Scenes 1. 背景介绍1.1 静态场景建模1.2 动态场景建模 2. 算法2.1 背景模型2.2 目标模型 3. 训练3.1 跟踪优化 4. 下游任务 Reference&#xff1a; Street Gaussians for Modeling Dynamic Urban Scenes 1.…

JavaGUI之SWT框架【Button】

文章目录 1. 按钮类型1.1 普通按钮1.2 单选按钮1.3 多选按钮 2. 文字风格3. 按钮外观风格 录制的视频 按钮Button&#xff0c;SWT框架中常见的组件。针对Button的设置分为三个层面&#xff0c;分别是按钮类型&#xff0c;按钮文字对齐风格&#xff0c;按钮外观风格 1. 按钮类型…

SpringBoot将第三方的jar中的bean对象自动注入到ioc容器中

新建一个模块&#xff0c;做自动配置 config&#xff1a;需要准备两个类&#xff0c;一个自动配置类&#xff0c;一个配置类 CommonAutoConfig&#xff1a;此类用于做自动配置类它会去读取resoutces下的META-INF.spring下的org.springframework.boot.autoconfigure.AutoConfig…

NoSQL基本内容

第一章 NoSQL 1.1 什么是NoSQL NoSQL&#xff08;Not Only SQL&#xff09;即不仅仅是SQL&#xff0c;泛指非关系型的数据库&#xff0c;它可以作为关系型数据库的良好补充。随着互联网web2.0网站的兴起&#xff0c;非关系型的数据库现在成了一个极其热门的新领域&#xff0c;…

苹果macOS 恶意软件家族被曝光:通过破解软件分发,可窃取敏感信息

卡巴斯基安全实验室近日发布博文&#xff0c;发现了一种针对苹果 macOS 设备的新型恶意软件家族&#xff0c;并提醒苹果 Mac 用户谨慎下载破解软件。 报告称这种新型恶意软件家族高度复杂&#xff0c;主要伪装成为各种知名 macOS 软件的破解版分发&#xff0c;用户下载恶意 PKG…

InitVerse:为云计算服务带来更高的透明度和可验证性

InitVerse&#xff1a;为云计算服务带来更高的透明度和可验证性 在云计算服务领域&#xff0c;透明度和可验证性是构建信任的关键要素。传统的云计算市场往往缺乏透明度&#xff0c;用户难以了解其数据和计算资源的实际使用情况。然而&#xff0c;通过利用区块链技术&#xff0…

2024.1.27 GNSS 学习笔记

1.精确的描述轨道的一组数据(星历)是实现精确定位与导航的基础。 2.GNSS卫星广播星历的提供方式一般有两种&#xff1a;一种是提供开普勒轨道参数和必要的轨道摄动改正项参数&#xff0c;如GPS、BDS、Galileo三大系统采用此种模式&#xff0c;还有QZSS系统&#xff1b;另一种是…

苹果提审被拒反馈崩溃日志.text | iOS 审核被拒crashLog

iOS审核人员拒绝后每个截图&#xff0c;只给了几个text文件&#xff0c;这种情况就是审核的时候运行你的代码&#xff0c;崩溃了。 仅仅看text文件&#xff0c;是看不出所以然来的&#xff0c;所以我们要将日志转换成.crash格式 1.将.text文件下载下来&#xff0c;将 .text手动…

快快销ShopMatrix 分销商城多端uniapp可编译5端-代理商收益管理:差价奖励和销售额统计

代理商收益管理是一种针对代理商的利润分配模式&#xff0c;主要通过差价奖励和销售额统计来实现。这种模式的核心思想是通过激励代理商的销售行为&#xff0c;提高代理商的积极性和销售效率&#xff0c;从而实现整个销售网络的增长。 差价奖励是代理商收益管理中的一种常见方…

Pyecharts 风采:从基础到高级,打造炫酷象形柱状图的完整指南【第40篇—python:象形柱状图】

文章目录 引言安装PyechartsPyecharts象形柱状图参数详解1. Bar 类的基本参数2. 自定义图表样式3. 添加标签和提示框 代码实战&#xff1a;绘制多种炫酷象形柱状图进阶技巧&#xff1a;动态数据更新与交互性1. 动态数据更新2. 交互性设计 拓展应用&#xff1a;结合其他图表类型…

MySQL的外键和连接,如何做到关联查询?

目录 一、MySQL介绍 二、什么是外键 三、什么是连接 四、如何实现关联查询 一、MySQL介绍 MySQL是一种开源的关系型数据库管理系统&#xff0c;它是目前最流行的数据库之一。MySQL由瑞典MySQL AB公司开发&#xff0c;后被Sun Microsystems收购&#xff0c;随后又被Oracle收…