`

MySQL-JDBC 编码问题

 
阅读更多

测试配置

用户实例配置,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(); -->

   encoding.jpg

    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

  1. 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

 

    传递参数.jpg

 

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的编码语言和服务端是一致的。

  encoding.jpg

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics