1 引言
最近接手了一个已离职同事的java项目,这个项目中原来使用了自己的mysql驱动版本,并未使用公司公共依赖中的版本号。我想为了统一版本号,就将当前项目中pom文件中mysql的版本号verson
给去除了。没怎么自测,就直接发到测试环境了,结果没多久就有测试人员告诉了说,有接口报错。
其主逻辑sql如下
SELECT * FROM rule_category where `name`='企业资质' and parent_id=0;
后面发现不止这个接口查询结果为空,其他好几个接口的查询结果都未空。我用同样的sql语句在navicat中能查出结果,我开始怀疑我是不是连错数据库了,是不是连接到开发环境中的数据库。我使用Environment#getProperty("spring.datasource.url")
获取到的数据库url可以确定是测试环境。后面进一步发现这些接口中都有中文
查询条件,我开始怀疑是字符集的问题。回到刚才的数据库连接URL,其格式是jdbc:mysql://xxhost:3306/rule_engine?serverTimezone=GMT%2B8
,这个URL明显没有指定字符集编码,我在这个url上加上characterEncoding=UTF8
参数,本地重新启动项目,在swagger中调用接口,最终预期数据正常返回。
现在问题是解决了,大致应该是不同版本的数据库驱动其默认字符集编码不同。这个项目的原始数据库驱动版本是8.0.32
,而我修改后继承了父依赖的版本号8.0.21
。
先看mysql-connector 8.0.32
com.mysql.cj.NativeCharsetSettings#NativeCharsetSettings
是mysql驱动 8.0.32
中的一个字符集配置相关的类,
上面的characterEncoding
字段的description的大致意思是在未主动配字符集编码时,8.0.25及以下版本的数据库驱动会使用mysql服务器的默认编码,而在8.0.26及以上版本的数据库驱动会使用utf8mb4
。
8.0.23的mysql驱动在建立socket连接之前,会调用com.mysql.cj.NativeCharsetSettings#configurePreHandshake方法
sessionCollationIndex为null,先给它赋值为utf8mb4_0900_ai_ci
字符集对应的index,在然后根据数据库服务器版本号重新进一步赋值,我们公司数据库的版本5.7.39
,此版本号小于下图中的8.0.1
会被赋值为 utf8mb4_general_ci
对应的index。所以可以看出8.0.23版本的mysql驱动的默认字符集编码是utfmb8
再看下面的connectionCollation的说明,这个属性是字符排序规则,这里还提到了字符编码。如果connectionCollation是latin1_swedish_ci,那么mysql的字符集就是latin1
,而映射到java语言就是windows-1252
字符集,如果characterEncoding
没有被设置,那么就会使用windows-1252
字符集。这里最后几句话再次说明了:characterEncoding
没有被主动设置时,在8.0.25以下版本,使用数据库server端的默认字符编码,而在8.0.26及以上版本,直接使用utf8mb4编码。
再看mysql-connector 8.0.21
类似地此版本的驱动在建立正式的socket连接之前,会调用com.mysql.cj.NativeCharsetSettings#configurePreHandshake方法
configurePreHandshake
又会调用 readServerCapabilities
方法,readServerCapabilities方法就是解析mysql server返回的第一数据报文,这个数据报文中包含了mysql server的通信协议版本、数据库版、默认字符集等信息。readServerCapabilities方法
的核心的逻辑在serverCapabilities.setInitialHandshakePacket(buf)
这行代码
从下图可以看出 mysql server的默认字符集编码是latin1
,对应的java字符集是windows-1252
为了验证mysql server的默认字符集是否是latin1, 可以在navicat执行一个这个SQL脚本
show VARIABLES like '%set_database';
下图显示其字符集编码果然是latin1
而数据库的表字段rule_category.name
是utf8mb4,在8.0.21版本的驱动使用mysql server默认的latin1编码去查询utf8mb4编码的 name
字段,当然会导致条件不匹配,查询结果为空