PageHelper学习使用

基于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());
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM 32 SPI 调试的问题

问题&#xff1a;STM32G0 系列 SPI单线与AD7694通信采集数据&#xff0c;使用stm32cube 生成的HAL库编写 刚开始HAL_SPI_Receive&#xff0c;执行到这句时&#xff0c;一直卡在这里不在往下执行。 最终发现&#xff0c;我使用正点原子的 sysytick 延时函数delay_us, 与这里用…

【数据类型转换】C语言中的数据类型转换

1.定义 数据类型转换&#xff0c;听这个名字你就懂了&#xff0c;就是将数据从一种类型转换为另一种类型。 2.自动类型转换 自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换&#xff0c;这种转换不需要程序员干预&#xff0c;会自动发生。比如说&#xff1a…

链表/双向循环链表(C/C++)

本篇将给出双向循环链表的有关操作&#xff0c;以及对应的一些解释&#xff0c;大多都以注释给出。本篇给出的双向循环链表全称为带头双向循环链表。即如下图所示&#xff1a; 在本篇中一共包含三个代码片段&#xff0c;分别为&#xff1a;双向链表需要实现的内容、双向链表函数…

JS进阶-解构赋值(一)

扩展&#xff1a;解构赋值时Js特有的一种处理数据的方式&#xff0c;在Java中没有处理数据的方式 知识引入&#xff1a; 思考&#xff1a;在js中&#xff0c;在没有学习解构赋值之前&#xff0c;我们是如何获取数组的内容的&#xff1f; 以上要么不好记忆&#xff0c;要么书写麻…

gitlab备份-迁移-升级方案9.2.7升级到15版本最佳实践

背景 了解官方提供的版本的升级方案 - GitLab 8: 8.11.Z 8.12.0 8.17.7 - GitLab 9: 9.0.13 9.5.10 9.2.7 - GitLab 10: 10.0.7 10.8.7 - GitLab 11: 11.0.6 11.11.8 - GitLab 12: 12.0.12 12.1.17 12.10.14 - GitLab 13: 13.0.14 13.1.11 13.8.8 13.12.15 - G…

利用nginx宝塔免费防火墙实现禁止国外IP访问网站

本章教程&#xff0c;主要介绍&#xff0c;如何利用nginx宝塔面板中的插件免费防火墙&#xff0c;实现一键禁止国外IP访问网站。 目录 一、安装宝塔插件 二、 开启防火墙 一、安装宝塔插件 在宝塔面板中的软件商店&#xff0c;搜索防火墙关键词&#xff0c;找到Nginx免费防火…

详解STP生成树

华子目录 前沿导入二层环路导致问题&#xff1a; 生成树802.1D 角色选举根网桥根端口指定端口非指定端口&#xff08;阻塞端口&#xff09; cost值接口状态down侦听学习转发阻塞 收敛时间当结构发生变化时 802.1D缺点802.1D配置命令PVSTPVST快速生成树RSTP基于组的快速生成树MS…

Docker数据管理

管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&#xff09;。 在生成容器的同时&#xff0c;加上 -v 选项&#xff0c;指定把当前服务器的目录映射到容器中&#xff0c;实现do…

单核QPS近6000S,陌陌基于OceanBase的持久化缓存探索与实践

挚文集团于 2011 年 8 月推出了陌陌&#xff0c;这款立足地理位置服务的开放式移动视频社交应用在中国社交平台领域内独树一帜。陌陌和探探作为陌生人社交领域的主流应用&#xff0c;涵盖了多种核心业务模块&#xff0c;包括直播服务、附近动态功能、即时通讯&#xff08;IM&am…

亚马逊测评:自养号测评的深度解析与策略

亚马逊测评对于卖家来说至关重要&#xff0c;特别是在当今竞争激烈的电商环境中。然而&#xff0c;许多人对亚马逊测评的理解仅停留在刷销量和积累好评的层面&#xff0c;忽视了自养号测评的其他重要功能。本文将深入探讨自养号测评的多种功能&#xff0c;以及如何建立稳定、高…

用graalvm将maven项目打包成可执行文件

概述&#xff1a;配置graalvm或者用graalvm打包springboot项目请看下面文章&#xff1a; Springboot3新特性&#xff1a;开发第一个 GraalVM 本机应用程序(完整教程)-CSDN博客 废话不多说&#xff0c;咱们开始用GraalVM打包maven项目。 第一步&#xff1a;引入依赖和插件 p…

mac 修改flutter sdk配置

问题描述&#xff1a;我mac电脑上有高低2个版本的flutter sdk&#xff0c;我需要低版本sdk的项目在setting里设置了sdk版本&#xff0c;可是命令行还是提示我版本过高。 直接上解决办法&#xff1a; 打开mac终端&#xff0c;输入open -e .bash_profile&#xff0c;然后修改下…

06.搭建一个自己的私有仓库-Gitea

06.搭建一个自己的私有仓库-Gitea | DLLCNX的博客 如果你是一位程序员或者IT相关领域的从业者&#xff0c;那么肯定知道git&#xff0c;而且也或多或少接触了不少开源仓库以及公司的私有仓库&#xff0c;但是我们有没有想过自己也搭建一个私有仓库呢。 这么多开源仓库&#xf…

Goodbye! Xshell、iTerm2、FinalShell,mobaxterm,新一代开源免费的终端工具真香!

前言 众所周知&#xff0c;在数字化时代&#xff0c;远程连接成为工作和管理中不可或缺的一环。 而在这个领域&#xff0c;SSH&#xff08;Secure Shell&#xff09;一直是最常用的协议之一&#xff0c;为远程管理提供了安全的通信渠道。 然而&#xff0c;伴随着技术的发展和…

docker 体验怀旧游戏(魂斗罗等)

docker run --restart always -p 8081:80 --name fc-games -d registry.cn-hangzhou.aliyuncs.com/bystart/fc-games:latest ip:8081访问 jsnes: js制作了一个网页版的NES模拟&#xff0c;可以在网页上玩fc游戏 (gitee.com)

Unity中URP下计算额外灯的方向

文章目录 前言一、为什么额外灯的方向&#xff0c;不像主平行灯一样直接获取&#xff1f;1、主平行灯2、额外灯中&#xff0c;包含 点光源、聚光灯 和 平行灯 二、获得模型顶点指向额外灯的单位向量三、Unity中的实现 前言 在上一篇文章中&#xff0c;我们获取了URP下额外灯的…

探索Gin框架:快速构建高性能的Golang Web应用

前言 Gin框架是一个轻量级的Web框架&#xff0c;基于Go语言开发&#xff0c;旨在提供高性能和简洁的API。它具有快速的路由和中间件支持&#xff0c;使得构建Web应用变得更加简单和高效。无论是构建小型的API服务还是大型的Web应用&#xff0c;Gin框架都能够满足你的需求。 无论…

vivado I/O和时钟规划设计流程步骤

I/O和时钟规划设计流程步骤 下图显示了左侧的项目设计流程步骤。水平箭头表示项目设计流程中可以执行I/O和时钟规划的点。中的步骤I/O和时钟规划设计流程如右图所示。 项目设计流程从一个空的I/O规划项目、RTL设计项目或合成后网表项目。使用这些项目类型中的任何一种&#xf…

【江科大】STM32:USART串口(理论部分)上

串口 全双工&#xff1a;可以进行同步通信 单端信号&#xff1a;信号线传输的就是单端信号。&#xff08;也就是与地线&#xff08;GND&#xff09;的电势差&#xff09; 缺点&#xff1a;防干扰能力差 原因&#xff1a;当信号从A点传输到B点&#xff0c;理想条件是A&#xff0…

Unity 解决异步分发方案

很多程序&#xff0c;包括游戏、小程序、一些AR、VR的程序&#xff0c;因为客户端体量太大&#xff0c;更新频繁都涉及到远程热更新的问题&#xff0c;解决这类问题的思路基本上是客户端解决主要功能&#xff0c;资源类放置在服务器。 下面记录下&#xff1a; 1.CDN或者云轻量…