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サイズを確実に予測できます。
この問題は、NLS_LENGTH_SEMANTICS
がUTF8
である場合のOracleによる補足のUnicode文字の処理に関連しています。
documentation から(強調を追加)。
UTF8文字セットは、文字を1、2、または3バイトにエンコードします。 ASCIIベースのプラットフォーム用です。
UTF8データベースに挿入された補足文字は、データベース内のデータを破損しません。 補助文字は、6バイトを占有する2つの個別のユーザー定義文字として扱われます。Oracleは、AL32UTF8に切り替えて、データベースの文字セット。
さらに、リス文字列の最後のコードポイントはバリエーションセレクターであり、オプションです。 Unicode文字インスペクターを使用してこれを見た
データベースのNLS_CHARACTERSET
パラメータをAL32UTF8
に変更した後、テストに合格しました。
以下は私の推測です。
Java String
sは 内部的に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を使用できます。