220507-mysql-connector-java-utf8mb4编码支持

文章目录
  1. 1. 编码设置解决unicode读写问题
  2. 2. 源码分析
    1. mysql-connector-java 5.x版本
    2. mysql-connector-java 8.x版本
  3. 3. 解决办法

对于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
2
3
4
5
6
7
8
9
10
if (!getUseOldUTF8Behavior()) {
if (dontCheckServerMatch || !characterSetNamesMatches("utf8") || (utf8mb4Supported && !characterSetNamesMatches("utf8mb4"))
|| (connectionCollationSuffix.length() > 0
&& !getConnectionCollation().equalsIgnoreCase(this.serverVariables.get("collation_server")))) {
execSQL(null, "SET NAMES " + utf8CharsetName + connectionCollationSuffix, -1, null, DEFAULT_RESULT_SET_TYPE,
DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false);
this.serverVariables.put("character_set_client", utf8CharsetName);
this.serverVariables.put("character_set_connection", utf8CharsetName);
}
}
  • 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
2
3
4
5
6
7
8
9
if (dontCheckServerMatch || !this.protocol.getServerSession().characterSetNamesMatches("utf8")
|| (!this.protocol.getServerSession().characterSetNamesMatches("utf8mb4")) || (connectionCollationSuffix.length() > 0
&& !connectionCollation.equalsIgnoreCase(this.protocol.getServerSession().getServerVariable("collation_server")))) {

sendCommand(this.commandBuilder.buildComQuery(null, "SET NAMES " + utf8CharsetName + connectionCollationSuffix), false, 0);

this.protocol.getServerSession().getServerVariables().put("character_set_client", utf8CharsetName);
this.protocol.getServerSession().getServerVariables().put("character_set_connection", utf8CharsetName);
}

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
# Mysql

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×