测试配置
用户实例配置,my.cnf [mysqld] default-character-set = gbk
测试结论
characterEncodingAsString 为JDBC中的字符编码
serverCharsetIndex 为jdbc中存储的server端编码
serverCharSet (我们用这个变量来代表实际mysql server会话连接编码)
当调用setNames的时候jdbc的上面两个值都没有更新,但是mysql server端是更新了,所以出现了乱码。
without characterEncoding=UTF-8 | with characterEncoding=UTF-8 | |
doHandshake阶段 | characterEncodingAsString = null serverCharsetIndex = 28 (GBK) serverCharSet = gbk socket交互统一使用characterEncodingAsString 如果为null,则使用UTF-8 |
characterEncodingAsString = utf8 serverCharsetIndex = 28 (GBK) serverCharSet = utf8 jdbc同socket交互统一使用characterEncodingAsString 也就是UTF-8,其实这里我猜测发送了个set name utf8命令过去了,具体代码得看下mysql的源码了。 |
configureClientCharacterSet阶段 | characterEncodingAsString = GBK serverCharsetIndex = GBK serverCharSet = gbk
|
characterEncodingAsString = utf8 serverCharsetIndex = 28 (GBK) serverCharSet = utf8 |
执行SQL阶段 | 此时执行SQL都是使用GBK编码所以不会乱码 | 其实本以为这里会乱码的,后来想明白了,原来serverCharsetIndex = 28这个值在jdbc里面已经是错误的了,不用关注。虽然JDBC内部两个变量不一致,但 是characterEncodingAsString 和mysql server端的编码方式是一致的。 |
set names utf8 | characterEncodingAsString = GBK serverCharsetIndex = gbk serverCharSet = utf8 此时的jdbc端是错误的 但是此时的server的连接已经切换成utf8 |
同样不会乱码 |
set names gbk | jdbc characterEncodingAsString = utf8 serverCharsetIndex = 28 (GBK) serverCharSet = gbk |
|
执行SQL阶段 | 此时JDBC将SQL转为GBK,而同时服务端使用的是UTF8编码 所以出现乱码问题 |
执行失败,中文乱码了。 因为characterEncodingAsString 和serverCharSet 不一致。 |
JDBC URL不带characterEncoding=UTF-8的测试场景
1. doHandshake前阶段
从ConnectionImpl.java定位到ConnectionImpl()函数里面的
initializeDriverProperties(info); -->
postInitialization(); -->
this.characterEncodingAsString = ((String) this.characterEncoding.getValueAsObject());
从这里可以看出characterEncodingAsString这个变量取值是null
2. doHandshake阶段
从ConnectionImpl.java定位到ConnectionImpl()函数里面的 -->
createNewIO(false);-->
connectOneTryOnly(isForReconnect, mergedProps); -->
coreConnect(mergedProps); -->
this.io.doHandshake(this.user, this.password, this.database);-->
this.serverCharsetIndex = buf.readByte() & 0xff;
这里可以看出mysql server socket 用一个int表示编码类型,
具体mysql server从根据哪个参数的编码类型来发这个int 需要查看mysql源码,猜测是default-character-set
其实里面有个map根据这个int 就可以获取到相应的编码类型
this.io.doHandshake(this.user, this.password, this.database);-->
secureAuth411(null, packLength, user, password, database, true); -->
String enc = getEncodingForHandshake(); -->
packet.writeString(user, enc, this.connection); -->
writeStringNoNull(s, encoding, encoding, false, conn);
3.configureClientCharacterSet阶段
connectOneTryOnly(isForReconnect, mergedProps); -->
initializePropsFromServer(); -->
configureClientCharacterSet(false); -->
获取第二步编码类型
realJavaEncoding = getEncoding(); // 获取url的编码 这里是null
String serverEncodingToSet = CharsetMapping.getJavaEncodingForCollationIndex(this.io.serverCharsetIndex);
setEncoding(serverEncodingToSet);
这里差不多完成了编码的赋值,执行executeSQL的时候就会调用这两种编码。
JDBC URL带characterEncoding=UTF-8
-
doHandshake前阶段
代码同上可以看到characterEncodingAsString 已经能够取到utf8,所以使用socket验证的时候都会使用utf8编码进行。
this.characterEncodingAsString = ((String) this.characterEncoding.getValueAsObject());
这个props参数中包含了characterEncoding=UTF-8属性,所以猜测mysql server接受到的报文中含有这个参数,会自动
调用set names utf8。因为JDBC源码中发现没有在这里设置set names但是连接的character_set_client,character_set_connection
的两个变量都从gbk变成了utf8。
character_set_client
2. doHandshake阶段
和上面类似。
3.configureClientCharacterSet阶段
realJavaEncoding = getEncoding(); // 获取url的编码 这里是null
String serverEncodingToSet = CharsetMapping.getJavaEncodingForCollationIndex(this.io.serverCharsetIndex);
setEncoding(serverEncodingToSet);
代码已经将characterEncodingAsString 设置成GBK因为mysql server是GBK的
这块比较特殊,发现代码的后面还会将characterEncodingAsString设置成UTF-8,
所以在jdbc中debug的时候这两个编码不一样,却不出现乱码,因为jdbc的编码语言和服务端是一致的。