web-dev-qa-db-ja.com

オラクルが補足のユニコード文字シマリスにJavaとは異なるバイト長を使用するのはなぜですか?

JavaコードがUTF-8文字列をOracle(11.2.0.4.0)列のサイズにトリミングしており、Java Oracleは、文字列を異なるバイト長として認識します。OracleのNLS_CHARACTERSETパラメータが 'UTF8'であることを確認しました。

私は nicodeシマリス絵文字 (????️)を使用して、以下の私の問題を説明するテストを書きました

public void test() throws UnsupportedEncodingException, SQLException {
    String squirrel = "\uD83D\uDC3F\uFE0F";
    int squirrelByteLength = squirrel.getBytes("UTF-8").length; //this is 7
    Connection connection = dataSource.getConnection();

    connection.prepareStatement("drop table temp").execute();

    connection.prepareStatement("create table temp (foo varchar2(" + String.valueOf(squirrelByteLength) + "))").execute();

    PreparedStatement statement = connection.prepareStatement("insert into temp (foo) values (?)");
    statement.setString(1, squirrel);
    statement.executeUpdate();
}

これはテストの最後の行で失敗し、次のメッセージが表示されます。

ORA-12899:列の値が大きすぎます
"MYSCHEMA"。 "TEMP"。 "FOO"(実際:9、最大:7)

NLS_LENGTH_SEMANTICSの設定はBYTEです。残念ながら、これはレガシーシステムなので変更できません。列サイズの増加には興味がなく、文字列のOracleサイズを確実に予測できます。

8
agradl

この問題は、NLS_LENGTH_SEMANTICSUTF8である場合のOracleによる補足のUnicode文字の処理に関連しています。

documentation から(強調を追加)。

UTF8文字セットは、文字を1、2、または3バイトにエンコードします。 ASCIIベースのプラットフォーム用です。

UTF8データベースに挿入された補足文字は、データベース内のデータを破損しません。 補助文字は、6バイトを占有する2つの個別のユーザー定義文字として扱われます。Oracleは、AL32UTF8に切り替えて、データベースの文字セット。

さらに、リス文字列の最後のコードポイントはバリエーションセレクターであり、オプションです。 Unicode文字インスペクターを使用してこれを見た

データベースのNLS_CHARACTERSETパラメータをAL32UTF8に変更した後、テストに合格しました。

1
agradl

以下は私の推測です。

Java Stringsは 内部的にUTF-16エンコーディングを使用して表されます です。 getBytes("UTF-8") Javaは2つのエンコーディング間で変換し、おそらく最新のJavaプラットフォームを使用します。

Java Stringをデータベースに保存しようとすると、OracleはJavaネイティブUTF-16とデータベース文字の間の変換も実行します_NLS_CHARACTERSET_の決定に従って設定されます。

シマリスのキャラクターは、2014年に(リンクしたページに従って)Unicode標準の一部として承認されましたが、Oracle 11g rel.2の最新リリース 2013年に公開されました

Oracleが異なるまたは古い文字変換アルゴリズムを使用しているため、サーバー(長さ9バイト)の????️)のバイト表現が、クライアントでgetBytes()が返すもの(7バイト)とは異なると想定している場合があります。 )。

この問題を解決するには、Oracleサーバーをアップグレードするか、データベースの文字セットとしてUTF-16を使用できます。

3
mustaccio