2012年10月13日 星期六

MySQL Server 與中文

MySQL Server 與中文

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

絕大多數的 Java 程式開發人員在使用 MySQL 之後,都會發現 Java 程式 在存取中文資料的時候很容易出現亂碼,但是英文的資料卻不會。這個問題 之所以存在的主要原因之一就是資料的編碼;身為一個程式開發人員,如果你的 程式(不限定於 Java)發生了中文亂碼的情形,你應該問問自己以下的問題: 資料庫儲存中文資料時,是以什麼 樣的編碼方式儲存,大五碼嗎?資料庫跟客戶端程式互相傳遞資料的時候, 它們又是以什麼樣的編碼在傳遞,大五碼嗎?你的應用程式中的中文資料 是以什麼方式編碼,大五碼嗎?在了解了這些編碼的情形之後,絕大部分的 問題應該可以迎刃而解。


如果世界上存在一種標準,要求所有上述的情形都是以大五碼的方式進行, 那麼這篇文章就不需要存在了。可是,對於那些不使用大五碼的國家呢? 他們該怎麼辦?所以,為了設計上的彈性,大多數的系統都會使用某一種 特定的預設編碼;如果你需要特定的編碼,你就必須仔細看完安裝手冊, 然後依據該手冊的要求進行安裝。問題是大多數人是不看手冊的(你是在 說我嗎?你可以再靠近一點 ^_^),或者手冊編寫的不好。
所以,這篇文章就是要談一談 MySQL Server 跟中文使用上的一些細節。 如果你是第一次安裝 MySQL Server 的人,請直接依據 在 Windows 安裝 Noinstall Zip Archive 版的 MySQL中,my.ini 的設定即可。 (如果你使用的是 Linux 的話,你需要依據環境的需求來更改設定檔;以 Debian 上利用 apt-get 的方式安裝 MySQL 5.0.x 版的話,你需要 修改 /etc/mysql/my.cnf 的內容) 如果你已經在 MySQL Server 中建立了一些資料,我們建議把這些資料 (也就是表格以及資料庫)刪除掉,然後依據我們的設定修改後,重新啟動 MySQL。如果你已經有很多資料,那麼請仔細看一下以下的說明,然後自己找出 一套適當的方法(不論是適當的轉碼還是"備份後還原")。"備份後還原"的方式 我還沒試過,或許幾年後我的伺服器需要升級的時候,我再來看看這些步驟該如何進行, 如果精神好的話,或許再寫一篇文章也說不定。
雖然 MySQL Server 從 4.1.16 以及 5.0.16 以後的版本就開始支援中文的大五碼, 但是在標準安裝的過程中,它使用的預設編碼是 latin1。你可以使用 mysql 執行檔來檢查;如果輸入 status;,你會看到如下的畫面:

從這個畫面中,我們可以看到:(a)MySQL Server 的版本是 5.1.44 的 Windows 版;(b)總共有四個地方跟字元編碼有關,它們分別 Server、Db(Database;資料庫) 、Client、和 Conn.(Connection;連線);而它們的編碼都是 latin1。 如果想進一步了解究竟有多少設定跟編碼有關,我們可以輸入 SHOW VARIABLES LIKE 'char%';,其畫面如下:

從畫面中,我們可以看出,除了之前的四種設定之外,還多了 filesystem(檔案 系統)、results(查詢結果)、以及 system(系統)。我們相信這種標準的預設 編碼方式,Windows 和 Linux 是相同的,因為我們使用的 Debian 上 MySQL 5.0.x 版也呈現類似的結果。
依據 Wikipedia 的說明,latin1 其實就是 ISO-8859-1 的編碼;也就是說, 如果你是依據 MySQL 預設的編碼來儲存中文資料,在你的程式送出 Big5 的資料之前, 請先以 ISO-8859-1 的方式先行轉碼,然後中文資料會以 ISO-8859-1 的編碼方式 儲存在資料庫中。之後,如果你需要將中文資料擷取出來的話,那麼你可以要求 顯示的編碼方式為 ISO-8859-1 或者(如果顯示的編碼方式為 Big5)將 ISO-8859-1 的資料轉碼成 Big5。
依據這個原則所寫出來的程式如以下的完整程式碼;在此我們把所謂的轉碼方式 說明一下。在查詢的時候,我們依據 rs.getString(i) 的方式把資料 從資料庫中以字串的方式取出來;由於該字串是以 ISO-8859-1 的方式編碼, 我們可以利用 getBytes("ISO-8859-1") 的方式把字串的內容一個 byte 一個 byte 的以 ISO-8859-1 編碼方式取出並形成一個 byte 陣列,然後以 String 的建構元,請它依據 Big5 的編碼方式形成字串,因此資料轉換的方式 如下:
System.out.print(new String(rs.getString(i).getBytes("ISO-8859-1"), "Big5"));
至於如何將 Big5 的資料轉成 ISO-8859-1 的資料然後再送給 MySQL 呢? 其概念跟前一段的說法一樣,只是轉碼的方向從 ISO-8859-1 --> Big5 變成 Big5 --> ISO-8859-1 而已。因此,不論是 insert(新增) 或者 update(修改),我們都需要把 executeUpdate(SQL) 改成
executeUpdate(new String(SQL.getBytes("Big5"), "ISO-8859-1"));
完整的程式碼如下。我們使用的是 MySQL 的 JDBC 驅動程式 -- MySQL Connector/J。
import java.sql.*;

public class NewJDBC {
  static String classname = "com.mysql.jdbc.Driver";
  static String jdbcURL = "jdbc:mysql://localhost/eric";
  static String UID = "jlu";
  static String PWD = "newpasswd";
  static Connection conn = null;

  public static void main( String argv[] ) {
    // initialize query string
    if(argv.length != 1) {
      System.out.println("Usage: java NewJDBC ");
      System.out.println("   ex. java NewJDBC Product");
      System.exit(2);
    }
    String aQuery = "select * from " + argv[0];
    String iSQL = "insert into " + argv[0] + " values(5,'鍵盤',14.5,2)";
    String uSQL = "update " + argv[0] + " set Name='無線鍵盤' where ID=5";
    String dSQL = "delete from " + argv[0] + " where ID=5";

    try {
      // load the JDBC-ODBC bridge driver
      Class.forName(classname).newInstance();

      // connect to Database
      conn = DriverManager.getConnection(jdbcURL,UID,PWD);

      // Display current content
      System.out.println("Display current content");
      ShowResults(aQuery);

      // Insert a new record
      System.out.println("\nInserting a new record .....");
      InsertNew(iSQL);
      ShowResults(aQuery);

      // Update record
      System.out.println("\nUpdateing a record .....");
      UpdateNew(uSQL);
      ShowResults(aQuery);

      // Delete record
      System.out.println("\nDeleting a record .....");
      DeleteNew(dSQL);
      ShowResults(aQuery);

      conn.close();
    } catch (Exception sqle) {
      System.out.println(sqle);
      System.exit(1);
    }
  }

  private static void DeleteNew(String dSQL) {
    try {
      Statement aStatement = conn.createStatement();
      aStatement.executeUpdate(dSQL);
    } catch (Exception e) {
      System.out.println("Delete Error: " + e);
      System.exit(1);
    }
  }

  private static void UpdateNew(String uSQL) {
    try {
      Statement aStatement = conn.createStatement();

      // ISO-8859-1 其實就是 latin1 的編碼
      // 以下的目的是要把 BIG5 的字串轉成 byte array 然後再轉成符合
      // 目前的 latin1 的編碼
      aStatement.executeUpdate(new String(uSQL.getBytes("BIG5"), "ISO-8859-1"));
    } catch (Exception e) {
      System.out.println("Update Error: " + e);
      System.exit(1);
    }
  }

  private static void InsertNew(String iSQL) {
    try {
      Statement aStatement = conn.createStatement();

      // 與 UpdateNew 一樣
      aStatement.executeUpdate(new String(iSQL.getBytes("BIG5"), "ISO-8859-1"));
    } catch (Exception e) {
      System.out.println("Insert Error: " + e);
      System.exit(1);
    }
  }


  private static void ShowResults(String aQuery) {
    try {
      // Construct a SQL statement and submit it
      Statement aStatement = conn.createStatement();
      ResultSet rs = aStatement.executeQuery(aQuery);

      // Get info about the query results
      ResultSetMetaData rsmeta = rs.getMetaData();
      int cols = rsmeta.getColumnCount();

      // Display column headers
      for(int i=1; i<=cols; i++) {
        if(i > 1) System.out.print("\t");
        System.out.print(rsmeta.getColumnLabel(i));
      }
      System.out.print("\n");

      // Display query results.
      while(rs.next()) {
        for(int i=1; i<=cols; i++) {
          if (i > 1) System.out.print("\t");

          // 由於資料庫的預設編碼為 latin1,所以傳出的也是 latin1,
          // 因此我們需要把所得到的字串從 latin1 轉成我們可以看得到
          // 的 BIG5
          System.out.print(new String(rs.getString(i).getBytes("ISO-8859-1"), 
                           "BIG5"));
        }
        System.out.print("\n");
      }

      // Clean up
      rs.close();
      aStatement.close();
    }
    // a better exception handling can be used here.
    catch (Exception e) {
      System.out.println("Exception Occurs.");
    }
  }
}



Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu













沒有留言:

張貼留言