【手撕Mybatis的分页插件】【查询结果集是0,直接返回[]】【提高查询我们的性能】

简介: 【手撕Mybatis的分页插件】【查询结果集是0,直接返回[]】【提高查询我们的性能】

正文


简介今天看了最新版本的《Java开发手册》,发现了一个有趣的事:【强制】代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。


情景介绍#


当前,我们的项目分页采用的是自定义的Mybatis的plugin,也就是自己写的一个分页组件(ps:感觉现成的不好用,遂自己写)

分页组件代码:


/**
 * @author :breakpoint/赵立刚
 * @date : 2019/11/10
 */
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PagePlugin extends AbstractPlugin implements Interceptor {
    // 数据库的方言
    private static String dialect = "";
    // id的正则表达式
    private static String pageSqlId = "";
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        Method method = invocation.getMethod();
        if (target instanceof RoutingStatementHandler) {
            RoutingStatementHandler statementHandler = (RoutingStatementHandler) target;
            BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper.getValueByFieldName(statementHandler, "delegate");
            MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getValueByFieldName(delegate, "mappedStatement");
            // 专门匹配需要分页的查询  有的查询是不需要分页的
            if (mappedStatement.getId().matches(pageSqlId)) {
                log.info("开始分页查询 start");
                BoundSql boundSql = delegate.getBoundSql();
                Object parameterObject = boundSql.getParameterObject();
                if (parameterObject == null) {
                    throw new NullPointerException("parameterObject error");
                } else {
                    Connection connection = (Connection) invocation.getArgs()[0];
                    String sql = boundSql.getSql();
                    String countSql = "select count(0) from (" + sql + ") myCount";
                    log.info("总数sql 语句:{}", countSql);
                    PreparedStatement countStmt = connection.prepareStatement(countSql);
                    BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
                    setParameters(countStmt, mappedStatement, countBS, parameterObject);
                    ResultSet rs = countStmt.executeQuery();
                    int count = 0;
                    if (rs.next()) {
                        count = rs.getInt(1);
                    }
                    rs.close();
                    countStmt.close();
                    PageInfo pageInfo = null;
                    // 设置结果的返回值
                    if (parameterObject instanceof PageInfo) {
                        pageInfo = (PageInfo) parameterObject;
                        pageInfo.setPageTotalByTotalCount(count);
                    } else if (parameterObject instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) parameterObject;
                        pageInfo = (PageInfo) map.get("pageInfo");
                        if (pageInfo == null)
                            pageInfo = new PageInfo();
                        pageInfo.setPageTotalByTotalCount(count);
                    } else {
                        Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "pageInfo");
                        if (pageField != null) {
                            pageInfo = (PageInfo) ReflectHelper.getValueByFieldName(parameterObject, "pageInfo");
                            if (pageInfo == null)
                                pageInfo = new PageInfo();
                            pageInfo.setPageTotalByTotalCount(count);
                            ReflectHelper.setValueByFieldName(parameterObject, "pageInfo", pageInfo);
                        } else {
                            throw new NoSuchFieldException(parameterObject.getClass().getName());
                        }
                    }
                    // 具有结果集合
                    if (count > 0) {
                        setHaveData(true);
                    } else {
                        setHaveData(false);
                    }
                    String pageSql = generatePageSql(sql, pageInfo);
                    log.info("pageSql={}", pageSql);
                    ReflectHelper.setValueByFieldName(boundSql, "sql", pageSql);
                }
            }
        }
        log.info("target={}", target);
        log.info("method={}", method);
        return invocation.proceed();
    }
    /**
     * 生成分页的sql
     *
     * @param sql
     * @param page
     * @return
     */
    private String generatePageSql(String sql, PageInfo page) {
        if (page != null && (dialect != null || !dialect.equals(""))) {
            StringBuffer pageSql = new StringBuffer();
            if ("mysql".equals(dialect)) {
                pageSql.append(sql);
                pageSql.append(" limit " + page.getDataStart() + "," + page.getPageSize());
            } else if ("oracle".equals(dialect)) {
                pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
                pageSql.append(sql);
                pageSql.append(")  tmp_tb where ROWNUM<=");
                pageSql.append(page.getDataEnd());
                pageSql.append(") where row_id>=");
                pageSql.append(page.getDataStart());
            }
            return pageSql.toString();
        } else {
            return sql;
        }
    }
    /**
     * 设置参数操作
     *
     * @param ps
     * @param mappedStatement
     * @param boundSql
     * @param parameterObject
     * @throws SQLException
     */
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            Configuration configuration = mappedStatement.getConfiguration();
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    PropertyTokenizer prop = new PropertyTokenizer(propertyName);
                    if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
                        value = boundSql.getAdditionalParameter(prop.getName());
                        if (value != null) {
                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
                        }
                    } else {
                        value = metaObject == null ? null : metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    if (typeHandler == null) {
                        throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
                    }
                    typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
                }
            }
        }
    }
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
    /**
     * 设置属性
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        dialect = properties.getProperty("dialect");
        if (dialect == null || dialect.equals("")) {
            try {
                throw new PropertyException("dialect property is not found!");
            } catch (PropertyException e) {
                e.printStackTrace();
            }
        }
        pageSqlId = properties.getProperty("pageSqlId");
        if (pageSqlId == null || pageSqlId.equals("")) {
            try {
                throw new PropertyException("pageSqlId property is not found!");
            } catch (PropertyException e) {
                e.printStackTrace();
            }
        }
    }
}


我们看到,当我们结果集是0的时候,我设置了一个参数


// 具有结果集合
 if (count > 0) {
     setHaveData(true);
 } else {
    setHaveData(false);
}


这个方法在我们的抽象类里面:


package com.block.plugins;
/**
 * @author :breakpoint/赵立刚
 * @date : 2020/04/25
 */
public abstract class AbstractPlugin {
    private static ThreadLocal<Boolean> haveData = new ThreadLocal<>();
    protected Boolean isHaveData() {
        Boolean aBoolean = haveData.get();
        haveData.set(null);
        return aBoolean;
    }
    protected void setHaveData(Boolean have) {
        haveData.set(have);
    }
}


从上面的代码,我们可以看到,我们定义了一个ThreadLocal ,保证了在同一个请求上获取到我们的数据。

在另一个拦截器里面实现我们的业务逻辑


/**
 * @author :breakpoint/赵立刚
 * @date : 2020/04/25
 */
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})})
public class QueryResultPlugin extends AbstractPlugin implements Interceptor {
    // id的正则表达式
    private static String pageSqlId = "";
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Boolean haveData = isHaveData();
        if (null == haveData || haveData) {
            log.info("执行 返回执行结果");
            return invocation.proceed();
        } else {
            // 没有执行 直接返回
            log.info("没有执行 直接返回");
            return new ArrayList<>(16);
        }
    }
    @Override
    public Object plugin(Object target) {
        // 返回代理的对象
        return Plugin.wrap(target, this);
        //return target;
    }
    @Override
    public void setProperties(Properties properties) {
        pageSqlId = properties.getProperty("pageSqlId");
        if (pageSqlId == null || pageSqlId.equals("")) {
            try {
                throw new PropertyException("pageSqlId property is not found!");
            } catch (PropertyException e) {
                e.printStackTrace();
            }
        }
    }
}


这样我们就实现了他所说的情况!!!!!


运行结果#


1.jpg


相关文章
|
3天前
|
SQL Oracle 关系型数据库
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
|
2天前
|
SQL Java 数据库连接
微服务——MyBatis分页
本文介绍了分页的多种实现方式,包括自带RowBounds分页、第三方插件PageHelper分页、SQL分页、数组分页及拦截器分页。其中,RowBounds是先查询全部结果再内存分页;PageHelper通过修改SQL动态添加分页关键字;SQL分页依赖数据库自身的分页功能如`LIMIT`;数组分页则是查询全量数据后用`subList`方法截取;拦截器分页则统一在SQL后添加分页语句。最后总结逻辑分页适合小数据量,但大数据量易内存溢出;物理分页虽小数据量效率较低,但更适合大数据场景,优先推荐使用。
14 0
|
2天前
|
SQL Java 关系型数据库
MyBatis篇-分页
本文介绍了多种分页方式,包括自带rowbound内存分页、第三方插件pagehelper(通过修改SQL实现分页)、SQL分页(依赖limit或rownum等关键字)、数组分页(先查询全部数据再用subList分页)、拦截器分页(自定义拦截器为SQL添加分页语句)。最后总结了逻辑分页(内存分页,适合小数据量)和物理分页(直接在数据库层面分页,适合大数据量)的优缺点,强调物理分页优先于逻辑分页。
|
25天前
|
监控 安全 数据库
【YashanDB 知识库】Mybatis-Plus 调用 YashanDB 怎么设置分页
数据库状态分为正常与异常两种情况。当出现异常时,首先查看告警列表确认问题(如实例无法连接),并尝试用数据库用户名和密码登录。若能登录,说明主实例故障已切换至备库;若无法登录或为单节点,则需进一步排查。接着检查监控项,若有数据表明主实例故障,无数据则可能是通信中断。随后检查主机上的服务是否存在,若存在但通信受限,需排查安全设置或网络;若服务不存在,可能因重启或断电导致,需手动启动相关服务。最终在YashanDB列表中确认状态恢复。
|
5天前
|
SQL Java 数据库连接
MyBatis 实现分页的机制
MyBatis 的分页机制主要依赖于 `RowBounds` 对象和分页插件。`RowBounds` 实现内存分页,适合小数据量场景,通过设定偏移量和限制条数对结果集进行筛选。而针对大数据量,则推荐使用分页插件(如 PageHelper),实现物理分页。插件通过拦截 SQL 执行,动态修改语句添加分页逻辑,支持多种数据库方言。配置插件后,无需手动调整查询方法即可完成分页操作,提升性能与灵活性。
|
1月前
|
XML SQL Java
十二、MyBatis分页插件
十二、MyBatis分页插件
69 17
|
9天前
|
Oracle 关系型数据库 Java
|
24天前
|
Oracle 关系型数据库 MySQL
【YashanDB 知识库】Mybatis-Plus 调用 YashanDB 怎么设置分页
**Mybatis-Plus 自动分页配置问题简介** Mybatis-Plus 是 Mybatis 的增强工具,简化 CRUD 操作并适配多种数据库,包括 YashanDB。自动分页配置错误会导致应用开发受影响。解决方法:1. 配置 pagehelper 为 oracle 或 mysql;2. 设置分页拦截器为 oracle 或 mysql。确保返回设置后的对象。正确配置后可在 service 层使用 page 方法实现自动分页。
|
2月前
|
XML Java 数据库连接
Mybatis一对一,一对多关联查询
## MyBatis一对一、一对多关联查询详解 MyBatis是一款优秀的持久层框架,提供了灵活的SQL映射功能,支持复杂的数据库操作。本文将详细介绍MyBatis中一对一和一对多关联查询的实现。 ### 一对一关联查询 一对一关联关系指的是一个表中的一条记录与另一个表中的一条记录相关联。例如,一个用户有一个地址信息。 #### 数据库表设计 假设有两个表:`user`和 `address`。 ``` CREATE TABLE user ( id INT PRIMARY KEY, name VARCHAR(50) ); CREATE TABLE address
59 18
|
2月前
|
SQL Java 数据库连接
【潜意识Java】MyBatis中的动态SQL灵活、高效的数据库查询以及深度总结
本文详细介绍了MyBatis中的动态SQL功能,涵盖其背景、应用场景及实现方式。
184 6