《企业实战分享 · MyBatis 使用合集》

news2024/10/5 13:08:40

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍

文章目录

      • 写在前面的话
      • MyBatis 返回限制
      • MyBatis 超时配置
      • MyBatis 批量操作
      • Mybatis 单字符判断
      • Mybatis List 入参问题
      • Mybatis 下划线转驼峰
      • Mybatis in 集合超出1000
      • Mybatis 搜不等于时不包含null
      • MyBatis 其他注意事项
      • 总结陈词

写在前面的话

对于Java程序猿而言,MyBatis应该是企业开发中再熟悉不过的技术了,通常搭配Spring使用。
笔者所在公司也采用了MyBatis、MyBatisPlus作为持久层框架,这边汇总分享若干MyBatis日常使用场景及应对方案,希望与君共勉。

Tips:这里不介绍MyBatis的基础用法,默认大家都熟悉了。


MyBatis 返回限制

场景描述:
企业开发中,经常出现由于 MyBatis 查询出来的数据量过多,导致内存溢出。
这种问题通常出现在大表查询中,并且 由于MyBatis 使用了大量动态标签,当参数都没有传递的时候,就执行了近乎全表查询。

解决方案:
为了防止出现接口入参不规范导致的全表查询问题,框架层面可以进行若干拦截,优雅处理这一问题。
自定义MyBatis插件,按具体配置完成返回条数限制功能。
拦截器的部分代码如下:

public MybatisQueryLimitInterceptor(MybatisPluginProperties mybatisProp) {
    MybatisPluginProperties.QueryLimit queryLimitProp = mybatisProp.getQueryLimit();
    Assert.notNull(queryLimitProp, "Mybatis查询拦截器配置信息不能为空");
    this.queryLimitProp = queryLimitProp;
    int queryLimit = queryLimitProp.getMaxResultRows();
    this.rowBounds = queryLimit > 0 ? new RowBounds(0, queryLimit + 1) : null;
}

@Override
public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    Class<?> returnType = invocation.getMethod().getReturnType();
    String methodId = ((MappedStatement) args[0]).getId();
    if (this.rowBounds == null || !Collection.class.isAssignableFrom(returnType) || !this.isPermit(methodId)) {
        return invocation.proceed();
    }
    args[2] = this.rowBounds;
    Object result = invocation.proceed();
    if (result instanceof Collection
            && ((Collection<?>) result).size() == this.rowBounds.getLimit()) {
        if (this.queryLimitProp.isThrowIfOverLimit()) {
            throw new MybatisQueryLimitException(this.queryLimitProp.getMaxResultRows());
        } else if (result instanceof List) {
            log.error("[Mybatis全表查询拦截] - 方法: {}, 返回结果超过阈值: {}, 已自动丢弃超出范围的数据.", methodId, this.rowBounds.getLimit() - 1);
            ((List<?>) result).remove(this.rowBounds.getLimit() - 1);
        }
    }
    return result;
}

配套的相关配置如下,仅供参考:

mybatis:
  plugins:
    enable-query-limit: true
    # SQL查询返回行数限制
    query-limit:
      # SQL查询最大返回行数 (默认2000,-1表示不限制)
      max-result-rows: 2000
      # 超过最大返回行数时候,true:抛出异常,false:丢弃超过最大行数后面的数据
      throw-if-over-limit: true
      # 白名单 (dao方法全路径名)
      exclude-methods:
        - xxx.TrainProductDao.findAll

MyBatis 超时配置

场景描述:
项目通常会在配置文件中对 MyBatis 的 sql 执行时间进行限制,默认为30秒,也可根据实际情况进行调整。
在 sql 执行时间超过配置的时间后,会抛出 “ORA-01013: 用户请求取消当前的操作”的异常。

解决方案:
这里框架层面没有额外封装,直接使用MyBatis、MyBatisPlus自带的超时限制。
如下所示,配置文件中的超时配置是全局配置,如果对于某个语法有特殊需求时,也可以在XML中用timeout属性对改语句进行特定的配置。

Tips:还有其他超时配置,例如事务超时时间等,这里不展开赘述。

mybatis-plus:
  configuration:
  	# SQL请求超时时间
    default-statement-timeout: 30
<update id="test" timeout="10">
  update xx set xx
</update>
<select id="test" timeout="10">
  select xx from xx
</select>

MyBatis 批量操作

场景描述:
当执行大量插入或更新动作时,传统是采用MyBatis的foreach动态标签,这种方式性能相当差。

解决方案:
框架层面针对这类型操作进行封装。当批量操作大量数据的时候,应使用框架提供的方法。
例如:insertBatchByJdbc、updateBatchByJdbc
框架层面自定义了相关插件类,自动根据方法名是否包含BatchByJdbc后缀来判断是否走jdbc方法还是mybatis的方法,实际用的是 NamedParameterJdbcTemplate 去执行该语句。
该操作比MyBatis正常的foreach批量操作会快非常多,但该SQL入参可能较大,因为对应的SQL日志线上环境不建议打印,这个后续另行讨论。

Tips:相关代码若有需要可以留言提供。


Mybatis 单字符判断

**背景:**程序猿写后端 xml 代码时候,if 语句的参数变量传入状态 ‘1’ 或 ‘a’,发现 if 明明满足但却不触发。

**分析:**Mybatis 是用 OGNL表达式来解析的,在OGNL的表达式中, ‘1’ 会被解析成字符,Java是强类型的,char 和 一个string 会导致不等,所以if标签中的sql不会被解析。

**解决:**单个的字符要写到双引号里面或者使用 .toString() 才行,如下:

<if test="takeWay == '1' and workday != null ">
改为 <if test='takeWay == "1" and workday != null '>
或改为 <if test="takeWay == '1'.toString() and workday != null ">即可。

扩展:
经常遇到这种错误,java.lang.NumberFormatException: For input string: “F”
这个也是单字符问题引起的一种,参考:链接

Tips:最新追加,解决方式使用单引号放外侧也可以,外单内双,,只有当比对的值是字符串才会有问题。

补充:
当你的控制台或者日志出现 java.lang.NumberFormatException 时,很可能就是字符串转换成数字类型出现的问题。例如,在调用Long.ValueOf(String)或者Long.parseLong(String)方法进行数据类型转换时,字符串内不能包含除数字之外的字符。

扩展:
整数类型的判断方式如下:

<if test="nDay!=0">
       and   enddate > sysdate + #{nDay}
</if>
<if test="nDay==0">
   and to_char(enddate, 'yyyymm')=to_char(sysdate, 'yyyymm')
</if>

字符类型包含的判断方式如下:

<if test="params.type != null and params.type.contains('ward')">
  AND a.dept_attribute = '4'
  AND a.parent_dept_code is null
</if>

Mybatis List 入参问题

场景描述:
批量操作、或 in 语法在功能开发中是比较常见的,通常传入 Array 或 List ,然后根据多个 id 获取多条符合要求的记录,但只要入参长度是0,很容易出现 SQL 报错,如下图。
image.png

解决方案:
1)Service 层面直接判断 ids 的是否为空,是的话,直接抛出异常,或其他处理,这是通常做法。
2)Xml-SQL 层面进行判断,如果入参为空,则不查这个条件,当然要衡量这个是否符合逻辑需要。
建议:后端接口容错要主动做好,不能依赖前端去保障列表必定不为空。
注意:这个问题往往会在发布现场后暴露出来,开发人员大多没有意识用单元测试覆盖去所有场景。


Mybatis 下划线转驼峰

问题描述:
SSM 项目中在 M 的配置文件中添加以下配置,可以将数据库中 user_name 转化成 userName 与实体类属性对应,如下:<settingname="mapUnderscoreToCamelCase"value=“true”/>
在 SpringBoot 项目中没有配置文件,也可以在 application.properties 中加入配置项:
mybatis.configuration.mapUnderscoreToCamelCase=true
注意:该操作对返回类型对Map的时候是无效的,需要的话,要额外处理。因此,如果返回的是Map类型,建议要明确指定别名。

扩展说明:
Mybatis 的 map-underscore-to-camel-case 参数设置为true时,可以将数据库的带下划线给去掉然后映射到实体类的属性上去,映射属性时的逻辑大致是:
1、先将下划线去掉,参考:MetaClass#findProperty

public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
        name = name.replace("_", "");
    }
    return this.findProperty(name);
}

2、将字段转成大写,然后查找对象中匹配的属性,参考:Reflector#findPropertyName

public String findPropertyName(String name) {
  return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
}

从以上分析可以看见,其实 M 的驼峰法映射并不是严格限制的驼峰法语法,具体来说,对应“aa_bb”字段,其既可以匹配上“aaBb”属性,也可以匹配上“Aabb”属性,这一点在日常写代码时需要注意下。
这也可以看出,如果是返回Map格式的时候,是无法自动完成映射的。


Mybatis in 集合超出1000

问题描述:
当oracle sql中的in()条件集合超出了1000之后,会出现异常,考虑到某些场景会碰到这种问题,mybatis可以使用这种方式,当要超出1000条时,对in进行结束,重新再加一个in条件。

解决方案:

<select id="batchSelectByIn" resultType="com.zoe.optimus.dia.modules.report.entity.ComStaffBasicInfo">
	select * from zoeods_his.COM_STAFF_BASIC_INFO
    where staff_no in
    <foreach collection="staffNoList" index="index" item="staffNo" open="(" separator="," close=")">
    	<if test="(index % 999) == 998"> #{staffNo} ) OR staff_no IN (</if>#{staffNo}
    </foreach>
</select>

最终生成的SQL如下:

//温馨提示:如果还需要加其他条件,这部分需要用括号包裹,不然影响OR的范围。 -- by.小庄
SELECT *
  FROM ZOEODS_HIS.COM_STAFF_BASIC_INFO
 WHERE STAFF_NO IN ('30080')
    OR STAFF_NO IN ('110308');

Tips:也可以从Java代码考虑分流逻辑,根据实际场景判定。


Mybatis 搜不等于时不包含null

需求:现在oracle数据库中有字段is_use 的值有:null,0,1,2。现在需要查询不等于2的数据解决办法的sql:

select * from uc_Users where nvl(is_use,'xx')<>'2'

nvl(is_use,‘xx’)的意思是:如果is_use为null,值为xx。
如果用select * from uc_Users where is_use<>‘2’ 只会查询出0,1的数据,null的数据查询不出来。

类似的问题记录:null <> ‘0’ = false
背景:现场角色分发表,增加了一个查看报表权限的字段,为1是可查看,为0不可查看,为兼容旧的数据(该字段值为null的),程序判定为1或者为null都是可查看,语法直接写为 xxxx != ‘0’,结果发现无效。
解析:查阅发现,Oracle的 null <> ‘0’ = false,因此改为如下语句:select * from zoecomm.com_role_privs t where nvl(t.report_auth,‘@’) <> ‘0’

类似的问题记录:not in (‘0’)
背景:门户更新用户密码时获取当前用户的所有账号,需要过滤附属账户,一开始使用条件<>'0’过滤,后发现用此语法过滤时会将值为null的记录过滤,后改成not in (‘0’),也有同样的问题
解析:查阅发现,<>, in, not in 做过滤时都会将值为null的记录过滤,因此有如下解决方案:
1、select * from sample where (a not in (‘A’) or a is null);在not in 后加上" or 条件 is null"
2、select * from sample where nvl(a,‘default’) <> ‘A’;使用nvl对null赋默认值,防止 null<>'A’情况的出现


MyBatis 其他注意事项

1、Data日期类型数据与字符串比较带来的问题
mybatis里面配置可以解决这个时间类型与字符串类型作比较。但是为了规范化,建议还是时间类型不要与字符串类型做比较,如果没有配置的话,很容易直接mybatis类型不匹配报错
这样容易报错,
建议改为

2、传入List参数问题
MyBatis 涉及批量操作、in 等场景,经常使用到 foreach,这时候要特别注意,先从Java代码层面就判空操作,如果数组或列表为空,就不要调 Dao 方法了,否则报错。
这个问题往往会在发布现场后暴露出来,开发人员大多没有意识用单元测试覆盖去所有场景。

3、数值类型且为0
Mybatis 判断是否为空一般为:
state = #{state}
但是若state是int类型,并且如果传入的值为0,就不运行该条。
因为Mybatis 默认0和""相等。
解决方案是:
上述判断只适用于String类型的判断,若state是Integer类型的,显然不应该用这种判断条件,要解决这个问题,可以把代码改为:

<if test="state!=null and state!='' or state==0">
<if test="state!=null">state = #{state}</if>

4、Mybatis SQL 日志打印
日常开发通常需要观察本地控制台的SQL执行日志输出情况。
新框架集成了MP进行数据操作,按如下配置即可打印SQL。

logging:
  level:
    # 输出Mybatis相关日志
    xxx.logging.trace.sql.mybatis: debug
    xxx.business.mybatis.interceptor: debug

总结陈词

上文分享若干企业实际开发中,MyBatis的日常使用场景及应对方案,希望对大家有帮助。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

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

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

相关文章

Seatunnel本地模式快速测验

前言 SeaTunnel&#xff08;先前称为WaterDrop&#xff09;是一个分布式、高性能、易于扩展的数据集成平台&#xff0c;旨在实现海量数据的同步和转换。它支持多种数据处理引擎&#xff0c;包括Apache Spark和Apache Flink&#xff0c;并在某个版本中引入了自主研发的Zeta引擎…

虚拟机交叉编译基于ARM平台的opencv(ffmpeg/x264)

背景&#xff1a; 由于手上有一块rk3568的开发板&#xff0c;需要运行yolov5跑深度学习模型&#xff0c;但是原有的opencv不能对x264格式的视频进行解码&#xff0c;这里就需要将ffmpegx264编译进opencv。 但是开发板算力有限&#xff0c;所以这里采用在windows下&#xff0c;安…

2024年07年01日 Redis数据类型以及使用场景

String Hash List Set Sorted Set String&#xff0c;用的最多&#xff0c;对象序列化成json然后存储 1.对象缓存&#xff0c;单值缓存 2.分布式锁 Hash&#xff0c;不怎么用到 1.可缓存经常需要修改值的对象&#xff0c;可单独对对象某个属性进行修改 HMSET user {userI…

Element中的选择器组件Select (一级选择组件el-select)

简述&#xff1a;在 Element UI 中&#xff0c;ElSelect&#xff08;或简称为 Select&#xff09;是一个非常常用的选择器组件&#xff0c;它提供了丰富的功能来帮助用户从一组预定义的选项中选择一个或多个值。这里来简单记录一下 一. 组件和属性配置 <el-selectv-model&q…

经典FC游戏web模拟器--EmulatorJS

简介 EmulatorJS是一个基于JavaScript和Webassembly技术的虚拟环境的实现&#xff0c;可以在网页中运行各种经典FC游戏系统&#xff0c;支持任天堂、世嘉、雅达利等经典红白机。EmulatorJS的诞生使得诸如超级玛丽、坦克大战、魂斗罗等经典FC游戏能够以一种全新的方式回归。本文…

MySQL:高效的索引

数据库索引 1. 索引介绍1.1 创建索引1.2 查看索引 2. 索引应用2.1 前缀索引2.2 全文索引2.3 复合索引2.4 复合索引中的列顺序2.5 建立最佳索引2.6 使用索引排序2.7 覆盖索引 3. 维护索引4. 建立性能数据库 索引对大型和高并发数据库非常有用&#xff0c;因为它可以显著提升查询…

KVM虚拟机动态添加网卡

一、在宿主机上临时在线添加虚拟网卡&#xff0c;关机再开机失效 1、查看运行的虚拟机 [rootlocalhost img]# virsh list 2、添加NAT网卡&#xff0c;会自动获取192.168.122.X网段的IP virsh attach-interface hadoop01 --type network --source default 3、查看虚机mac …

vue+element-ui简洁完美实现个人博客“​响石潭 ​”

目录 一、项目介绍 二、项目截图 1.项目结构图 2.首页 3.生活 ​编辑 4.文章详情 ​编辑 5.关于我 ​编辑 ​编辑 三、源码实现 1.项目依赖package.json 2.项目启动 3.首页源码 四、总结 一、项目介绍 本项目在线预览&#xff1a;点击访问 参考官网&#xff1…

360的chromesafe64.dll动态链接库导致chrome和edge浏览器闪退崩溃关闭

在chrome或edge浏览器中打开特定的一些网页会导致浏览器闪退关闭 这是windows系统记录的报错日志 chrome报错日志 edge报错日志 日志指向的就是chromesafe64.dll这个动态库 然后用everything搜索发现原来在360安装目录下 360安装目录下的chromesafe64.dll文件 为什么360中的…

使用TensorFlow进行OCR识别:将表格图片转换为结构化数据

随着人工智能和机器学习技术的不断发展&#xff0c;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;技术已经成为处理图像中文本信息的强大工具。TensorFlow是一个广泛使用的开源机器学习框架&#xff0c;它提供了丰富的API和工具&#xff…

独立开发者系列(17)——MYSQL的常见异常整理

虽然安装MYSQL到本地很简单&#xff0c;但是数据库报错还是经常出现&#xff0c;这个时候&#xff0c;需要我们进行逐步检查与修复。作为我们最常用的开发软件&#xff0c;无论切换php/go/python/node/java&#xff0c;数据库的身影都少不了&#xff0c;对于我们储存数据而言&a…

Android 如何通过一个设备开发多种分辨率屏幕UI

获取当前屏幕密度&#xff1a; adb shell wm density 获取当前分辨率&#xff1a; adb shell wm size 重置设备密度和分辨率 adb shell wm size reset adb shell wm density reset 屏幕1 adb shell wm size 3082x934 adb shell wm density 160 屏幕2 adb shell wm siz…

【数据结构与算法】利用堆结构高效解决TopK问题

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 二、堆的基本概念 三、使用堆解决TopK问题 四、算法实现&#xff08;C语言…

HTTPS基础

目录 1. HTTPS概述2. HTTPS工作原理3. HTTPS证书4. HTTPS安全性特性5. 配置HTTPS示例5.1 获取和配置SSL/TLS证书5.2 示例&#xff1a;在Nginx上配置HTTPS5.3 实施HSTS 6. 结论 1. HTTPS概述 术语描述HTTPS超文本传输安全协议&#xff0c;HTTP的安全版本。SSL/TLS安全套接字层/…

UG NX二次开发(C++)-根据草图创建拉伸特征(UFun+NXOpen)

1、前言 UG NX是基于特征的三维建模软件,其中拉伸特征是一个很重要的特征,有读者问如何根据草图创建拉伸特征,我在这篇博客中讲述一下草图创建拉伸特征的UG NX二次开发方法,感兴趣的可以加入QQ群:749492565,或者在评论区留言。 2、在UG NX中创建草图,然后创建拉伸特征 …

uniapp + vue3 + Script Setup 写法变动 (持续更新)

一、uniapp 应用生命周期&#xff1a; https://uniapp.dcloud.net.cn/tutorial/vue3-composition-api.html 注意&#xff1a; 应用生命周期仅可在App.vue中监听&#xff0c;在其它页面监听无效。 二 、uniapp页面生命周期&#xff1a; https://uniapp.dcloud.net.cn/tutori…

电商控价:系统监测的必要性与优势

在品牌的发展进程中&#xff0c;会遭遇各种各样的渠道问题&#xff0c;控价乃是其中颇为关键的一环。品牌进行控价的目的无疑是为了妥善治理低价链接&#xff0c;低价链接的发现途径可以是人工&#xff0c;也可以是系统。力维网络在为上百个品牌提供服务的过程中察觉到&#xf…

中南大学湘雅三院张如旭/刘爱华团队发现牙髓干细胞来源的外泌体减轻脑缺血再灌注损伤的神经保护机制

随着我国人口老龄化的加剧&#xff0c;中风已成为我国主要的公共卫生疾病之一&#xff0c;确定其潜在的分子机制和治疗靶点对于开发有效的预防和治疗策略至关重要。近期&#xff0c;中南大学湘雅第三医院张如旭、刘爱华团队在经典权威期刊《Pharmacological Research》&#xf…

在 Mac 上使用 MLX 微调微软 phi3 模型

微调大语言模型是常见的需求&#xff0c;由于模型参数量大&#xff0c;即使用 Lora/Qlora 进行微调也需要 GPU 显卡&#xff0c;Mac M系是苹果自己的 GPU&#xff0c;目前主流的框架还在建立在 CUDA 的显卡架构&#xff0c;也就是主要的卡还是来自英伟达。如果要用 Mac 来做训练…

【AI提升】如何使用大模型:本机离线和FastAPI服务调用

大模型本身提供的功能&#xff0c;类似于windows中的一个exe小工具&#xff0c;我们可以本机离线调用然后完成具体的功能&#xff0c;但是别的机器需要访问这个exe是不可行的。常见的做法就是用web容器封装起来&#xff0c;提供一个http接口&#xff0c;然后接口在后端调用这个…