よく混乱するのでメモ.Java + MySQL5 の環境で,Java から出力される文字列が文字列する場合の対処方法.
今回は出力時の文字化けを扱うので,DB 上のデータは正しい文字コードで保存されていることを仮定する.そうなっていなければ,それはデータ挿入(編集)時のミスである.
Java から JDBC を通して MySQL 上にある文字列を取得して表示するとき,次の2つの文字コード変換はユーザーの責任で行う必要がある.
- クライアント文字コードから,Java 内部文字コード(UCS2)への変換.
- Java 内部文字コードから,コンソールやファイルなどの出力先の文字コードへの変換.
したがって,出力結果が文字化けする場合は,次の点をチェックすれば良い.チェック時に正しい表示が得られるよう出力側から進めていくが,UTF-8 をバイナリで読める目力があれば逆順の方が効率が良い.
- Java の Writer の出力文字コードを確認し,これがコンソールなど出力先で要求される文字コードと一致するか確かめる.Writer が使う文字コードは,コンストラクタで指定されていなければ JVM のデフォルトになる.JVM のデフォルトは file.encoding システムプロパティによって決まり,それも指定されていなければ OS の標準(LANG 環境変数など)で決まる.これが出力先の文字コードと一致しない場合は,Writer のコンストラクタで明示的に指定する.
- MySQLクライアント文字コードを確認し,正しくJava String に変換されているか確かめる.クライアント文字コードは,接続時に characterEncoding で指定したもの,サーバー側の character_set_results システム変数,の順に使われる.一方で Java では,特に指定しない場合(ResultSet$getString を使う場合など) JVM のデフォルトを用いて String が生成される.したがって,クライアント文字コードと JVM のデフォルト文字コードが異なる場合は,String のコンストラクタで明示的に指定する.
すべて自分のコード上で責任を持って扱う場合は,たとえば次のようになる.このコードでは UTF-8 で転送し,SJIS で出力する.
Connection conn = DriverManager.getConnection( "jdbc:mysql:***?useUnicode=true&characterEncoding=UTF-8"); ResultSet rslt = ...; PrintWrtier pw = new PrintWriter( new OutputStreamWriter(new FileOutputStream("output.txt"), "SJIS")); ... String text = null; try { text = new String(rslt.getBytes("column"), "UTF-8"); } catch (UnsupportedEncodingException e) { ... } pw.print(text);
ここまでの説明に従えば,Connection 生成時と String 生成時の文字コード指定は同じものになるはずである.ただし,状況によってはこれが別のものになることも考えられるが,これは後述する.
上のようにしても文字化けが起こる場合はデータ取得側の責任ではないが,責任の所在を調べるにはさらに次のチェックを行う.
- DB 上の対象カラムの文字コードを確認し,実際に格納されているデータの文字コードと一致するか確かめる.カラムの文字コードはカラム宣言,テーブルの DEFAULT CHARSET,データベースの DEFAULT CHARSET,character_set_database システム変数の順に使われる.これが実際のデータと一致していれば,DB 内でデータを取り出してからクライアントで取り出されるまでに問題があるので,MySQL チームに相談する.一致していなければデータ挿入時の問題なので,テーブルの作成者に相談する.
ちなみに DB 上のカラム文字コードと実際の文字コードが一致していない場合でも,クライアント文字コードを DB 上の文字コードに合わせれば文字コード変換は行われないので,String を生成するときに元の文字コードを指定すれば値は取り出せる(上のコード例で言うと,Connection 生成時と String 生成時で文字コードが異なる).もっともこのような状況は不整合に変わりないので,テーブルの作成者に相談した方が良い.
ここまで書いて気づいたが,内容は今更感満載なのにやたらエラそうな文章になってしまった.ちなみに自分は見事にコンソールの文字コードでひっかかったタイプの人間です.