MyBatis布尔字段映射陷阱全过程解析
在开发过程中,我们常常会遇到一些看似简单却令人困惑的问题。本文记录了一次将 boolean 改为 Boolean 后,MyBatis 插入数据时出现的意外情况。本文不仅逐步揭示了问题的根本原因,还提供了解决方案,并强调了在开发中遵循规范和仔细排查问题的重要性。
为了实现某个功能,需要为已有的表新增字段,其中有一个字段需要表达的含义是:是否有对话条数。
加字段要遵守规范,咱就去看了《阿里巴巴开发规约》的“MySQL规约”,有这么一段描述:
因此,“是否有对话条数”的字段名为 is_has_messages,数据类型为:unsigned tinyint(1表示是,0表示否;默认为1)
给mysql加好字段了,咱还得给 xxxDO 加上字段,按照上面的说法“POJO类中的任何布尔类型的变量,都不要加is前缀”,那就这么写:
/**
* 是否有对话条数;1表示是,0表示否 <br>
* 默认为1
*/
private boolean hasMessages;
一切看起来是那么的自然~
![]()
翻车
▐ 奇怪的结果
is_has_messages 咋是0了?true 不应该映射为1吗?
各种检查代码,都没找到原因... 因为相关业务逻辑太简单了,1 + 1 = 2,有啥需要怀疑的吗?
▐ 打印sql
-
拦截器
/**
* @author hanxu
* @since 2024/12/4
*/
4j
({
"prepare", args = {Connection.class, Integer.class}), (type = StatementHandler.class, method =
})
public class MyBatisSqlInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 获取SQL语句
String sql = statementHandler.getBoundSql().getSql();
// 获取参数
Object parameterObject = statementHandler.getBoundSql().getParameterObject();
log.info("Executing SQL: {}", sql);
log.info("Parameters: {}", parameterObject);
// 执行原始方法调用
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
// 如果需要可以读取配置
}
}
<!-- mybatis的配置文件 -->
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
...
</settings>
<plugins>
...
<plugin interceptor="com.alibaba.xxx.utils.mybatis.interceptor.MyBatisSqlInterceptor"/>
</plugins>
</configuration>
<configuration>
...
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 控制台打印dao层日志 -->
<logger name="com.alibaba.xxx.dao" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
...
</configuration>
-
日志
通过拦截器把sql打印出来,也没发现啥异常的地方:
c.a.i.u.m.i.MyBatisSqlInterceptor - Parameters: xxxDO(empId=xxx, stageName=xxx, realName=xxx, summary=利物浦的首都是?, status=NORMAL, feature={"talkType":"MODEL"}, sessionId=f703babf-791b-432b-a162-7e5d4731bbca, talkEntityType=MODEL, setAsTop=false, hasMessages=true)
c.a.i.u.m.i.MyBatisSqlInterceptor - Executing SQL: INSERT INTO
xxx_session
(
gmt_create,
gmt_modified,
emp_id,
stage_name,
real_name,
summary,
feature,
status,
session_id,
talk_entity_type,
is_set_as_up,
is_has_messages
)
VALUES
(
now(),
now(),
?,
?,
?,
?,
?,
?,
?,
?,
?,
?
)
![]()
启发
isHasMessages(): boolean
难道说,mybatis实际上调用的是getHasMessages()?
MyBatis 会查找参数对象中是否存在 getHasMessages() 方法或者 hasMessages 字段。
由于压根就没有 getHasMessages() 方法,因此使用了 hasMessages 字段的值,也就是默认的false。
如此一来,落db后,is_has_messages正好是0。
可以确认的是,落db的时候,#{hasMessages}一定不是缺失的。因为我给is_has_messages配了默认值为1,一旦缺失了,db里的结果应该为1,而不是0。
实践
从上文可知,我们想知道is_has_messages的占位符到底是什么值。如果是true,那么落到db里的一定是1,反之则为0。
ParameterHandler在Mybatis中负责将sql中的占位符替换为真正的参数。其只有一个实现类:DefaultParameterHandler。
-
我们重点debug:DefaultParameterHandler的setParameters方法
...
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
public Object getParameterObject() {
return parameterObject;
}
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
▐ isHasMessages方法也是可以的...
▐ 计算机不存在了... is_has_messages又是0了...
再折腾,需求做不完了... 我知道的解决办法是:
复盘
前几日早晨,被猫搞醒了,突然间灵光一现,想明白“智子”到底是谁了。收拾整顿后,便来验证自己的想法了。
给自己,也给读者朋友们一个答复。毕竟没有结论的文章,就像说一半的话,让人难受。
▐ 还原现场
▐ 抓出真凶
我们的注意力都放在addSession接口了,在debug了Mybatis的源码时,非常肯定确认插入db的时候,is_has_messages的值一定是true。为什么有时候is_has_messages的值一会儿是1,一会儿又是0呢?
那是因为有一只被忽视的手(或者说“智子”)在暗中操作,没被我们看见。这只手便是xxxTalk接口。
-
xxxTalk接口为啥要去update呢?因为用户对话后,要刷新session表的更新时间,使得用户正在对话的session在session列表中处于第一个。
我们看一下:updateById的写法:
当xxxSessionDO的hasMessages的类型为boolean时,默认是false。
因此,updateById时,is_has_messages为0。
▐ 猜想
未debug时,addSession接口比xxxTalk接口执行快,addSession接口insert时,is_has_messages为1;但xxxTalk接口update后,把is_has_messages修改为0。
debug时,addSession接口阻塞了,xxxTalk接口update无效(因为还没这条记录);addSession接口insert时,is_has_messages为1。
这就是为什么我们一会儿能看到is_has_messages为1,一会儿又为0。
▐ 验证
如果我的猜想是对的,那么我们注释掉“xxxTalk接口update”的代码,应该能看到is_has_messages为1的结果。
果真如此,猜想正确!这背后的“智子”便是:xxxTalk接口update了xxx_session表。
当我们将hasMessages的boolean改成Boolean时,我们还做了一件非常重要的事情:将hasMessages默认为TRUE。这样即使“xxxTalk接口update”,is_has_messages还是1。
private Boolean hasMessages = Boolean.TRUE;
当然了,最后的解法不是注释掉“xxxTalk接口update”的代码,而是:
结语
在这篇文章中,我们经历了一次从困惑到柳暗花明的过程。最终,我们将boolean改为Boolean,并显式地设置了默认值为TRUE,成功解决了问题。这一过程不仅让我们更加了解MyBatis的参数处理机制,也提醒我们在开发中要特别注意类型的选择和默认值的设置,避免因细微的差异而导致意想不到的错误。
相信科学,保持好奇心,勇于探索问题的本质,才能在面对复杂的技术挑战时找到正确的解决方案。希望这篇文章能为各位开发者提供一些有价值的参考,帮助大家在未来的开发中少走弯路。
团队介绍
我们是淘天集团-ideaLAB团队,专注于运用AI工程技术提升集团内部业务的生产效率。我们深入研究PE、RAG、Agent、多模态及模型评测等前沿领域,并积极参与集团内ideaLAB产品的建设。我们的目标是通过开发AI SDK和一站式AI Studio,提供先进的AI应用构建能力。同时,我们致力于将AI工程技术广泛应用于集团的多种业务场景,包括但不限于toC、toB和toP等领域,借助丰富的实践经验,不断探索和实施创新技术,以实现更智能、更高效的业务解决方案。
本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
为什么某些 batch size 会突然导致性能下降?
编者按:你是否曾在优化深度学习模型时感到困惑,明明增加了 batch size,GPU 利用率却没有如预期提升?在实际项目中,这个问题可能导致资源浪费、训练效率低下,甚至影响整个 AI 产品的交付周期。 本文作者深入剖析了现代 GPU 批处理的工作原理,揭示了内存带宽与计算能力之间的微妙关系。通过建立理论模型并结合实际实验,作者不仅解释了为什么某些 batch size 会突然导致性能下降,还提供了如何找到最佳 batch size 的方法。 作者 | Finbarr Timbers 编译 | 岳扬 一般来说,对于现代深度学习系统而言,你能做的第一个也是最重要的优化措施就是实现批处理(batching)。在进行推理时,不是单独处理单个输入,而是同时处理包含 N 个输入的一批数据。大多数情况下,这个操作是无需额外成本的 ------ 无论是处理单个输入还是 N 个输入,推理所需的时间几乎相同。这又是为何呢?表面上看,批量处理数据似乎应该消耗更多资源,毕竟,工作量增加了 N 倍。 然而,如果我们使用一个简单或者不成熟的模型来理解神经网络的工作方式,那么批处理(batching)的计算并不是...
- 下一篇
优化永不止步:TinyVue v3.20.0 正式发布,更美观的官网UI,更友好的文档搜索,更强大的主题配置能力~
本文由体验技术团队Kagol原创。 我们非常高兴地宣布,2024年12月4日,TinyVue 发布了 v3.20.0 🎉。 本次 3.20.0 版本主要有以下重大变更: OpenTiny 官网首页 UI 和性能优化,更具现代性和设计美感,加载速度更快、体验更好。 TinyVue 官网增加 Algolia 全文搜索,原来只能搜索组件名称,现在可以搜索组件Demo和API文档,让你更容易搜索到自己想要的内容。 主题配置工具增加覆盖CSS功能,定制性更强,并且增加了老主题、Aurora主题的适配。 详细的 Release Notes 请参考:https://github.com/opentiny/tiny-vue/releases/tag/v3.20.0 本次版本共有13位贡献者参与开发,感谢朋友们的辛苦付出👏 shenjunjian kagol zzcr gimmyhehe Davont GaoNeng-wWw betavs wuyiping0628 Youyou-smiles James-9696 chenxi-20 mengqiuleo MomoPoppy 你可以更新 @opent...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7设置SWAP分区,小内存服务器的救世主
- Mario游戏-低调大师作品
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 2048小游戏-低调大师作品
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题