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

这篇具有很好参考价值的文章主要介绍了mybatis-plus技巧--动态表名-多语句-拼接sql--关于mybatis的mysql分页查询总数的优化思考。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态表名

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}
sqlSelect select参数, @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
mybatis表名动态拼接,mybatis(-plus),mybatis,sql,mysql
mybatis表名动态拼接,mybatis(-plus),mybatis,sql,mysql
下面是自定义的分页插件,吞吐量17,很明显快一些,当然小数据量/单表的时候速度和count差不多,主要在于多表数据量大的时候提升效果显著。
mybatis表名动态拼接,mybatis(-plus),mybatis,sql,mysql
mybatis表名动态拼接,mybatis(-plus),mybatis,sql,mysql
至于准确率方便,设置有响应断言,都没有异常,可以保证准确率
mybatis表名动态拼接,mybatis(-plus),mybatis,sql,mysql

实现:
也可以当mybatis插件的一个练习吧,借鉴了mybatis-plus的分页插件。
依据是否存在page作为参数来进行是否进行分页查询,这个条件也可以依据自己的更换。文章来源地址https://www.toymoban.com/news/detail-794682.html

@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);
        }
    }

到了这里,关于mybatis-plus技巧--动态表名-多语句-拼接sql--关于mybatis的mysql分页查询总数的优化思考的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Mybatis-plus动态条件查询QueryWrapper的使用

    queryWrapper是mybatis plus中实现查询的对象封装操作类,可以封装sql对象,包括where条件,order by排序,select哪些字段等等,他的层级关系如下图: 2.1-案例一:根据name模糊查看未删除的用户列表信息 过滤条件: queryWrapper实现: 2.2-案例二:查看姓李的并且邮箱不为空的用户列表

    2024年02月14日
    浏览(37)
  • Springboot 配置动态多数据源(Mybatis-plus)

    前言:在项目中需要用到动态切换多数据源,查阅Mybatis-plus文档得知可以通过@DS注解,但该方法主要针对不同内容的数据源,而目前场景是相同内容的数据库需要在运行时根据请求头动态切换,因此文档方法不适用。 注意,不要使用dynamic-datasource-spring-boot-starter依赖包。 应用

    2024年02月12日
    浏览(29)
  • grpc + springboot + mybatis-plus 动态配置数据源

    前言 这是我在这个网站整理的笔记,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 1.1 项目初始化 项目初始化的时候会调用com.baomidou.dynamic.datasource.DynamicRoutingDataSource对象的addDataSource方法添加数据源,数据源存进dataSourceMap中。 1.2 接口请求时候 进行数据操作时,

    2024年02月09日
    浏览(31)
  • 利用 Mybatis-Plus 的动态数据源实现多数据源配置

    目录 一、导入依赖 二、Application.yaml配置文件 三、切换数据源 四、其他方法 4.1 配置多个数据源 4.2 定义Datasource和EntityManager 4.3 在需要使用数据源的地方注入不同的EntityManager 官网:https://baomidou.com/pages/a61e1b/#dynamic-datasource 默认是使用配置文件中master参数设置的数据库。

    2024年02月13日
    浏览(31)
  • Spring Boot + MyBatis-Plus 实现 MySQL 主从复制动态数据源切换

    MySQL 主从复制是一种常见的数据库架构,它可以提高数据库的性能和可用性。 动态数据源切换则可以根据业务需求,在不同场景下使用不同的数据源,比如在读多写少的场景下,可以通过切换到从库来分担主库的压力 。 在本文中,我们将介绍如何在 Spring Boot 中实现 MySQL 动

    2024年02月19日
    浏览(42)
  • 【Mybatis-Plus】Mybatis-Plus快速入门

    Mybatis-Plus是基于Mybatis的数据库操作组件,其实现的功能完全是Mybatis的功能拓展,不改变Mybatis的使用方式,可以兼容Mybatis的操作方式。 创建一个数据库、一个表进行基础操作: 创建一个Spring项目,项目通过Spring Initlizer创建,不导入任何依赖包,在POM.xml文件中进行依赖导入

    2024年02月07日
    浏览(39)
  • Mybatis-Plus 进阶开发 -- Mybatis-Plus 入门教程(二)

    为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚,文章中如果有记录错误,欢迎读者朋友们批评指正。 (博客的参考源码可以在我主页的资源里找到,如果在学习的

    2024年02月10日
    浏览(39)
  • Mybatis-Plus(三)--Mybatis-Plus配置和条件构造器

    在MP中有大量的配置,其中有一部分是Mybatis原生的配置,另一部分是MP的配置,详情:https://mybatis.plus/config 【1】configLocation--自己单独的MyBatis配置的路径 SpringMVC的xml中写法: 【2】mapperLocations--MyBatis Mapper所对应的XML文件位置 如果你在Mapper中有自定义方法(XML中有自定义实现

    2024年02月15日
    浏览(50)
  • Mybatis-Plus通用枚举功能 [MyBatis-Plus系列] - 第493篇

    历史文章( 文章 累计490+) 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《

    2024年02月08日
    浏览(33)
  • Mybatis Plus之DQL(条件查询方式、查询投影、查询条件设定、字段映射与表名映射)

    增删改查四个操作中,查询是非常重要的也是非常复杂的操作,这块需要我们重点学习下,这节我们主要学习的内容有: 条件查询方式 查询投影 查询条件设定 字段映射与表名映射 1.1 条件查询的类 MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的

    2024年02月05日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包