¿Por qué Oracle usa una longitud de byte diferente a Java para la ardilla adicional de caracteres Unicode?

8

Tengo un código Java que recorta una cadena UTF-8 al tamaño de mi columna Oracle (11.2.0.4.0) que termina arrojando un error porque Java y Oracle ven la cadena como longitudes de bytes diferentes. Verifiqué que mi NLS_CHARACTERSETparámetro en Oracle es 'UTF8'.

Escribí una prueba que ilustra mi problema a continuación usando el emoji chipmunk unicode (🐿️)

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();
}

Esto falla en la última línea de la prueba con el siguiente mensaje:

ORA-12899: valor demasiado grande para la columna
"MISQUEMA". "TEMP". "FOO" (real: 9, máximo: 7)

El ajuste de NLS_LENGTH_SEMANTICSes BYTE. Desafortunadamente, no puedo cambiar esto, ya que es un sistema heredado. No me interesa aumentar el tamaño de la columna, solo poder predecir de manera confiable el tamaño de Oracle de una cadena.

agradl
fuente
Lamentablemente, veo informes contradictorios en Internet sobre cuántos bytes debería ser. Algunos dicen 7, algunos dicen 8, algunos dicen 12 (???). ¿Qué sucede si declara el campo Oracle como 8 en lugar de 7. ¿Funciona entonces? Me doy cuenta de que eso no responde explícitamente a tu pregunta de por qué, pero puede darte una respuesta.
jcolebrand

Respuestas:

3

Lo que sigue es mi especulación.

Los Java Strings se representan internamente utilizando la codificación UTF-16 . Cuando getBytes("UTF-8")Java convierte entre las dos codificaciones, y probablemente usa una plataforma Java actualizada.

Cuando intenta almacenar un Java Stringen la base de datos, Oracle también realiza la conversión entre el UTF-16 nativo de Java y el conjunto de caracteres de la base de datos según lo determinado por NLS_CHARACTERSET.

El personaje chipmunk fue aprobado como parte del estándar Unicode en 2014 (de acuerdo con la página que ha vinculado), mientras que la última versión de Oracle 11g rel.2 se publicó en 2013 .

Se podría suponer que Oracle usa un algoritmo de conversión de caracteres diferente u obsoleto, por lo que la representación de byte de 🐿️) en el servidor (9 bytes de longitud) es diferente de la que getBytes()devuelve el cliente (7 bytes).

Supongo que para resolver este problema, puede actualizar su servidor Oracle o usar UTF-16 como el conjunto de caracteres de la base de datos.

mustaccio
fuente
Eso resolvió el problema. Mi oráculo 11g estaba usando jdk 1.6.0_141 mientras que la instancia 12 usa jdk 1.8.0_121
agradl
3
Marque la pregunta como respondida para que la siguiente persona sepa que esto funcionó :)
jcolebrand
Hablé demasiado pronto, estoy investigando más para confirmar mi sospecha: no estaba relacionado con la versión del oráculo ... estad atentos
agradl
1

El problema es con el manejo de Oracle de caracteres Unicode suplementarios cuando NLS_LENGTH_SEMANTICSes UTF8.

De la documentación (énfasis agregado).

El juego de caracteres UTF8 codifica caracteres en uno, dos o tres bytes. Es para plataformas basadas en ASCII.

Los caracteres suplementarios insertados en una base de datos UTF8 no corrompen los datos en la base de datos. Un carácter suplementario se trata como dos caracteres separados definidos por el usuario que ocupan 6 bytes. Oracle recomienda que cambie a AL32UTF8 para obtener soporte completo de caracteres suplementarios en el conjunto de caracteres de la base de datos.

Además, el último punto de código en la cadena de ardilla es un selector de variación y opcional. Vi esto usando un inspector de caracteres Unicode

Después de cambiar el NLS_CHARACTERSETparámetro de la base de datos a AL32UTF8la prueba aprobada.

agradl
fuente