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

文章目录

  • 动态表名
    • 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.mfbz.cn/a/115155.html

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

相关文章

性能优化之懒加载 - 基于观察者模式和单例模式的实现

一、引入 在前端性能优化中&#xff0c;关于图片/视频等内容的懒加载一直都是优化利器。当用户看到对应的视图模块时&#xff0c;才去请求加载对应的图像。 原理也很简单&#xff0c;通过浏览器提供的 IntersectionObserver - Web API 接口参考 | MDN (mozilla.org)&#xff0c…

JavaScript设计模式之责任链模式

适用场景&#xff1a;一个完整的流程&#xff0c;中间分成多个环节&#xff0c;各个环节之间存在一定的顺序关系&#xff0c;同时中间的环节的个数不一定&#xff0c;可能添加环节&#xff0c;也可能减少环节&#xff0c;只要保证顺序关系就可以。 如下图&#xff1a; ES5写法…

flutter之bloc使用详解

flutter中一切皆为Widget&#xff0c;因此在我们开发中&#xff0c;往往业务和UI逻辑写在一起&#xff0c;这样不利于代码维护&#xff0c;因此状态管理框架久诞生了&#xff0c;这篇就开始讲一讲Bloc。 对于Bloc库有两个&#xff0c;如下图&#xff1a; flutter_bloc其实是对…

进程控制(三):进程替换

文章目录 进程控制&#xff08;三&#xff09;进程替换进程替换函数进程中的环境变量 总结 进程控制&#xff08;三&#xff09; 进程控制中的进程替换&#xff0c;下文我们学习进程替换的意义&#xff0c;以及进程替换的方式 进程替换 初步认识进程替换&#xff0c;我们先使…

Spring Cloud应用- Eureka原理、搭建

初期对Spring Cloud的学习以应用搭建为主&#xff0c;所以内容不会太枯燥。 一直以来&#xff0c;自以为Spring全家桶的学习中&#xff0c;Spring framework是基础中的基础&#xff0c;部分内容也还是必须要读源码去理解底层原理&#xff0c;SpringMVC、SpringBoot&#xff0c…

【ElasticSearch系列-03】ElasticSearch的高级句法查询Query DSL

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

Linux上编译sqlite3库出现undefined reference to `sqlite3_column_table_name‘

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 在Ubuntu 18上编译sqlite3库后在运行程序时出现undefined reference to sqlite3_column_table_name’的错误。网上的说法是说缺少SQLITE_ENABLE_COLUMN_M…

解决ModuleNotFoundError: No module named ‘yaml‘

报错&#xff1a;ModuleNotFoundError: No module named yaml 使用&#xff1a; pip install yaml 仍然报错&#xff1a; 最终解决方案&#xff1a; pip install pyyaml 或者 conda install pyyaml

百度竞价排名推广对比自然排名哪一个更具优势-华媒舍

在搜索引擎结论网页页面&#xff08;SERP&#xff09;中&#xff0c;我们经常会看到一些网站链接及其广告栏。这种连接一般分为两种类型&#xff1a;百度竞价推广排名推广与自然排名。究竟哪个更有优势&#xff1f;本文将对这几种排名形式进行科谱详细介绍。 什么叫百度竞价推广…

右击显示Pycharm打开教程

效果图 操作流程 win r 输入 regedit 回车打开注册表编辑器 2.找到 shell 路径 计算机\HKEY_CLASSES_ROOT\Directory\shell3.在 shell 下新建项&#xff0c;名称为 Pycharm 单击Pycharm文件夹&#xff0c;双击默认项&#xff0c;修改默认值&#xff0c;这个数值就是你右击后…

KaiwuDB 内核解析 - SQL 查询的生命周期

一、概述 KaiwuDB 内核解析系列共分上下两部分&#xff0c;本文是该系列的第一部分&#xff0c;主要涵盖了网络协议到 SQL 执行器&#xff0c;解释 KaiwuDB 如何执行 SQL 查询&#xff0c;包括系统各个组件的执行路径&#xff08;网络协议、SQL 会话管理、解析器、执行计划及优…

ucos_conf、ucos_src和ucos_port

目录 ucos_conf 文件夹ucos_src 文件夹ucos_port 文件夹 在 uC/OS-II 中&#xff0c;ucos_conf、ucos_src 和 ucos_port 是三个不同的文件夹&#xff0c;它们的作用和功能有所不同&#xff1a; ucos_conf 文件夹 ucos_conf 文件夹&#xff1a;ucos_conf 文件夹包含了 uC/OS-II…

CSGO游戏里的饰品是如何被炒作起来的?

csgo倒狗们是如何操盘csgo饰品市场的&#xff1f; CSGO游戏里的饰品是如何被炒作起来的&#xff1f; 随着近几年csgo玩家数量急剧上升&#xff0c;倒狗在市场中的比例也在上升&#xff0c;之前的csgo饰品市场以散户居多&#xff0c;价格波动不大&#xff0c;现在倒狗大量涌入&a…

Docker学习——①

文章目录 1、什么是虚拟化、容器化&#xff1f;2、为什么要虚拟化、容器化&#xff1f;3、虚拟化实现方式3.1 应用程序执行环境分层3.2 虚拟化常见类别3.3 常见虚拟化实现3.3.1 主机虚拟化(虚拟机)实现3.3.2 容器虚拟化实现3.3.3 空间隔离实战--基础知识3.3.4 PID 隔离3.3.5 Mo…

【优选算法系列】【专题九链表】第一节.链表常用技巧和操作总结(2. 两数相加)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、链表常用技巧和操作总结二、两数相加 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、链表常…

uniapp自定义权限菜单,动态tabbar

已封装为组件&#xff0c;亲测4个菜单项目可以切换&#xff0c; 以下为示例&#xff0c;根据Storage 中 userType 的 值&#xff0c;判断权限菜单 <template><view class"tab-bar pb10"><view class"tabli" v-for"(tab, index) in ta…

会声会影2024对比2023变化以及功能对比

全新会声会影2024版本现已登场&#xff0c;小伙伴们相信已经急不可待地想知道2024版到底有哪些新功能。对比2023版本&#xff0c;会声会影2024版本有没有功能的增强&#xff1f;事不宜迟&#xff0c;现在就让我们一起来看看会声会影2024对比2023的变化&#xff0c;包括功能对比…

什么是Babel?它的主要作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

编译正点原子LINUXB报错make: arm-linux-gnueabihf-gcc:命令未找到

编译正点原子LINUX报错make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到 1.报错内容2.解决办法3./bin/sh: 1: lzop: not found4.编译成功 1.报错内容 make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到CHK include/config/kernel.releaseCHK include/generat…

unity3d 开发笔记

unity 3d Unity是一个游戏引擎&#xff0c;包含渲染引擎&#xff0c;物理引擎&#xff0c;碰撞检测&#xff0c;音效&#xff0c;动画效果&#xff0c;场景管理等系统。它的开发效率高、脚本使用C#开发、简单易用、跨平台&#xff08;可以导出各个平台的程序&#xff09;&…