在JDBC中使用Whacky latin1转换为UTF8

时间:2015-01-09 18:04:44

标签: java mysql jdbc groovy utf-8

当被要求从包含未定义的latin1代码页字符的latin1列中读取时,JDBC似乎插入了utf8替换字符。这种行为与MySQL的内部功能不同。

字符编码是我在上周被困住的一个兔子洞,为了不产生100个明显的答案,我将展示几个代码示例中发生的事情。

MySQL的:

[admin@yarekt ~]$ echo 'SELECT CONVERT(UNHEX("81") using latin1);' | mysql --init-command='set names latin1' | tail -1| hexdump -C
00000000  81 0a                                             |..|
00000002
[admin@yarekt ~]$ echo 'SELECT CONVERT(UNHEX("81") using latin1);' | mysql --init-command='set names utf8' | tail -1| hexdump -C
00000000  c2 81 0a                                          |...|
00000003

这非常明显,完全符合预期。 0x81是未定义的latin1代码点。它表示为UTF8中的\u0081或十六进制中的c2 81"磁盘上的#34;。

现在奇怪来自JDBC,拿这个时髦的例子:

@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')
import groovy.sql.Sql
sql = Sql.newInstance( 'jdbc:mysql://localhost/test', 'root', '', 'com.mysql.jdbc.Driver' )
sql.eachRow( 'SELECT CONVERT(UNHEX("C281") using utf8) as a;' ) { println "$it.a --" }

此查询的输出是两个字节c2 81,如预期的那样。它很容易理解这里发生了什么。 Mysql连接默认为UTF8。 unhexxed列也被强制转换为UTF8(没有编码,因为源是二进制的,CONVERT()之后的数据仍然是c2 81)。

现在考虑这个案例。连接仍然是UTF8,与JDBC一样。我们将0x81字节转换为latin1,所以希望mysql将其转换为c2 81,就像在上面的bash示例中一样。

@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')
import groovy.sql.Sql
sql = Sql.newInstance( 'jdbc:mysql://localhost/test', 'root', '', 'com.mysql.jdbc.Driver' )
sql.eachRow( 'SELECT CONVERT(UNHEX("81") using latin1) as a;' ) { println "$it.a --" }

使用groovy latin1_test.groovy | hexdump -C运行此操作会产生以下结果:

00000000  ef bf bd 0a                                       |....|
00000004

ef bf bd是一个utf8替换字符。当utf8转换失败时使用的char。

1 个答案:

答案 0 :(得分:2)

  

当被要求从包含未定义的latin1代码页字符的latin1列读取时,JDBC似乎插入了utf8替换字符

是的,这是CharsetDecoder instances的默认行为,默认情况下,when the (byte) input is malformed将使用substitution执行此不可映射字节序列的Unicode's replacement character, U+FFFD

使用此行为的方法的示例是Reader个,但也是String构造函数,它们将字节数组作为参数。这就是为什么你永远不应该使用String存储二进制数据的原因!

唯一能解决错误的方法是获取原始字节输入,创建自己的解码器并在那种情况下将其告诉fail ......