对于mysql而言,我摩恩知道utf8与utf8mb4两种编码之间是不同的,通常来说我们推荐使用后者,可以用来存储emoj表情;通常而言,上面的编码对于我们的实际使用并没有什么影响,然而现实总有特殊场景
下面记录一下定位mysql-connector-java客户端建立连接,设置编码的全过程
1. 编码设置解决unicode读写问题
当我们直接使用终端连接mysql时,可能会出现emoj无法正确查看的场景,如下
比如直接再终端连接mysql,查看连接编码
1 | show variables like 'char%' |
而我们实际上希望的是utf8mb4,当连接编码使用utf8时,在我们查看emoj表情会有问题
当我们修改了编码之后,则正常显示
1 | SET NAMES utf8mb4 |
那么问题来了,通过java代码连接之后,为什么我们一般都不会去主动设置 set names utfmb4
,在实际使用的时候也没有问题,why?
2. 源码分析
mysql-connector-java 5.x版本
java侧,通常是使用上面这个包来建立连接,首先是在连接url中指定编码
1 | jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai |
- 注意:
useUnicode=true&characterEncoding=UTF-8
这两个配置很重要
对于5.x系列,关键代码在
1 | com.mysql.jdbc.ConnectionImpl |
在上面的实现中有一个版本判断,当mysql服务器的版本 >= 5.5.2时,utf8mb4Supported= true
再看一下设置连接编码的条件配置
1 | if (!getUseOldUTF8Behavior()) { |
dontCheckServerMatch = false
characterSetNamesMatches
这个方法,主要是判断mysql服务器的配置character_set_client
+character_set_connection
是否与客户端设置的编码一致,若不一致表示需要修改- 比如当服务端设置的是
utf8
时,(utf8mb4Supported && !characterSetNamesMatches("utf8mb4"))
这个条件满足 - 若服务端设置的是
utf8mb4
时,!characterSetNamesMatches("utf8")
这个条件满足
- 比如当服务端设置的是
- 其次就是
collation_server
这个配置不匹配时,也会执行下面的编码设置
基于上面的分析,我们走到了set names utf8mb4
的编码设置,即不需要我们再手动去设置这个编码了,就可以愉快的使用utf8mb4
进行玩耍了
再捞一下源码提交历史,最早的这个版本限制来自于10年6月,后续又有一般通过server charaset
来判断是否可以指定utf8mb4
编码, 最后又在18年7月的时候,支持通过在url中设置参数connectionCollation
来指定具体编码(具体的源码在下面8.x版本有分析)
从提交历史来看,要使用connectionCollation
来指定链接编码时,请确保依赖版本大与等于 5.1.47
mysql-connector-java 8.x版本
8.x版本的连接之后,设置编码的逻辑与上面不太一样,核心代码在下面 (以8.0.20版本为例)
1 | com.mysql.cj.NativeSession#configureClientCharacterSet |
注意:最新版本上面设置字符编码的逻辑,迁移到
com.mysql.cj.NativeCharsetSettings#configurePostHandshake
中了
在8.x版本中,获取字符集在更前面一点,下面框出来的逻辑,主要是解析url中的connectionCollation
配置,当不存在这个配置时,若realJavaEncoding = utf8
则默认使用utf8mb4
(5.x也有下面这个逻辑,具体代码在 com.mysql.jdbc.ConnectionImpl#configureClientCharacterSet
)
因此基于上面的实现,可以通过下面的方式指定具体的编码
1 | jdbc:mysql://127.0.0.1:3306/story?connectionCollation=utf8mb4_general_ci&useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai |
最后同样看一下设置 utf8mb4 连接编码的条件限定,其实和5.x的是一致的
1 | if (dontCheckServerMatch || !this.protocol.getServerSession().characterSetNamesMatches("utf8") |
3. 解决办法
上面两个主要分析了为什么我们平时使用的时候,不需要设置连接编码,但是请注意,默认场景下并不是一定没问题,比如5.x客户端,若mysql的服务器版本小于5.5.2,那也不成,因此为了以防万一,最好的方式就是在连接url中,指定connectionCollation
,即使用下面的方式
1 | jdbc:mysql://127.0.0.1:3306/story?connectionCollation=utf8mb4_general_ci&useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=Asia/Shanghai |
connectionCollation
: 连接字符集characterEncoding
: 字符编码useUnicode
使用connectionCollation
配置时,请确保版本
- 5.x: >= 5.1.47
- 8.x: >= 8.0.13
其次就是服务器端设置默认编码为 utf8mb4
1 | default-character-set=utf8mb4 |