임동문의 횡설수설

dmlim.egloos.com

포토로그


라이프로그


Unicode Surrogate Area 캐릭터의 MySQL 입력시 에러 프로그래밍

자바데몬에서 한글을 입력시에 MySQL 에 Insert 하는 과정에서 아래와 같은 Exception 이 종종 발생 했는데.. 며칠 삽질한 끝에 원인을 찾았습니다.

Incorrect string value: '\xF0\x9F\x98\x8D\xF0\x9F...' for column 'NICKNAME' at row 1java.sql.SQLException: Incorrect string value: '\xF0\x9F\x98\x8D\xF0\x9F...' for column 'NICKNAME' at row 1
    com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1056)
    com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957)
    com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3376)
    com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3308)
    com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837)
    com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961)
    com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2543)
    com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1737)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2022)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1940)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1925)
    nateon.qsync.table.SyncStatementInfo.sync(SyncStatementInfo.java:100)
    nateon.qsync.table.TableDistribute.syncRecord(TableDistribute.java:118)
    nateon.qsync.QSyncEntry.run(QSyncManager.java:124)
   
문제는 Surrogate Area 라고 불리는 유니코드의 영억 때문인데요.
D800-DBFF, DC00-DFFF 이 영억에는 실제로 매핑된 캐릭터가 오는게 아니고, UTF-16 확장을 위해서 미리 잡아놓은 영역입니다.

JAVA String, Oracle NVARCHAR 는 UTF-16 입니다. (UCS-2 아님)
즉 UCS-4, UCS-8 도 수용이 가능하다는 얘기이지요.


그런데, MySQL의 유니코드 캐릭터셋은 아마도 UTF-16 이 아닌 UCS-2 를 그대로 사용하는 듯 합니다.
그래서 이 예비 영역의 글자가 들어오면 인식하지 못하고 에러를 뱉어 냅니다.

해결책은 아래 샘플 코드 처럼 스트링에서 Surrogate Area 에 있는 캐릭터를 제거하는 겁니다.

 public static String removeSurrogateArea(String val)
 {
  if ( val == null ) return null;
  
  StringBuffer buf = new StringBuffer();
  int len = val.length();
  for ( int i = 0 ; i < len ; i++ )
  {
   char c = val.charAt(i);
   if ( 0xD800 <= c && c <= 0xDBFF ||
     0xDC00 <= c && c <= 0xDFFF )
   {
   }
   else
   {
    buf.append(c);
   }
  }
  return buf.toString();
 }



아래는 wiki 에서 찾은 Surrogates 의 원문입니다.

http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates

SurrogatesThe 2,048 surrogates are not characters, but are reserved for use in UTF-16 to specify code points outside the Basic Multilingual Plane. They are divided into leading or "high surrogates" (D800-DBFF) and trailing or "low surrogates" (DC00-DFFF). In UTF-16, they must always appear in pairs, as a high surrogate followed by a low surrogate, thus using 32 bits to denote one code point.

10000 + (H - D800) × 400 + (L - DC00)
where H and L are the numeric values of the high and low surrogates respectively.

Since high surrogate values in the range DB80-DBFF always produce values in the Private Use planes, the high surrogate range can be further divided into (normal) high surrogates (D800-DB7F) and "high private use surrogates" (DB80-DBFF).


핑백

덧글

  • 본인입니다 2013/08/02 16:36 # 삭제 답글

    Mysql 5.5 버전부터 utf8mb4 라는 인코딩이 추가되었는데.. 이 컬럼 타입으로 만들면 오류가 나지 않습니다.
  • 본인입니다 2013/08/02 16:38 # 삭제 답글

    아이폰의 이모티콘 자판에서 입력한 캐릭터들이 이영역을 사용하는군요..
  • 2014/02/13 10:48 # 삭제 답글 비공개

    비공개 덧글입니다.
  • 김정미 2014/06/18 10:43 # 삭제 답글

    db charset도 utf8mb4_unicode_ci로 셋팅하고, my.cnf 에 collation-server = utf8_unicode_ci 로 셋팅을 해야 하더라구요~ ㅎㅎ
댓글 입력 영역