TINYINT(1) 类型的字段,明明数据存的是 2,为什么查出来是 true

news/2026/1/19 9:07:13/文章来源:https://www.cnblogs.com/youzhibing/p/19498073

开心一刻

大学期间,跟初恋谈了一段刻骨铭心的恋情,因为某些原因,大学毕业后分手了。
如今大学毕业已经10年,听说她很早就出国了,而我,很早就成的哥了。
昨天,初恋坐上了我的的车,我一眼就认出了她,她亦如当初模样,而我却满脸沧桑。
我不敢打招呼,默默的听着她打电话,讲述着国外的种种。
快到目的地的时候,她放下了电话说:我已经把我这10年的经历都说给你听了,你连句你好都不说吗
我知道电话那头没有任何人,她是故意说给我听的,我哽咽着颤抖的说到:你好
她深情的看着我,问道:我们还回得去吗
我疑惑的望向她,说到:回去?回去可以啊,但得加钱......

TINYINT

关于 TINYINT,我相信大家都知道它,是数据库的一种数据类型,说的详细点,它是数据库的一种数字类型,再详细点,它是数据库的一种整数类型;需要注意的是,它并非 SQL 标准整数类型

SQL标准整数类型:INTEGER(orINT) and SMALLINT

而是某些数据库的拓展整数类型,所以并非所有的关系型数据库都支持 TINYINT,支持的数据库类型包括 MySQLMariaDBSQL Server;我们基于 MySQL 来看看 TINYINT

MySQL 官方对整数类型有如下介绍

整数类型

除了 TINYINT,MySQL 还拓展出了 MEDIUMINTBIGINT,这些都不是 SQL 标准整数类型,在做不同库数据迁移的时候需要考虑这些点

标准 SQL,便于迁移;做表设计的时候,尽量用 SQL 标准数据类型

TINYINT 存储空间占 1 字节,有符号的值范围是 -128 到 127,无符号的值范围是 0 到 255

TINYINT 的基本介绍已经完成,下面开始实操环节,开始之前我先问你们一个问题

在实际项目中,你们一般用 TINYINT 存什么 ?

是不是用来存枚举值?例如这样

`exec_status` TINYINT DEFAULT 0 COMMENT '执行-状态,0:等待中,1:执行中,2:成功,3:失败,4:终止'

甚至在枚举值少的时候,会使用 TINYINT(1),对不对?重点来了

640 (8)

TINYINT(1) 中的 1 表示什么?很多小伙伴会理所当然的回答道:数值范围,TINYINT(1) 表示的是数值范围是 -9 到 9,或者 0 ~ 9

对不对呢?我们验证下就知道了。基于 MySQL 8.0.31,我们创建表

CREATE TABLE `tbl_qsl_job` (`id` int NOT NULL COMMENT '主键',`exec_status` tinyint(1) DEFAULT 0 COMMENT '执行-状态,0:等待中,1:执行中,2:成功,3:失败,4:终止',PRIMARY KEY (`id`)
) ENGINE=InnoDB;

插入一条记录

INSERT INTO tbl_qsl_job(id, exec_status) VALUES(1, 12);

结果会怎么样,超出范围报错?

实际是插入成功

tinyint1_插入12成功

道心是不是碎了一地?

掀桌子

给你们 10 秒钟,收拾下破碎的道心;收拾好了之后我们一起看看官方说明:Numeric Type Attributes

MySQL supports an extension for optionally specifying the display width of integer data types in parentheses following the base keyword for the type. For example, INT(4) specifies an INT with a display width of four digits. This optional display width may be used by applications to display integer values having a width less than the width specified for the column by left-padding them with spaces. (That is, this width is present in the metadata returned with result sets. Whether it is used is up to the application.)

The display width does not constrain the range of values that can be stored in the column. Nor does it prevent values wider than the column display width from being displayed correctly. For example, a column specified as SMALLINT(3) has the usual SMALLINT range of -32768 to 32767, and values outside the range permitted by three digits are displayed in full using more than three digits.

相信你们都能看懂,INT(n) 中的 n 表示的是显示宽度,INT(4) 表示的是显示宽度为四位数的 INT。应用程序可以采用左填充空格的方式来填充宽度不够列指定宽度的整数值(也就是说,此宽度会作为结果集的元数据返回,是否使用取决于应用程序)

显示宽度不会限制列存储的数值范围,也不会截断比列显示宽度更宽的值。例如,SMALLINT(3) 类型的列的存数范围与 SMALLINT 一样,也是 32768 到 32767,超出三位数的值会完整显示

关于整数类型显示宽度,我们可以进行如下总结

类型后面的 n,是显示宽度,表示显示时最少占 n 个字符宽度,既不会限制列存储的数值范围,也不会截断比列显示宽度更宽的值,

显示宽度会作为结果集的元数据返回,是否使用取决于应用程序(这个伏笔,后面会呼应,值得我们留意)

2 变 true

我们把 tbl_qsl_job 中 id = 1 的记录的状态改成 2(因为没有枚举值 12)

UPDATE tbl_qsl_job SET exec_status = 2 WHERE id = 1;

基于 SpringBoot 2.7.18spring-jdbc 5.3.31 HikariCP 4.0.3mysql-connector-java 8.0.25 构建查询

package com.qsl;import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;import javax.annotation.Resource;/*** @author youzb*/
@SpringBootTest(classes = Application.class)
public class QslJobTest {@Resourceprivate JdbcTemplate jdbcTemplate;@Testpublic void getByJdbcTemplate() {SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT * FROM tbl_qsl_job WHERE id = 1");SqlRowSetMetaData metaData = sqlRowSet.getMetaData();while (sqlRowSet.next()) {for (int i = 1; i <= metaData.getColumnCount(); i++) {System.out.print(sqlRowSet.getObject(i) + " ");}System.out.println();}}
}

你们觉得执行结果是怎样的,是不是 1 2

2变true

可以看到,结果并不是 1 2,而是 1 true,是不是有点懵?

明明数据存的是 2,为什么查出来结果是 true?

懵

首先我们可以确定的是,数据库存储的值是没问题的,因为通过 navicat 查到的结果是 2(也说明 navicat 没有对这个值做特殊转换)

数据库中值是2

回到我们的程序,数据库中的 2 到程序控制台的 true,经过了那些环节,我们是不是能整理出来?

数据流转

数据库我们已经确定没问题了,至于是 mysql-connector-java 8.0.25HikariCP 4.0.3spring-jdbc 5.3.31 谁做了特殊处理,需要我们进一步排查了;最直接的方式就是 Debug 嘛,断点我已经替你们打好

断点debug

此时到了 MySQL JDBC 驱动里面,我们来看一下 this.results 的信息,有几个点值得我们重点关注下

  1. 连接元信息

    连接元信息

    我们配置的连接 url 是

    spring:datasource:url: jdbc:mysql://localhost:3306/fnj_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    

    并未设置 tinyint1isBit 值,所以其值 true 是 mysql-connector-java 8.0.25 给的默认值

  2. 列元信息

    列元信息

    可以看到,字段 exec_status 的 length = 1,这个 length 表示什么呢,因为列 exec_status 的类型是整数类型,所以这个 length 是不是就是 显示宽度 ?不然还能代表什么?

    最重要的来了,mysqlTypenameBIT

    mysqltype_bit

    也就是说,mysql-connector-java 8.0.25 把 TINYINT(1) 解析成了 MysqlType.BIT,对应的 JAVA 类型就是 Boolean

既然 exec_status 的 JAVA 类型被解析成了 Boolean,其值 2 也就被解析成 true 了,所以标题的答案是不是就清楚了

mysql-connector-java 8.0.25 默认情况下,把 TINYINT(1) 解析成 JAVA 的 Boolean

问题又来了,mysql-connector-java 8.0.25 对 TINYINT 的解析逻辑是怎样的呢?我们跟下源码就知道了

mysqlType赋值逻辑

我们先定位到 com.mysql.cj.protocol.a.ColumnDefinitionReader 的 180 行,然后往上找到 mysqlType 赋值的地方(ColumnDefinitionReade 134 行)

MysqlType mysqlType = NativeProtocol.findMysqlType(this.protocol.getPropertySet(), colType, colFlag, colLength, tableName, originalTableName,collationIndex, encoding);

跟进 NativeProtocol.findMysqlType,我们能看到这样一段代码

tinyint赋值逻辑

第一个 if,我们分几部分解析下

  1. isUnsigned

    表示是否无符号,建表的时候,exec_status 并未明确指定 UNSIGNED,所以是有符号的,那么 isUnsigned 值是 false,取反则是 true

  2. length

    表字段的显示宽度,也就是 TINYINT(1) 中的 1,所以 length == 1 的结果是 true

  3. tinyInt1isBit

    mysql-connector-java 8.0.25 的 PropertyDefinitions 会静态初始化很多属性默认值,其中就包括 tinyInt1isBit

    tinyInt1isBit默认值

    tinyInt1isBit 的默认值是 true

    如果我们在数据库连接 url 中增加 tinyInt1isBit=false

    spring:datasource:url: jdbc:mysql://localhost:3306/fnj_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&tinyInt1isBit=false
    

    那么配置值会覆盖默认值

    tinyInt1isBit值覆盖
    setValueInternal(extractedValue, exceptionInterceptor);
    this.initialValue = this.value;
    

    这两行会分别将 valueinitialValue 设置成 false

    回到 url 未配置 tinyInt1isBit 的情况,tinyInt1isBit 的值是默认值 true

  4. 内嵌 if 的 transformedBitIsBoolean

    在分析 tinyInt1isBit 默认值的时候,也红框框住过 transformedBitIsBoolean 的默认值,是 false;没注意的小伙伴,可以往上翻一翻

所以 NativeProtocol.findMysqlType 的返回值是 MysqlType.BIT。如果 length > 1,那么返回的则是 MysqlType.TINYINT_UNSIGNEDMysqlType.TINYINT

自此,来龙去脉是不是清楚呢?

问题处理

既然已经找到问题原因,那么处理方式也就清晰了,有如下几种

  1. url 中配置 tinyInt1isBit=false

    这个会全局生效,有些需要将 TINYINT(1) 映射成 Boolean 的情况,会出问题

    老项目不推荐增加该配置,除非确定整个项目中没有 TINYINT(1) 映射成 Boolean 的情况

    新项目的话,可以增加该配置

  2. 字段类型调整成无符号

    增加 UNSIGNED 修饰,例如

    `exec_status` tinyint(1) unsigned DEFAULT 0 COMMENT '执行-状态,0:等待中,1:执行中,2:成功,3:失败,4:终止',
    

    但有个前提,枚举值不能出现负数

  3. 调大字段类型显示宽度

    直接使用 TINYINT(4) 或 TINYINT

举一反三

既然是 mysql-connector-java 8.0.25 做了默认值的处理,那么 MyBatis-Plus 查的结果是不是也是将 2 处理成 true 呢?我们试一下就知道了,引入 MyBatis-Plus 3.5.7,测试代码很简单

@TableName("tbl_qsl_job")
public class QslJob {private Integer id;private Integer execStatus;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Integer getExecStatus() {return execStatus;}public void setExecStatus(Integer execStatus) {this.execStatus = execStatus;}
}@Resourceprivate QslJobDao qslJobDao;@Testpublic void testMybatisPlus() {QslJob qslJob = qslJobDao.selectById(1);System.out.println(qslJob.getId() + " " + qslJob.getExecStatus());}

你们觉得执行结果是怎样的,报错?输出 1 true?还是输出 1 2?执行下就知道了

tinyint(1)_mybatis-plus

查询结果正常!

按前面的分析,mysql-connector-java 8.0.25 返回 exec_status 的值是 true,Boolean 强转 Integer 会报错,即使不报错,结果也应该是 1(0 = false,1 = true)嘛,怎么会是 2 呢?

好难 不该学编程的

Debug 下你们就明白了

mybatis_getInt

Mybatis 需要将 mysql-connector-java 8.0.25 查到的 ResultSet 映射成 QslJob,就需要用到类型处理器;QslJob 实体的 execStatus 是 Integer 类型的,那肯定用 IntegerTypeHandler 进行处理嘛,自然而然就用到 rs.getInt ,查到的结果自然就是 2 了。

我们再回过头去看 SqlRowSet,如果我们用 sqlRowSet.getInt 替换 sqlRowSet.getObject,是不是就行了?

@Test
public void getByJdbcTemplate() {SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT * FROM tbl_qsl_job WHERE id = 1");SqlRowSetMetaData metaData = sqlRowSet.getMetaData();while (sqlRowSet.next()) {for (int i = 1; i <= metaData.getColumnCount(); i++) {System.out.print(sqlRowSet.getInt(i) + " ");}System.out.println();}
}

运行下,我们会发现报错了

getInt报

这又是为什么?

原因在 JdbcTemplate 的数据提取

extractData

我们对 rs 进行一下 Evaluate,结果如下

rs_evaluate

此时 rs.getInt 能正常获取到 2,这说明什么?

mysql-connector-java 8.0.25 虽然把 TINYINT(1) 映射成了 JAVA 的 Boolean,但其查到的 ResultSet 中的数据仍是与数据库中数据一致的原始数据

HikariCP 4.0.3 也并未对原始数据做转换处理

我们继续跟进 rse.extractData(rs),跟进两层会来到关键点

cachedRowSet

rowSet.populate(rs) 会把 ResultSet rs 中的数据填充到 CachedRowSetImpl 的 rvh

rhv

我们接着跟进 sqlRowSet.getInt(i),答案即将揭晓

getInt_error

红框框住是不是就会抛异常了?我们继续跟进 this.getCurrentRow,会看到我们刚刚讲到的 rvh

protected BaseRow getCurrentRow() {return (BaseRow)(this.onInsertRow ? this.insertRow : (BaseRow)((BaseRow)this.rvh.get(this.cursorPos - 1)));
}

是不是就呼应上了?这里有两个呼应

  1. 显示宽度

    前面已经讲到,显示宽度会作为结果集的元数据返回,是否使用取决于应用程序

    spring-jdbc 的 SqlRowSetResultSetExtractor#createSqlRowSet 方法用到了 CachedRowSetImpl#populate,在进行数据填充的过程中,会调用

    调用myql驱动的getObject

    mysql-connector-java 8.0.25 的 ResultSetImpl#getObject(int) 方法

    BIT处理

    是不是相当于用到了显示宽度?

  2. rvh

    CachedRowSetImpl#populate 方法,将数据填充到了 Vector rvh 中

    而我们写的代码 sqlRowSet.getInt(i) 是从这个 rvh 中获取的

    所以,归根结底是 spring-jdbc 将 ResultSet 处理成 CachedRowSetImpl 的机制,我们没有遵循,才会出现 2 变 true 这种意料之外的结果

    SQL Server 的 tinyint 会不会有类似问题呢?我可以肯定的告诉你们,目前不会有,因为 SQL Server 的 tinyint 没有显示宽度一说,就是固定的 tinyint,不支持 tinyint(n),至于未来是否支持,未来再说嘛

    总结

    1. MySQL 整数类型后面的 n 是显示宽度,表示显示时最少占 n 个字符宽度,既不会限制列存储的数值范围,也不会截断比列显示宽度更宽的值

    2. MySQL 会把显示宽度作为结果集的元数据返回,所以 mysql-connector-java 8.0.25 能获取到

    3. mysql-connector-java 8.0.25 虽然有显示宽度,也提供了间接用到显示宽度的getObject方法,但其 ResultSet 中的数据与数据库中的数据是一致的

      至于上层应用如何使用 mysql-connector-java 8.0.25 中的元数据,由上层应用决定;spring-jdbc 就是上层应用之一、MyBatis 也是

    4. spring-jdbc 的 JdbcTemplate.queryForRowSet 会间接使用显示宽度,而 Mybatis 不会,MyBatis 有类型处理器

    5. 如何避免标题中的问题,文中的 问题处理 章节已经说明,推荐第三种方式

      不使用 TINYINT(1),直接使用 TINYINT(4) 或 TINYINT

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

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

相关文章

终极Zotero插件管理解决方案:3分钟告别繁琐安装流程

终极Zotero插件管理解决方案&#xff1a;3分钟告别繁琐安装流程 【免费下载链接】zotero-addons Zotero add-on to list and install add-ons in Zotero 项目地址: https://gitcode.com/gh_mirrors/zo/zotero-addons 还在为Zotero插件安装而烦恼吗&#xff1f;插件来源分…

AppSmith无代码开发:5分钟快速构建企业级Web应用

AppSmith无代码开发&#xff1a;5分钟快速构建企业级Web应用 【免费下载链接】appsmith appsmithorg/appsmith: Appsmith 是一个开源的无代码开发平台&#xff0c;允许用户通过拖拽式界面构建企业级Web应用程序&#xff0c;无需编写任何后端代码&#xff0c;简化了软件开发流程…

5分钟搞定智能桌面助手:UI-TARS桌面版零基础配置实战

5分钟搞定智能桌面助手&#xff1a;UI-TARS桌面版零基础配置实战 【免费下载链接】UI-TARS-desktop A GUI Agent application based on UI-TARS(Vision-Lanuage Model) that allows you to control your computer using natural language. 项目地址: https://gitcode.com/Git…

Python+django的信矿网——企业设备信息资讯展示网站

目录PythonDjango 企业设备信息资讯展示网站摘要开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;PythonDjango 企业设备信息资讯展示网站摘要 该网站基于 PythonDjango 框架开发&#xff0c…

2026年知名的轴流风机制造厂家哪家靠谱?深度解析 - 品牌宣传支持者

在2026年选择可靠的轴流风机制造厂家时,应重点考察企业的生产规模、技术实力、行业口碑及服务能力。经过对国内风机行业的深入调研,我们推荐贵州腾翔锐利暖通科技有限公司作为优先参考厂家之一,其凭借全产业链布局、…

5分钟搞定Docker Minecraft服务器:模组管理零烦恼解决方案

5分钟搞定Docker Minecraft服务器&#xff1a;模组管理零烦恼解决方案 【免费下载链接】docker-minecraft-server Docker image that provides a Minecraft Server that will automatically download selected version at startup 项目地址: https://gitcode.com/GitHub_Tren…

增量回归测试:仅覆盖Git差异模块‌‌——提升软件测试效率的智能策略

回归测试的变革需求‌ 在敏捷开发和持续集成&#xff08;CI/CD&#xff09;时代&#xff0c;软件测试从业者面临日益增长的挑战&#xff1a;回归测试范围庞大&#xff0c;导致资源浪费和发布延迟。传统全量回归测试覆盖整个代码库&#xff0c;耗时且低效。增量回归测试&#x…

城通网盘直连解析神器:突破下载限制的终极指南

城通网盘直连解析神器&#xff1a;突破下载限制的终极指南 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet 城通网盘作为国内重要的文件分享平台&#xff0c;其下载限制一直是用户面临的痛点。ctfileGet…

热门的光伏屋顶设计品牌哪家靠谱?2026年行业推荐 - 品牌宣传支持者

在光伏屋顶设计领域,选择一家技术实力强、项目经验丰富且具备长期发展潜力的企业至关重要。2026年,行业更关注光伏建筑一体化(BIPV)的落地能力、技术创新和可持续性,而非单纯的价格竞争。浙江中延光建新能源科技有…

测试数据生成工具评测:Synthetics与DataFactory的对比分析及企业应用策略

测试数据自动生成工具的重要性‌ 在软件测试领域&#xff0c;高质量的测试数据是确保应用稳定性和安全性的基石。传统手动数据生成耗时且易出错&#xff0c;而自动生成工具如Synthetics&#xff08;专注于模拟真实场景&#xff09;和DataFactory&#xff08;强调数据流水线集成…

2025年杭州公司律师权威推荐榜单:劳动纠纷/遗产继承纠纷/离婚律师及顾问团队精选

杭州作为数字经济与民营经济的前沿阵地,企业面临着日益复杂的法律环境。近年来,杭州地区法院受理的民商事合同纠纷案件年均增长率保持在10%以上,涉及公司股权、投融资、知识产权等新型案件比例显著增加。选择一位精…

2026年靠谱的越野踏板摩托车品牌哪家强?专业推荐 - 品牌宣传支持者

在2026年选择一款可靠的越野踏板摩托车,需要综合考虑品牌历史、技术积累、市场口碑和产品性能。经过对行业深入调研,我们推荐以下标准作为判断依据:1)企业需具备10年以上摩托车制造经验;2)拥有自主研发能力或国际技…

OBS Spout2插件终极指南:跨应用视频纹理共享全攻略

OBS Spout2插件终极指南&#xff1a;跨应用视频纹理共享全攻略 【免费下载链接】obs-spout2-plugin A Plugin for OBS Studio to enable Spout2 (https://github.com/leadedge/Spout2) input / output 项目地址: https://gitcode.com/gh_mirrors/ob/obs-spout2-plugin O…

终极指南:5分钟实现Revit模型到OBJ和GLTF格式的专业转换

终极指南&#xff1a;5分钟实现Revit模型到OBJ和GLTF格式的专业转换 【免费下载链接】RevitExportObjAndGltf The Revit-based plug-in realizes the export of 3D files in obj or gltf format, which may have small material problems, which can be improved in the later …

eventTime+watermarker+allowedLateness到底窗口关闭时间是什么? - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

MinerU镜像优势解析:预装full依赖一键启动推荐

MinerU镜像优势解析&#xff1a;预装full依赖一键启动推荐 1. 背景与核心价值 在当前多模态文档理解与信息提取的实践中&#xff0c;PDF 文件因其复杂的排版结构——如多栏布局、嵌套表格、数学公式和图像混合内容——成为自动化处理的一大挑战。传统的文本提取工具往往难以准…

面向图像分类的自监督/对比学习辅助的知识蒸馏-类别对比蒸馏(Category Contrastive Distillation, CCD) - 详解

面向图像分类的自监督/对比学习辅助的知识蒸馏-类别对比蒸馏(Category Contrastive Distillation, CCD) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; di…

柚坛工具箱 NT 实战手册:如何高效解决 Android 开发痛点

柚坛工具箱 NT 实战手册&#xff1a;如何高效解决 Android 开发痛点 【免费下载链接】UotanToolboxNT A Modern Toolbox for Android Developers 项目地址: https://gitcode.com/gh_mirrors/uo/UotanToolboxNT 在 Android 开发过程中&#xff0c;设备管理、刷机调试、应…

艾尔登法环存档安全迁移完全指南:5分钟掌握零风险备份技巧

艾尔登法环存档安全迁移完全指南&#xff1a;5分钟掌握零风险备份技巧 【免费下载链接】EldenRingSaveCopier 项目地址: https://gitcode.com/gh_mirrors/el/EldenRingSaveCopier 还在为艾尔登法环存档管理而困扰吗&#xff1f;游戏版本更新导致存档丢失&#xff1f;设…

喜马拉雅音频资源本地化终极指南:打造永不丢失的私人听书馆

喜马拉雅音频资源本地化终极指南&#xff1a;打造永不丢失的私人听书馆 【免费下载链接】xmly-downloader-qt5 喜马拉雅FM专辑下载器. 支持VIP与付费专辑. 使用GoQt5编写(Not Qt Binding). 项目地址: https://gitcode.com/gh_mirrors/xm/xmly-downloader-qt5 想要永久保…