解决 JSP 与 MySQL 下各种中文乱码问题

近来 Web 课需要处理 JSP、MySQL 前台后台各种,不可避免地遇到了乱码问题。经过无数折腾以后终于(算是)解决,遂将经验总结如下:

本文记录了解决过程。想看结论及解决方案可以直接拉到最后。

Lab7 中已经遇到了连接数据库乱码的问题,当时的情况是:

MySQL 默认安装的编码是 Latin1(ISO8859-1) Eclipse 默认编码是 GBK ,后改为 UTF-8

当时的现象: MySQL 使用 UTF-8 建的数据库

DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci

在命令行中可以正常输入中文并在命令行中显示,但是从 Java 中读取而且从 Java 中插入数据库的中文会乱码。

当时的解决方法: 读取时:

String sql = "SELECT * FROM user";
Statement stm = connection.createStatement();
ResultSet rs = stm.executeQuery(sql);
while (rs.next()) {
    String isbn = rs.getString("Name");
    String bookName = rs.getString("Username");
    bookName = new String(bookName.getBytes("iso8859-1"), "gbk");
    System.out.println(bookName + ":" + isbn);
}

写入时:

String sql = String.format("INSERT INTO ISBN_LIST (ISBN, BOOK_NAME) VALUES ('%s' , '%s');", isbn, bookName);
sql = new String(sql.getBytes("gbk"), "iso8859-1");
Statement stm = connection.createStatement();
int row = stm.executeUpdate(sql);
System.out.println("ok," + row + "row(s) affected");

为什么要转字符而且和 UTF-8 完全无关?不明! ( PS :后来了解到 MySQL 变量因此转 ISO8859-1 应该是这方面的原因,但是 GBK 为什么有仍然不明)

后来的探索: 因写 Lab 时中文是从 Eclipse 中打进去的,而 Project 的中文是从表单中用户输入获得的,上文的方法依旧导致乱码。 ( PS :后来意识到中文从前台传到后台还没写入数据库就已经乱码,因此如果再转一次可能可以解决,但已经没办法回去试了,而且这样也不是长久之计,因为 MySQL 本身设置有问题)

网上查询各种方法未果。 因为数据库建成时已经指定了 UTF-8 ,因此网上说的临时改字符集的方法都是没有用的。 网上某处看到 show variables like 'character_set_%'; 查看 MySQL 的环境变量,发现除了指定了 database 是 UTF-8 ,其他有好多 Latin1 ,通过 SET character_set_database = utf8; 之类的方法全改成 UTF-8 并若干次重开 MySQL 服务(伴随删除数据表再新建)后发现重启 MySQL 后仍然还原成默认设置。也通过修改 my.ini 尝试过,但是仍有两项为 Latin1 这时候我认为把所有的改成 UTF-8 可以解决问题,因此在这种思路下在网上找到了真正的永久修改的方法:在 MySQL 安装目录下 bin 下找到 MYSQLInstanceConfig.exe 重新配置安装选项,在选择编码时选择 UTF-8 ,这样成功将所有环境变量变成 UTF-8 。( PS :这样也解决了 mysqldump 导出后中文乱码的问题)

然而仍然不成功,而且发现这时候在命令行下也无法输入中文的值了。因为之前也看到过命令行的默认编码是 GBK ,会影响中文的显示(但是试过改成 UTF-8 结果出了一堆窗口上的变化导致不敢乱试而且觉得并不是主要),因此使用 SET NAMES 'GBK' ,这样可以在命令行显示中文了,但在命令行看并没有什么用,最终还是要解决 JSP 下获取的问题.

寄希望于能在 JSP 下正常得到,新建了一个 servlet 用来在网页中输出当前数据库的各条记录,使用本文开头 Lab 里所用的转换编码的方法多次尝试,但发现一点用处没有。这使我将目光转向生成 SQL 语句时的编码。生成 SQL 语句后同样按照本文开头 Lab 中的方法对其自身转换编码,虽然回回得到的还是乱码,但是乱码的形式在改变,证明这里可以起作用,或许只要找到正确的转换即可解决。这个时候我注意到之前插的一句调试语句:输出从前台得到的 name 的值,如果从前台得到的就是乱码,那么插进去肯定还是乱码(我也真是迟钝明明之前已经放了这个语句然而写完居然忘记了又去试验别的方法) 发现果然这个值得到的时候就已经是乱码了,观察乱码的形式感觉像是西欧文字,因此将转换的语句写为

sql = new String(sql.getBytes("iso8859-1"), "utf8");

果然可以正常显示中文了,我认为至此得到了使用上的解决 以前因为网页啊 Eclipse 啊都是 UTF-8 因此前一个编码认为应该是 UTF-8 ,但是后面不管怎么试都没用。这样确定了前面是西欧文字后,因为 MySQL 等都被我改成了 UTF-8 ,后面自然就觉得是 UTF-8 但是,为什么从前台得到的中文是西欧文字,并不知道

另:此时重开 MySQL 命令行,因为没有设置 SET NAMES ‘GBK’ ,所以在 MySQL 中显示中文变了乱码,但是既然 JSP 得到是正常了,我认为命令行中 SET NAMES ‘GBK’ ,或者把命令行编码改成 UTF-8 应该能显示正常。 ( PS: 的确如此)

// 一天过去了 5.22 昨天只解决了 name 字段的中文乱码问题,而 name 字段只是一个普通的字段。今天当测试 username 字段为中文时出现了其他的问题。 username 字段需要用在登录页面中进行 SQL 查询,而且需要保存在 cookie 中 首先登录时使用中文名字果然无法登录,提示用户名或密码错误,结合昨天的经验容易想到在登录验证的 SQL 查询语句后增加

sql = new String(sql.getBytes("iso8859-1"), "utf8");

然而并没有什么用(事实上是可以的,只是 Eclipse 的默认浏览器出了问题)

又想到使用 Ajax 进行实时检测时也要进行 SQL 查询,试验后发现对于中文名字重复无法正常提示用户名已被占用,怀疑是编码的问题但是无论怎么改都没用,而后调试输出发现并没有什么问题,最后去掉了改编码的代码,在 chrome 下可以正常提示,网上的说法是 Ajax 是默认用 UTF-8 传值的,看来这个问题已经得到了解决 PS:此时 Eclipse 默认浏览器还是错误的。这个浏览器真是害人不浅,浪费了我大量时间,现在已经把运行的默认浏览器改成了 chrome ,只不过无法通过 system.out.println 方便调试了(更正:可以调试)。 知道了 Eclipse 浏览器的问题,换用 chrome ,登录问题也可以得到解决.

此外我还探索了为什么前台后台传值使用 ISO8859-1 的问题,网上有说法说要改 Tomcat 的 server.xml 文件。我在安装目录和 Eclipse 的 project explorer 下的 Tomcat 都做了修改然而并没有什么用。或许是我没改对吧。不过更多的地方说更改这个文件并不是什么好方法,因为实际情况下有时不能改变服务器配置。因此作罢。

随后是 cookie 存中文的问题,存中文会报错说有控制字符。这其实是一个小问题了,查阅网上资料后得知只要 URLEncoder.encode(username, "UTF-8") 即可,这时候中文变成 %C3%A5%C2%95%C2%8A%C3%A5%C2%95%C2%8A%C3%A5%C2%95%C2%8A 这样的形式,可以正常存储了,但是在读取时却出了问题。 首先是 JS 读取 cookies 问题,用于在主页显示当前登录用户的用户名。然而用 JS 读出来并解码后发现还是乱码,西欧字符。经过检查, JS 读取 cookies 的内容是没问题的,就是之前存入的 cookies 的内容,这样问题就很明显了。因为从前台到后台得到的中文本来就是西欧字符,为了方便起见我在构成 SQL 查询字符串后进行了转码,这样不用单独对每一个得到的有可能是中文的前台表单元素在后台获得时就挨个进行转码。但是这样的话在存进 cookies 时所用来编码的 username 仍然是西欧字符的 username 。 因此,在保存 cookies 时先将 username 转码再 encode 存储即可。 注意: JS 解码时应使用 decodeURI 函数而不是 unescape 函数,后者仍然使用西欧编码解码。 注意:这样 SQL 和 cookie 处都有需要解码的地方,感觉看起来略杂乱,觉得还是不能偷懒,在获得前台表单值时就应立即转码。

接下来是 servlet(JSP) 读取 cookies 问题,用于访问主页时根据 cookies 中是否有值判断应跳转至主页还是登录页面。既然前面已经将 cookies 正确储存了,这里就没什么问题了,直接在得到 cookies 以后 URLDecoder.decode(cUsername,"UTF-8") 即可。 注意:这里的 SQL 查询构建 SQL 语句后就不用从西欧转到 UTF-8 了,因为这里有可能是中文的字段并不是从前台得到,而是从 cookie 中格式正确的中文得到。

结论

为了消灭乱码,请如下操作:

一次性操作:

  1. 将 Eclipse 默认编码设置为 UTF-8
  2. 在 MySQL 安装目录下 bin 下找到 MYSQLInstanceConfig.exe 重新配置安装选项,在选择编码时选择 UTF-8 (这样也不需要在每次建库时用长长一串指定 UTF-8 了)
  3. (补充)Eclipse:Window => Preference => Web=> Jsp file下也改编码为 UTF-8 ,这个值不随 Eclipse 默认值改变,如果不改的话每次新建 Jsp 还要手动把三处 ISO8859-1 改成 UTF-8

编程时需要注意的:

  1. 后台接收前台传来的值时,如果值有可能是中文,接收完立即调用 str = new String(str.getBytes("iso8859-1"), "utf8")转码(立即调用:这样进行 SQL 查询和存 cookie 时都不用额外转码) (5.23 PS:按 ltl 同学的方法,在接收值之前加上request.setCharacterEncoding("UTF-8")可以解决这个问题,照原理来看应该也是可以的,然而我之前试验过没有成功,可能是那时候 Eclipse 自带浏览器的问题,待试验。如果这个证明可行,那么这一点可以和“其他”下的第 2 点归结为同一点,即后台 servlet 的 doGetdoPost 在开始写代码前都要把 request 和 response 的编码设一遍。)
  2. 中文存进 cookies 时对可能为中文的字段需额外编码, URLEncoder.encode(str,"UTF-8") 使用 Java 读取时使用 URLDecoder.decode(str, ”UTF-8″) 解码。使用 JS 读取时使用 decodeURI(str)解码
  3. 以上步骤正确,那么 Ajax 不需要额外操作

其他:

  1. Eclipse 自带浏览器难以信任,请使用外部浏览器测试。(然而 chrome 多次测试后也出现了不正常的现象,可能需要休息一会?)
  2. 关于在 servlet 中使用 out.print 输出引起乱码的问题:
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
TAGS:  JavaJSPMySQL
正在加载,请稍候……