数据库字符“乱码”现象,跟页面的编码、连接池的编码、数据库的字符集参数、表和字段的字符集设置有关。而分析“乱码”现象过程中,又会受终端的字符集编码、文件的字符集编码影响。诸多因素导致“乱码”现象扑朔迷离。“眼见不一定为实”,“经验不一定总是正确的”。
本文分享对“字符集”原理和使用场景的思考,以期望能快速抓住数据库字符乱码问题的本质。
由于对OS和开发的理解不很深入,理解难免有疏漏之处,还请各位及时指出。
抽象字符表(Abstractcharacter repertoire)
抽象字符表(Abstractcharacter repertoire)是一个系统支持的所有抽象字符的集合。
“字符”可以表示数字、符号、图形、各种文字等等。“字符”的意义是人赋予的。在计算机的世界里,“字符”只是一个图形符号,没有实际意义,不能参予“运算”。计算机能识别的只有二进制数0和1。数字到图形的转换关系是由“代码页”决定的。
编码字符集(CCS:CodedCharacter Set)
编码字符集(CCS:CodedCharacter Set)是将字符集C中每个字符映射到1个坐标(整数值对:x, y)或者表示为1个非负整数N。字符集及码位映射称为编码字符集。
简单说就是把“字符”映射为其在字符表中的位置(码位)。如Unicode 通用编码字符集,其中“A”的数字表示是65. 如GB2312 或 GB 2312–80 是中华人民共和国国家标准简体中文字符集,对所收汉字进行“分区处理”,使用区位码表示,类似座标。 理论上Unicode包含了所有可能的字符,但是用这个编码对于具体的某个语言要用到的字符表来说,太过浪费空间(每个字符至少3个字节存储),于是就有了字符编码表。
字符编码表(CEF:CharacterEncoding Form)
字符编码表(CEF:CharacterEncoding Form),也称为"storage format",是将编码字符集的非负整数值(即抽象的码位)转换成有限比特长度的整型值(称为码元code units)的序列。
如果是定长编码,则是到自身的映射(数字可能会变);如果是变长编码,映射就复杂了。如在GBK编码下,汉字是两个字节存储;在UTF-8编码下,英文字母只要一个字节(码元)存储,汉字需要三个字节(码元)存储。存储的时候会面临“大端”和“小端”问题。
字符编码方案(CES:CharacterEncoding Scheme)
字符编码方案(CES:CharacterEncoding Scheme),也称作"serialization format"。
将定长的整型值(即码元)映射到8位字节序列,以便编码后的数据的文件存储或网络传输。 在使用Unicode的场合,使用一个简单的字符来指定字节顺序是大端序或者小端序(但对于UTF-8来说并不需要专门指明字节序)。 通常使用过程中不需要关注到CES。
字符“中”,Unicode是“\u4e2d”,在GBK编码里值是:D6D0,在UTF8编码里是:E4B8AD。 字符“国”,Unicode是“\u56fd”,在GBK编码里值是:B9FA,在UTF8编码里是:E59BBD。
给一个二进制的序列:E4B8ADE59BBD,计算机也不知道它代表的意义。按照UTF-8编码解释这是2个字,按GBK编码解释这是3个字。因此还要告知相应的字符编码(是GBK还是UTF-8?)。
所以,计算机里传递字符数据的时候,一定有编码值(二进制数)和具体的字符编码(其实也是二进制数)。
字符集转换的时候就是在前一个字符编码表里根据编码查找Unicode,然后在后一个字符编码表里根据Unicode找编码。 通常 字符集 ASCII < GBK < UTF8,由小到大转换可以保证前者的编码值一定能在后者中找到对应的编码。反之,则不一定。当找不到时,大部分场景的选择就是以约定的字符(?,编码3F)指定为转换后的值。也就是通常说的有乱码。
字符写入文件或者数据库表,或者写入一个Stream流等,都要把编码值转换为该字符在目标端的字符编码规则下的编码值。如果找不到对应的编码,则写入“?”(0x3F)。
如表的字段的字符集是GBK,现在有SQL文本要更新该字段为0xE4B8AD(SQL编码规则是UTF-8),则写入该字段的二进制编码应该是UTF8编码的0xE4B8AD对应的字符在GBK编码里的编码D6D0。
字符从文件或者数据库表读出,一定会读出具体的编码值(二进制)和对应的编码规则名,然后转换为接收者的字符集。
然而,从写入一个字符到数据库的表或者从表里读出一个字符到显示器,这其中的链路通常都会发生多次字符传递。
判断字符写入是否正确的唯一绝对有效的办法,就是查看该字符的十六进制以及与它所在字符集是否匹配。linux下命令是xxd,mysql下函数是hex(),oracle下函数是dump()。
通常,字符的键入都是通过键盘,字符的显示都是通过显示器。目前,没有键盘和显示器的协助,人根本无法跟计算机交流(智能机器人的内置程序也是码农通过键盘输入的)。
写入流程
有了输入法,人可以将键盘上指定的字母序列跟对应的字符关联起来,在选择好了字符回车的时候,操作系统接收了键盘驱动提供的数据以某个编码规则下的编码保存。这个编码规则的选择跟操作系统的语言设置、终端或编辑器的字符编码设置有关。
如你在终端下键入的是“中国”这个词,如果操作系统的字符编码只有ASCII的话,操作系统根本就不能转换这个词,因此屏幕也不会回显这个词。而当操作系统的语言有中文且选择中文时,操作系统会通过GBK字符集识别这个输入,记录一个0xD6D0B9FA的编码和编码名(GBK)。然后发送给终端。 如果终端的字符编码选择的是ASCII,则终端接收的是一个乱码(?,编码0x3F),屏幕回显的也是该乱码;如果终端的字符编码选择的是GBK,则终端接收的是(0xD6D0B9FA,GBK);如果终端的字符编码选择的是UTF8,则终端接收的是(0xE4B8ADE59BBD,UTF8)。
假设前面数值传递正确的话,如果上面是在终端里面的用vi编辑某个文件,如果vi内置字符集编码是utf8,则vi写入缓冲区的字符为(0xE4B8ADE59BBD,UTF8);如果vi内置字符集编码是cp936,则vi写入缓冲区的字符为(0xD6D0B9FA,cp936)。然后,vi开始保存的时候,如果vi内置字符集编码跟文件字符集编码一致,则直接写入文件;如果二者不一致,将再次转换。
Vi中写入流程如下:
键盘输入中文字符
-> OS
接收该字符并传递给终端程序
->
终端程序接收该字符并传递给vi进程
-> vi
进程接收该字符并写入到vi缓冲区
-> vi
将缓冲区的内容写入到文件中
总结:在vi里写入中文并保存,将经历这么多次可能发生字符集转换的环节.判断最终是否正确写入,直接通过命令xxd查看文件的二进制编码!
显示(读取)流程
vi里面回显的字符,显示流程是:
os
读取文件内容的字符传递给vi进程
-> vi
进程接收字符传入缓冲区并显示
->
终端程序接收该显示字符传递到终端界面
->
终端界面将该字符显示请求传递给OS图形界面
-> OS
将显示请求发给显示器
除了键盘和显示器这两个环节外,其他每个环节的前后字符集编码如果不一致,都会发生转换。为了避免转换导致数据丢失(指映射不到),OS的字符编码、终端的字符编码、ssh会话的字符编码(影响vi的缓冲区字符编码)都设置为UTF-8. 文件的编码也建议为UTF-8。
下面假设OS环节和终端环节的字符集编码都是UTF-8(不会导致字符转换损失),然后研究vi的字符乱码。
Vim 有四个跟字符编码方式有关的set选项,即: encoding、fileencoding、fileencodings、termencoding。
encoding
缩写为enc。可以通过:set enc 查看当前值。通过:set enc=utf8设置一个值。 enc值默认会根据终端会话的LANG变量选择一个最接近的字符编码。也可以在 当前用户的~/.vimrc里指定。观察发现设置gbk编码的时候,vi会自动选择为cp936编码。
bash set encoding=utf-8
encoding是vi进程的内置编码,决定了vi菜单、缓冲区、消息的字符的编码。所以,它也是影响vi界面输入和输出的关键设置。
fileencodings
缩写为fencs。可以通过 :setfencs 查看当前值。通过 :set fencs=utf8,cp936设置一序列值。 fencs可以设置一序列字符集,用于vi打开文件的时候检测该文件的字符编码。按顺序测试。一旦又发现不属于该字符集的字符,就换一下字符集测试。所以,为提高vi打开文件时编码监测效率和准确率,把utf-8设置为第一个。可以在~/.vimrc里指定。
bash set fileencodings=utf-8,ucs-bom,gbk,gb2312,cp936
fileencoding
缩写为fenc。可以通过:set fenc 查看当前值。通过:set fenc=utf8 设置一个值。 fenc为文件编码,当打开文件的时候,vi通过fencs里指定的字符集一一检测,检测到合适的字符集后就设置它为fenc的值。新建文件的时候,字符集没有设置,可以指定,也可以在保存的那一刻由vi自动从fencs里选择。逻辑跟打开时一样。
termencoding
一般都不会改这个,不用理睬。终端会话的字符集主要还是看终端软件的字符集。
a. vi 启动时,检测~/.vimrc是否设置enc值。如果没有,就根据LANG的变量值选择接近的字符集。
b. 读取要编辑的文件,根据fencs设置的字符编码逐一探测文件的字符集,并设置fenc为最接近的(不一定是最正确的)字符集。如果都不匹配,则默认用latin1编码(ASCII字符集)打开。
c. 对比fenc和enc值。若不同,则调用iconv将文件内容转换为enc指定的字符集,转换后的内容放入vi进程的缓冲区。编辑开始。
d. 编辑时,vi进程接收的字符(二进制数和对应的编码)会转换为缓冲区的字符集,保存在缓冲区中。
e. 保存时,再次比对enc和fenc值。若不同,则调用iconv将缓冲区的内容转换为fenc指定的字符集,并保存到文件中。
总结:为了避免vi产生字符乱码,把vi的选项enc和fencs都设置为utf-8。
注意:鉴别是否发生“乱码”的唯一依据是看mysql表的字段里的值的二进制编码,结合字段的字符集设置得到的字符值是否符合预期。通常应用端的展示是一个鉴别方法,但不一定能说明字符写入绝对正确或者绝对错误。
在分析mysql 相关字符乱码问题之前,先理清楚这个字符从输入到写入字段的各个环节,以及从字段读出到显示到显示器的环节。然后分析mysql的字符集参数。再分析jdbc的字符集参数原理。
写入流程
从APP开始输入:
APP
页面接收用户输入并传递给后台程序
-> APP
后台打开数据库连接并传递sql文本
-> mysql
服务端接收sql文本并解析,传递更新请求给存储引擎
-> mysql
存储引擎更新对应字段
从终端里的MySQL客户端开始输入:
终端接收键盘输入并传递给mysql客户端
-> mysql
客户端发送sql文本给mysql服务端
-> mysql
服务端接收sql文本并解析,传递更新请求给存储引擎
-> mysql
存储引擎更新对应字段
注意:之所以要列举两种类型的输入,是因为一般人分析APP的乱码时会采取在MySQL客户端里去模拟。这两者还是有细微区别。
读取流程
输出到app:
mysql
存储引擎读出字段字符
-> mysql
服务端从存储引擎接收字符并发送给客户端
-> APP
后端接收mysql服务端返回字符发送给APP前端
-> APP
前端展现该字符
输出到终端MySQL客户端:
mysql
存储引擎读出字段字符
-> mysql
服务端从存储引擎接收字符并发送给mysql客户端
-> mysql
客户端接收mysql服务端返回字符发送给终端显示界面
->
终端展现该字符(后面省略OS环节)
很显然,MySQL字符“乱码”现象就发生在其中一个或者几个环节的字符集转换上。
MySQL字符集参数跟四个参数有关系,即:
character_set_client
、character_set_connection
、character_set_results
、character_set_server
。
character_set_client
标识mysql客户端数据使用的字符集,由客户端建立会话时通过 set names 字符集 的方式设定。注意:不管客户端字符的实际字符集是什么,mysql server端都以这个参数指定的为准。
如果客户端是通过jdbc连接mysqlserver,jdbc的characterEncoding参数会指定这个值。
如果客户端是通过mysql命令行连接mysql server的,客户端的某个 my.cnf里character_set_client
值会指定这个值。如果都没有指定,那默认mysql server端的my.cnf里指定的这个值。
character_set_connection
连接层字符集,设置方法跟character_set_client
一样。这个参数作用不明,通常只要跟character_set_client
一致即可。
character_set_results
mysql server 返回给客户端的查询结果字符集。
character_set_server
MySQL默认的内部操作字符集。
character_set_server
-> character_set_database
-> 表的字符集
-> 列的字符集
通常列的字符集由离它最近的字符集(包含列自己的设置)设置而定。
建议:一个良好的习惯是针对database和表设置字符集,且尽量一致。
4.3 MySQL server端的字符集转换逻辑
1. MySQL Server收到请求时将请求数据从character_set_client
转换为character_set_connection
;
2. 进行内部操作前将请求数据从character_set_connection
转换为内部操作字符集,其确定方法如下:
a.使用每个数据字段的CHARACTER SET设定值;
b.若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值;
c.若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
d.若上述值不存在,则使用character_set_server
设定值。
e.若字符转换有损(找不到映射的编码),则报warning:1366 。
3. 将操作结果从内部操作字符集转换为character_set_results
,发送回客户端。
jdbc连接MySQL的流程跟终端连接MySQL的流程,在MySQLserver端都是一致的。
不同的之处:
1. 在于app建立jdbc连接的时候会设置这三个参数(character_set_client
、character_set_connection
、character_set_results
)的值。
2. jdbc在接收app发送数据的时候会将字符从app的字符集编码(一般是UTF8)转换为jdbc连接的character_set_client
指定的字符集。这个转换如果有损,会以乱码符号(?,编码3F)替换相应字符,然后继续。所以往往被应用忽略。
根据jdbc连接字符串指定characterEncoding的值分析。
jdbcurl里不指定characterEncoding
a) jdbcdriver跟mysql server通讯,获取server charset index (28:代表GBK) .此时还没有开始发送sql。
b) jdbcdriver发起sql 获取上面四个字符集参数值。
sql SHOW VARIABLES WHERE ... Variable_name ='character_set_client' OR Variable_name = 'character_set_connection' ... ORVariable_name = 'character_set_server' ... OR Variable_name ='character_set_results'
c) 如果第一步获取的值跟第二步获取的character_set_client
和character_set_connection
值不同,则jdbc driver发起sql :set names 字符集,字符集的值是第一步取来的。
d) 如果第二步获取的character_set_server
值非空,则jdbc driver发起sql :SET character_set_results
= NULL ,以避免数据返回时在mysql server端发生转换。换句话说,这个参数对jdbc的连接查询没有影响。
e) jdbcdriver 将app请求的sql文本转换为 character_set_client
指定的字符集,然后发送给mysql server端。
f) mysqlserver端的流程跟上面讲的一样,就不赘述。
g) mysqlserver端将数据发回给jdbc客户端后,jdbc driver将接收的数据(编码和编码对应的字符集)返回给APP。
h) APP在把从jdbc接收的数据转换为APP内部编码,显示到界面上。
i) jdbcurl里指定characterEncoding
j) 由于jdbc url指定了characterEncoding,所以不需要获取 server charsetindex。
k) 后面逻辑跟上面一致。
l) jdbcurl指定了错误的characterEncoding
m) 前4步跟上面一样。
n) jdbcdriver将app请求的sql文本转换为character_set_client
指定的字符集,然后发送给mysql server端。此时,由于characterEncoding设置不当导致转换有损,发出去的sql可能不符合语法。报语法错误。即使不报错,查询结果也不符合预期,这个后果更严重!
示例代码 MysqlDemo.java(见文末)
构造了一个包含gbk列和utf8列的表
mysql> show create table ctest\G
***************************1. row ***************************
Table: ctest
Create Table:CREATE TABLE ctest
( id
bigint(20) NOT NULL AUTOINCREMENT, name_in_gbk
varchar(50) CHARACTER SET gbk DEFAULT NULL, name_in_utf8
varchar(50) DEFAULT NULL, comment
varchar(200)DEFAULT NULL, gmt_create
timestamp NOT NULL DEFAULT CURRENTTIMESTAMP ON UPDATE CURRENTTIMESTAMP, PRIMARY KEY (id
) ) ENGINE=InnoDB AUTOINCREMENT=5 DEFAULT CHARSET=utf8
指定不同的characterEncoding测试
输出如下:
[qing.meiq@dbconsole.sqa.bja /home/qing.meiq]$java MysqlDemo
Load mysql jdbc driver successfully!
Init db connection with charset '' successfully! Create tablectest successfully! CREATE TABLE ctest
( id
bigint(20) NOT NULL AUTOINCREMENT,name_in_gbk
varchar(50) CHARACTER SET gbk DEFAULT NULL, name_in_utf8
varchar(50) DEFAULT NULL, comment
varchar(200)DEFAULT NULL, gmt_create
timestamp NOT NULL DEFAULT CURRENTTIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id
) )ENGINE=InnoDB DEFAULT CHARSET=utf8
show session character setvariables : charactersetclientutf8 charactersetconnectionutf8 charactersetresults
run insert sql [insert intoctest(nameingbk,nameinutf8,comment) values('中国', '中国', 'file:UTF-8 Unicode;jdbc:characterEncoding:'); ]
query results [select id, nameingbk, hex(nameingbk), nameinutf8,hex(nameinutf8),comment, gmtcreate fromctest order by id desc limit 1;] : id nameingbk hex(nameingbk) nameinutf8hex(nameinutf8) comment gmtcreate 1 中国 D6D0B9FA 中国 E4B8ADE59BBD file:UTF-8Unicode;jdbc:characterEncoding: 2016-01-31 23:51:15.0
Init db connection with charset'gbk' successfully! show session character set variables : charactersetclient gbkcharactersetconnectiongbk charactersetresults
run insert sql [insert intoctest(nameingbk,nameinutf8,comment) values('中国', '中国', 'file:UTF-8 Unicode;jdbc:characterEncoding:gbk'); ]
query results [select id, nameingbk, hex(nameingbk), nameinutf8,hex(nameinutf8),comment, gmtcreatefrom ctest order by id desc limit 1;] : id nameingbk hex(nameingbk) nameinutf8hex(nameinutf8) comment gmtcreate 2 中国 D6D0B9FA 中国 E4B8ADE59BBD file:UTF-8Unicode;jdbc:characterEncoding:gbk 2016-01-31 23:51:15.0
Init db connection with charset'utf8' successfully! show session character set variables : charactersetclient utf8charactersetconnectionutf8 charactersetresults
run insert sql [insert intoctest(nameingbk,nameinutf8,comment) values('中国', '中国', 'file:UTF-8 Unicode;jdbc:characterEncoding:utf8'); ]
query results [select id, nameingbk, hex(nameingbk), nameinutf8,hex(nameinutf8),comment, gmtcreatefrom ctest order by id desc limit 1;] : id nameingbk hex(nameingbk) nameinutf8hex(nameinutf8) comment gmtcreate 3 中国 D6D0B9FA 中国 E4B8ADE59BBD file:UTF-8Unicode;jdbc:characterEncoding:utf8 2016-01-31 23:51:15.0
java.sql.SQLException: Incorrectstring value: '\xF0\x9F\x98\x87 ' for column 'nameingbk' at row 1 atcom.mysql.jdbc.SQLError.createSQLException(SQLError.java:1075) atcom.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3562) atcom.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3494) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1960)at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2114) atcom.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2690) atcom.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1648) atcom.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1567) atMysqlDemo.testcase01(MysqlDemo.java:158)at MysqlDemo.main(MysqlDemo.java:229)
Init db connection with charset'latin1' successfully! show session character set variables : charactersetclient utf8charactersetconnectionutf8 charactersetresults
run insert sql [insert intoctest(nameingbk,nameinutf8,comment) values('中国CHINA', '中国CHINA', 'file:UTF-8 Unicode;jdbc:characterEncoding:latin1'); ]
query results [select id, nameingbk, hex(nameingbk), nameinutf8,hex(nameinutf8),comment, gmtcreatefrom ctest order by id desc limit 1;] : id nameingbk hex(nameingbk) nameinutf8hex(nameinutf8) comment gmtcreate 4 ??CHINA 3F3F4348494E41 ??CHINA3F3F4348494E41 file:UTF-8 Unicode;jdbc:characterEncoding:latin1 2016-01-3123:51:15.0
分析: 程序中报错“java.sql.SQLException: Incorrect stringvalue”,这个是由于往GBK列插入了一个表情字符,在字符集转换时mysql报错导致。关于表情字符的正确保存,有很多文章,这里不多说。
有些平台级的产品,涉及到数据的迁移(如DRC、DTS),或者数据的校验(TCP、AMG),都要妥善的处理字符集问题。一般是程序内部使用Unicode字符集,连接字符串使用utf8字符集,表和字段都用utf8字符集。
但是,个别老应用由于字符集是gbk,导致相关的应用和产品不得不做相应的字符集调整才能正确迁移、比对数据。有类似场景的都需要注意。
附: MysqlDemo.java
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
public class MysqlDemo {
private String s_conn_;
private String s_charset_;
private Connection obj_conn_;
private Statement obj_stmt_;
private ResultSet obj_rs_;
MysqlDemo(String conn, String charset) {
s_conn_ = conn;
s_charset_ = charset;
init();
System.out.println("Init db connection with charset '" + s_charset_.toString() + "' successfully! ");
}
protected void finalize() {
destroy();
}
private int init() {
int ret = 1;
try {
obj_conn_ = DriverManager.getConnection(s_conn_);
obj_stmt_ = obj_conn_.createStatement();
} catch (Exception e) {
e.printStackTrace();
ret = 0;
}
return ret;
}
private void destroy() {
if (obj_stmt_ != null) {
try {
obj_stmt_.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (obj_rs_ != null) {
try {
obj_rs_.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (obj_conn_ != null) {
try {
obj_conn_.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private int print_session_character() {
int ret = 1;
String sql;
try {
sql = "show session variables where variable_name in ('character_set_client', 'character_set_connection', 'character_set_results');";
obj_rs_ = obj_stmt_.executeQuery(sql);
System.out.println("show session character set variables :");
while (obj_rs_.next()) {
System.out.println(obj_rs_.getString(1) + "\t" + obj_rs_.getString(2));
}
obj_rs_.close();
} catch (Exception e) {
e.printStackTrace();
ret = 0;
} finally {
if (obj_rs_ != null) {
try {
obj_rs_.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println();
return ret;
}
private int init_table() {
String sql;
int result;
int ret = 1;
try {
sql = "drop table if exists ctest;";
result = obj_stmt_.executeUpdate(sql);
sql = "create table ctest(id bigint not null auto_increment primary key, name_in_gbk varchar(50) character set gbk, name_in_utf8 varchar(50) character set utf8, comment varchar(200) , gmt_create timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) engine=innodb default charset=utf8;";
result = obj_stmt_.executeUpdate(sql);
if (result != -1) {
System.out.println("Create table ctest successfully!");
sql = "show create table ctest";
obj_rs_ = obj_stmt_.executeQuery(sql);
while (obj_rs_.next()) {
System.out.println(obj_rs_.getString(2));
}
obj_rs_.close();
}
System.out.println();
} catch (Exception e) {
e.printStackTrace();
ret = 0;
} finally {
if (obj_rs_ != null) {
try {
obj_rs_.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println();
return ret;
}
private int test_case_01(String msg) {
String sql;
int result;
int ret = 1;
try {
sql = "insert into ctest(name_in_gbk, name_in_utf8, comment) values('" + msg.toString() + "', '" + msg.toString() + "', 'file:UTF-8 Unicode;jdbc:characterEncoding:" + s_charset_.toString() + "'); ";
result = obj_stmt_.executeUpdate(sql);
System.out.println("run insert sql [" + sql.toString() + "]");
System.out.println();
sql = "select id, name_in_gbk, hex(name_in_gbk), name_in_utf8, hex(name_in_utf8), comment, gmt_create from ctest order by id desc limit 1;";
obj_rs_ = obj_stmt_.executeQuery(sql);
System.out.println("query results [" + sql.toString() + "] :");
System.out.println("id\tname_in_gbk\thex(name_in_gbk)\tname_in_utf8\thex(name_in_utf8)\tcomment\t\t\t\t\tgmt_create");
while (obj_rs_.next()) {
System.out.println(obj_rs_.getInt(1) + "\t" + obj_rs_.getString(2) + "\t\t" + obj_rs_.getString(3) + "\t\t" + obj_rs_.getString(4) + "\t\t" + obj_rs_.getString(5) + "\t\t" + obj_rs_.getString(6) + "\t" + obj_rs_.getString(7));
}
obj_rs_.close();
} catch (Exception e) {
e.printStackTrace();
ret = 0;
} finally {
if (obj_rs_ != null) {
try {
obj_rs_.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println();
return ret;
}
public static void main(String[] args) throws Exception {
String sql;
String url = "jdbc:mysql://192.168.1.101:3307/test?user=mq&password=PaSsW0rd&useUnicode=true";
String url_gbk = "jdbc:mysql://192.168.1.101:3307/test?user=mq&password=PaSsW0rd&useUnicode=true&characterEncoding=GBK";
String url_utf8 = "jdbc:mysql://192.168.1.101:3307/test?user=mq&password=PaSsW0rd&useUnicode=true&characterEncoding=UTF8";
String url_latin1 = "jdbc:mysql://192.168.1.101:3307/test?user=mq&password=PaSsW0rd&useUnicode=true&characterEncoding=latin1";
try {
Class.forName("com.mysql.jdbc.Driver");
System.out.println("Load mysql jdbc driver successfully!");
System.out.println("---------------------------------------------------------------");
MysqlDemo test01 = new MysqlDemo(url, "");
test01.init_table();
test01.print_session_character();
test01.test_case_01("中国");
System.out.println("---------------------------------------------------------------");
MysqlDemo test02 = new MysqlDemo(url_gbk, "gbk");
test02.print_session_character();
test02.test_case_01("中国");
System.out.println("---------------------------------------------------------------");
MysqlDemo test03 = new MysqlDemo(url_utf8, "utf8");
test03.print_session_character();
test03.test_case_01("中国");
test03.test_case_01("中国😇 ");
System.out.println("---------------------------------------------------------------");
MysqlDemo test04 = new MysqlDemo(url_latin1, "latin1");
test04.print_session_character();
test04.test_case_01("中国CHINA");
System.out.println("---------------------------------------------------------------");
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}