mybatis-plus技巧--动态表名-多语句-拼接sql--关于mybatis的mysql分页查询总数的优化思考

news2025/1/14 18:30:36

文章目录

  • 动态表名
    • xml表名填充
    • 表名拦截器
      • 每天按统计
      • 每次设置
  • 多语句操作
    • forEach动态拼接
  • 参数构建
  • java进行拼接sql
  • mysql分页查询总数
    • count
      • 不要使用count(常数),count(列名)代替count(*)
      • 自己计数
    • SQL_CALC_FOUND_ROWS
      • xml单条接口实现
      • mybatis拦截器

动态表名

xml表名填充

<select>
select *
from ${tableName}
</select>

传入tableName参数就可以了,不过只能用$不能用#

因为#会发生预编译,然后会在表名上加引号’'。

表名拦截器

新建一个表名拦截类实现TableNameHandler

@Component
public class MyTableHandler implements TableNameHandler {
    @Override
    public String dynamicTableName(String sql, String tableName) {
    	// 表名处理
        return tableName;
    }
}

mybatisPlus添加插件

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //1.添加动态表名插件
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
        dynamicTableNameInnerInterceptor.setTableNameHandler(new MyTableHandler());
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        return interceptor;
    }

实例:

每天按统计

如果表名为count则加上今天的时间

@Component
public class MyTableHandler implements TableNameHandler {
    @Override
    public String dynamicTableName(String sql, String tableName) {
    	// 表名处理
        if (StringUtils.equals(tableName,"count")){
            return tableName+ LocalDate.now();
        }
        return tableName;
    }
}

每次设置

@Component
public class MyTableHandler implements TableNameHandler {

    // 每个请求线程维护一个数据,避免多线程数据冲突。所以使用ThreadLocal
    private static final ThreadLocal<String> SUFFIX = new ThreadLocal<>();

    // 设置请求线程的month数据
    public static void setData(String suffix) {
        SUFFIX.set(suffix);
    }

    @Override
    public String dynamicTableName(String sql, String tableName) {
        String suffix = SUFFIX.get();
        if(StringUtils.isNotBlank(suffix)){
            SUFFIX.remove();
            return suffix;
        }
        return tableName;
    }
}

直接设置名字,然后就会改变的。

   MyTableHandler.setData(tableName);
   list();

多语句操作

需要在配置文件中的url中新增条件允许多查询allowMultiQueries=true

在xml中多条sql用;隔开,就可以多条sql同时发送执行。

forEach动态拼接

传递List<Student>的参数

<update id="updateBatchById">
    <foreach collection="list" item="s" separator=";">
        update
            `user`
        set
            `name` = #{name},
            `salary` = #{salary}
        where
            id = #{id}
    </foreach>
</update>

参数构建

java进行拼接sql

缺点,没有ide提示,容易敲错

mapper参数传递 @Param(“ew”) LambdaQueryWrapper
param中只能是ew不能是其他的。

在xml中使用:

    <select id="getAllInfo" resultType="com.yu.model.domain.Company">
        select *
        from table
        ${ew.customSqlSegment}
    </select>

ew有多个属性,${ew.sqlSegment},${ew.sqlSelect},${ew.customSqlSegment}

属性介绍
customSqlSegment等同where+sql
sqlSegment等于sql不能用lambda的查询了
sqlSet需要更新的 update tableName set ${ew.sqlSet} where ${ew.sqlSegment}
sqlSelectselect参数, @Select(select ${ew.sqlSelect} from a )

mysql分页查询总数

count

不要使用count(常数),count(列名)代替count(*)

由于count不会统计null值,count(列名)可能会导致总数出错。
又因为COUNT(*)是SQL92定义的标准统计行数的语法,因为他是标准语法,所以MySQL数据库对他进行过很多优化。

自己计数

自己维护计数,如存入redis。比较麻烦但是高效。

SQL_CALC_FOUND_ROWS

我觉得如果有大量用这个更好。

SQL_CALC_FOUND_ROWS是MySQL的一个特殊修饰符,用于在执行SELECT查询时同时计算满足条件的总行数。

每次分页都需要count一次,就像mybatis-plus也是在查询之前先进行count一次,如果total不为0在进行下一轮查询。
SQL_CALC_FOUND_ROWS 语句会统计出符合筛选条件的记录总数,保存在mysql 端;

后面使用 SELECT FOUND_ROWS() 语句可以得到这个数字,不用再次计算。当然,如果你再次运行了SQL_CALC_FOUND_ROWS的话,这个数字就会被覆盖成最新的。

不过呢,这个东西说好的和不好的都有。也没有个说法,但是我在实际中可以知道,在某些情况下肯定是有比count快的。
我在sql也不是特别懂,我只能通过实践来进行了,哪个快用哪个。这个呢不太好说。

它的原理是在执行SELECT查询时,MySQL会先执行一次不带LIMIT子句的查询来计算满足条件的总行数,然后再执行带LIMIT子句的查询来返回实际的结果集。这样可以避免在查询结果集之前进行一次COUNT(*)查询,从而提高查询的性能。

使用SQL_CALC_FOUND_ROWS修饰符的查询语句的执行速率取决于满足条件的总行数和实际返回的结果集的大小。如果总行数很大,而实际返回的结果集较小,那么执行速率可能会比较慢。反之,如果总行数和实际返回的结果集大小相差不大,执行速率可能会比较快。

需要注意的是,使用SQL_CALC_FOUND_ROWS修饰符会增加查询的开销,因为MySQL需要执行两次查询。因此,在实际应用中,需要根据具体情况权衡使用SQL_CALC_FOUND_ROWS的优势和开销。

测试中SQL_CALC_FOUND_ROWS 确实会导致第一次查询变慢,但是得到总数快。2条sql进行查询,有时候会快点有时候慢点。

xml单条接口实现

<select id="selectList" resultMap="User,count">
SELECT SQL_CALC_FOUND_ROWS user_id,user_name
FROM user
LIMIT 10;
SELECT FOUND_ROWS() AS total;
</select>

mapper

List selectList();

servies

List<Object> list= baseMapper.selectList();
List<User> user = list.get(0);
Integer total = list.get(1);

这样,每次都需要list接收,一个数据一个是总数。

mybatis拦截器

我们来看一下对比图,这是mybatis-plus自带的分页插件,吞吐量11
在这里插入图片描述
在这里插入图片描述
下面是自定义的分页插件,吞吐量17,很明显快一些,当然小数据量/单表的时候速度和count差不多,主要在于多表数据量大的时候提升效果显著。
在这里插入图片描述
在这里插入图片描述
至于准确率方便,设置有响应断言,都没有异常,可以保证准确率
在这里插入图片描述

实现:
也可以当mybatis插件的一个练习吧,借鉴了mybatis-plus的分页插件。
依据是否存在page作为参数来进行是否进行分页查询,这个条件也可以依据自己的更换。

@Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        try {
            if (target instanceof Executor executor) {
                Object[] args = invocation.getArgs();
                Object parameter = args[1];
                // 处理参数为 IPage 的情况
                IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);

                if (null == page) {
                    return invocation.proceed();
                }
                boolean isUpdate = args.length == 2;
                MappedStatement ms = (MappedStatement) args[0];

                if (!isUpdate && ms.getSqlCommandType() == SqlCommandType.SELECT) {
                    RowBounds rowBounds = (RowBounds) args[2];
                    BoundSql boundSql = ms.getBoundSql(parameter);

                    // 处理 page中 orderBy 拼接
                    boolean addOrdered = false;
                    String buildSql = boundSql.getSql();
                    List<OrderItem> orders = page.orders();
                    if (CollectionUtils.isNotEmpty(orders)) {
                        addOrdered = true;
                        buildSql = this.concatOrderBy(buildSql, orders);
                    }

                    // size 小于 0 且不限制返回值则不构造分页sql
                    Long _limit = page.maxLimit() != null ? page.maxLimit() : 1000;
                    if (addOrdered) {
                        PluginUtils.mpBoundSql(boundSql).sql(buildSql);
                    }

                    handlerLimit(page, _limit);
                    IDialect dialect = DialectFactory.getDialect(DbType.MYSQL);


                    final Configuration configuration = ms.getConfiguration();
                    DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());
                    PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
                    List<ParameterMapping> mappings = mpBoundSql.parameterMappings();
                    Map<String, Object> additionalParameter = mpBoundSql.additionalParameters();
                    model.consumers(mappings, configuration, additionalParameter);
                    // 加入SQL_CALC_FOUND_ROWS
                    String selectSqlCalcFoundRows = model.getDialectSql()
                            .replaceFirst("(?i)SELECT", "SELECT SQL_CALC_FOUND_ROWS ");
                    mpBoundSql.sql(selectSqlCalcFoundRows);
                    mpBoundSql.parameterMappings(mappings);
              
                    CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
                    Connection connection = executor.getTransaction().getConnection();
                    Statement statement = connection.createStatement();
                    // 查询数据 95%
                    List<Object> query = executor.query(ms, parameter, rowBounds, (ResultHandler) args[3], cacheKey, boundSql);
                    // 查询总数 占速率5%
                    ResultSet resultSet = statement.executeQuery("SELECT FOUND_ROWS() AS total;");
                    while (resultSet.next()) {
                        String column = resultSet.getString(1);
                        page.setTotal(Long.parseLong(column));
                    }
                    return query;
                }
            }
        }catch (Exception e){
            log.error("分页失败优化失败,原因:{}",e.getMessage());
            // 打印本次调用的方法和参数
            log.error("本次调用的方法为:{}",invocation.getMethod());
            log.error("本次调用的参数为:{}",invocation.getArgs());
        }
        return invocation.proceed();
    }
    protected List<OrderByElement> addOrderByElements(List<OrderItem> orderList, List<OrderByElement> orderByElements) {
        List<OrderByElement> additionalOrderBy = orderList.stream()
                .filter(item -> StringUtils.isNotBlank(item.getColumn()))
                .map(item -> {
                    OrderByElement element = new OrderByElement();
                    element.setExpression(new Column(item.getColumn()));
                    element.setAsc(item.isAsc());
                    element.setAscDescPresent(true);
                    return element;
                }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(orderByElements)) {
            return additionalOrderBy;
        }
        // github pull/3550 优化排序,比如:默认 order by id 前端传了name排序,设置为 order by name,id
        additionalOrderBy.addAll(orderByElements);
        return additionalOrderBy;
    }

    /**
     * 处理超出分页条数限制,默认归为限制数
     *
     * @param page IPage
     */
    protected void handlerLimit(IPage<?> page, Long limit) {
        final long size = page.getSize();
        if (limit != null && limit > 0 && (size > limit || size < 0)) {
            page.setSize(limit);
        }
    }

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

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

相关文章

左偏树学习笔记

定义 堆&#xff0c;是一棵树&#xff0c;且每个节点的键值都大于等于 / 小于其父亲的键值。 左偏树是一种可合并的堆&#xff0c;可以以 O ( log ⁡ n ) O(\log n) O(logn) 的复杂度实现合并。 性质 左偏树满足堆的性质。 我们设定一个值 dist \text{dist} dist&#xf…

学习笔记|配对设计卡方检验|配对及二分类变量|McNemar检验|规范表达|《小白爱上SPSS》课程:SPSS第十七讲 | 配对设计卡方检验怎么做?

目录 学习目的软件版本原始文档配对设计卡方检验一、实战案例读数据 二、统计策略三、SPSS操作四、结果解读第一&#xff0c;卡方检验结果第二&#xff0c;分析统计结果 五、规范报告1、规范表格2、规范文字 学习目的 SPSS第十七讲 | 配对设计卡方检验怎么做&#xff1f; 软件…

如何在嵌入式软件开发的过程中使用DevSecOps方法,提升开发效率与安全性

DevOps可以帮助软件开发和IT从僵化的瀑布式开发脱离出来&#xff0c;转为更灵活的敏捷开发&#xff0c;使开发团队能够更快地解决问题、降低代码复杂性并加快产品交付。 既然DevOps有这么多的好处&#xff0c;那么对于希望确保软件开发过程安全的开发团队来说&#xff0c;下一…

学习笔记三十四:Ingress和 Ingress Controller概述

Ingress和 Ingress Controller概述 回顾service四层负载在k8s中为什么要做负载均衡Service不足之处四层负载和七层负载的区别OSI七层模型&#xff1a; Ingress介绍Ingress Controller介绍Ingress-controller 作用Ingress和Ingress Controller总结使用Ingress Controller代理k8s…

学习笔记|多组率卡方检验和Fisher确切法|个案加权|规范表达|《小白爱上SPSS》课程:SPSS第十六讲 | 多组率卡方检验和Fisher确切法

目录 学习目的软件版本原始文档多组率卡方检验和Fisher确切法一、实战案例二、统计策略三、SPSS操作1、个案加权2、卡方检验 四、结果解读第一&#xff0c;分组统计描述结果第二&#xff0c;卡方检验。 五、规范报告1、规范表格2、规范文字 六、划重点 学习目的 SPSS第十六讲 …

java JUC并发编程 第十章 Synchronized与锁升级

系列文章目录 第一章 java JUC并发编程 Future: link 第二章 java JUC并发编程 多线程锁: link 第三章 java JUC并发编程 中断机制: link 第四章 java JUC并发编程 java内存模型JMM: link 第五章 java JUC并发编程 volatile与JMM: link 第六章 java JUC并发编程 CAS: link 第七…

vivado 报错之procedural assignment to a non-register result is not permitted“

文章目录 这个错误通常是由于尝试在非寄存器类型的对象上进行过程赋值所引起的。在 Verilog 中&#xff0c;当使用 always 块时&#xff0c;其中的赋值操作应该只用于寄存器类型的变量&#xff0c;比如 reg 类型。非寄存器类型的信号&#xff08;比如 wire&#xff09;不能在 a…

如何将苹果手机照片导出?教你3个导出照片的必备技巧!

照片是我们记录生活&#xff0c;以及留下美好瞬间的最佳方式之一。通过手机照片&#xff0c;我们可以随时随地回忆过去的点点滴滴&#xff0c;还能将其分享给朋友和家人。因此&#xff0c;照片对于大家来说具有不可替代的价值与意义。 为了防止手机照片丢失&#xff0c;部分小…

WPF布局控件之WrapPanel布局

前言&#xff1a;博主文章仅用于学习、研究和交流目的&#xff0c;不足和错误之处在所难免&#xff0c;希望大家能够批评指出&#xff0c;博主核实后马上更改。 概述&#xff1a; 后续排序按照从上至下或从右至左的顺序进行&#xff0c;具体取决于方向属性的值。WrapPanel 位…

beego模板解析报错

文章目录 前言解决beego解析问题总结 前言 网上搜索为模板解析路径问题&#xff0c;实际是beego解析vue打包后的index.html出现错误&#xff0c; 比如解决时排除了.go代码&#xff0c;发现没问题&#xff0c;运行beego打印,打开浏览器进入web时发现wen打不开&#xff0c;并在b…

支付宝本地生活团购服务商如何申请?两个方法教给你

支付开宝的本地生活来了&#xff01;按支付宝财大气粗的做法&#xff0c;它一旦要推什么项目&#xff0c;那自然会在前期疯狂洒钱&#xff0c;以求通过这种模式快速占领市场。 所以&#xff0c;这次支付宝要推本地生活项目&#xff0c;这一贯做法自然得跟上&#xff0c;只是这…

2024上海国际人工智能展(CSITF)“创新驱动发展·科技引领未来”

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;作为当今世界科技发展的关键领域之一&#xff0c;正不断推动着各行各业的创新和变革。作为世界上最大的消费市场之一&#xff0c;中国正在积极努力将AI技术与产业融合并加速推广应用。在这个背景下&…

基于Chirp窄带扩频技术的无线混合组网应用,以多角色智能计量插座作为Chirp广域基站,构建边缘计算混合无线网络

随着物联网&#xff08;IoT&#xff09;的不断发展&#xff0c;无线通信技术的需求也在不断增加。Chirp窄带扩频技术是一种具有广泛应用潜力的无线通信技术&#xff0c;它在低功耗、广域覆盖、抗干扰等方面具备独特的优势。本文介绍了如何利用磐启微Chirp技术构建ECWAN无线混合…

混合式ANC主动降噪耳机系统设计(含C源代码)

混合式ANC主动降噪耳机系统设计(含C源代码) 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送语音信号处理降噪算法,蓝牙音频,DSP音频项目核心开发资料, 1 FF信号链路与FB 链路算法处理上一样 X(n)为噪声输…

强化学习的动态规划

一、动态规划 动态规划&#xff08;DP&#xff09;一词指的是一系列算法&#xff0c;这些算法可用于在给定环境的完美模型作为马尔可夫决策过程&#xff08;MDP&#xff09;的情况下计算最优策略。经典的DP算法在强化学习中具有有限的实用性&#xff0c;既因为其对完美模型的假…

【ElasticSearch系列-04】ElasticSearch的聚合查询操作

ElasticSearch系列整体栏目 内容链接地址【一】ElasticSearch下载和安装https://zhenghuisheng.blog.csdn.net/article/details/129260827【二】ElasticSearch概念和基本操作https://blog.csdn.net/zhenghuishengq/article/details/134121631【三】ElasticSearch的高级查询Quer…

迅为iTOP-RK3568开发板npu手册更新

iTOP -RK3568开发板使用教程更新&#xff0c;后续资料会不断更新&#xff0c;不断完善&#xff0c;帮助用户快速入门&#xff0c;大大提升研发速度。 为了满足人工智能的需要&#xff0c;去年&#xff0c;迅为基于RK3568开发板编写了对应的手册文档>>>【资料上新】基…

K7系列FPGA进行FLASH读写1——CCLK控制(STARTUPE2原语)

最近的工作涉及对 FPGA 进行远程更新&#xff0c;也就是通过远程通信接口将 .bin 文件送到 FPGA&#xff0c;然后写入 FLASH&#xff0c;这样当 FPGA 重新上电后就可以执行更新后的程序了。因此第一步工作就是进行 FLASH 的读写控制。 然而如果尝试配置 FLASH 管脚时&#xff0…

队列、循环队列和双端队列

目录 1、队列 1.1 概念 2.2 队列的使用 2.3 队列模拟实现 2、循环队列 2.1 循环队列的认识 2.2 设计循环队列 3. 双端队列 (Deque) 1、队列 1.1 概念 队列 &#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列…

Python之Excel——复制一个sheet当做模板,生成多个sheet

目录 专栏导读背景思路1、加载模板2、项目文件2、完整版代码:3、视频演示:4、总结:&#x1f44d; 该系列文章专栏&#xff1a;[Python办公自动化专栏] 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&…