Почему Oracle использует длину байт, отличную от java, для дополнительного символа unicode chipmunk?

8

У меня есть Java-код, обрезающий строку UTF-8 до размера моего столбца Oracle (11.2.0.4.0), который в итоге выдает ошибку, потому что Java и Oracle видят строку как разные длины байтов. Я подтвердил, что мой NLS_CHARACTERSETпараметр в Oracle - «UTF8».

Я написал тест, который иллюстрирует мою проблему ниже, используя эмодзи бурундук 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();
}

Это завершается с ошибкой в ​​последней строке теста со следующим сообщением:

ORA-12899: слишком большое значение для столбца
"MYSCHEMA". "TEMP". "FOO" (фактическое: 9, максимальное: 7)

Настройка NLS_LENGTH_SEMANTICSесть BYTE. К сожалению, я не могу изменить это, поскольку это устаревшая система. Я не заинтересован в увеличении размера столбца, просто могу надежно предсказать размер строки Oracle в Oracle.

agradl
источник
К сожалению, я вижу противоречивые сообщения в Интернете о том, сколько это должно быть байтов. Некоторые говорят 7, некоторые говорят 8, некоторые говорят 12 (???). Что произойдет, если вы объявите поле Oracle как 8 вместо 7. Работает ли оно тогда? Я понимаю, что это не дает четкого ответа на ваш вопрос, почему, но это может дать вам ответ.
Jcolebrand

Ответы:

3

Далее следует мое предположение.

Java Stringс являются внутренне представлены с использованием UTF-16 кодировки . Когда вы getBytes("UTF-8")преобразовываете Java между двумя кодировками и, вероятно, используете современную платформу Java.

Когда вы пытаетесь сохранить Java Stringв базе данных, Oracle также выполняет преобразование между собственным UTF-16 Java и набором символов базы данных, как определено NLS_CHARACTERSET.

Символ бурундука был утвержден как часть стандарта Unicode в 2014 году (согласно странице, на которую вы ссылаетесь), в то время как последний выпуск Oracle 11g rel.2 был опубликован в 2013 году .

Можно предположить, что Oracle использует другой или устаревший алгоритм преобразования символов, поэтому байтовое представление 🐿️) на сервере (длина 9 байт) отличается от того, что getBytes()возвращает клиент (7 байт).

Я думаю, что для решения этой проблемы вы можете обновить сервер Oracle или использовать UTF-16 в качестве набора символов базы данных.

mustaccio
источник
Это решило проблему. Мой оракул 11g использовал jdk 1.6.0_141, в то время как экземпляр 12 использует jdk 1.8.0_121
agradl
3
Пожалуйста, пометьте вопрос как ответивший, чтобы следующий человек знал, что это сработало :)
jcolebrand
Я говорил слишком рано, я продолжаю расследование, чтобы подтвердить свое подозрение - это не было связано с версией оракула ... следите за обновлениями
agradl
1

Проблема связана с обработкой Oracle дополнительных символов Юникода, когда NLS_LENGTH_SEMANTICSесть UTF8.

Из документации (выделение добавлено).

Набор символов UTF8 кодирует символы в один, два или три байта. Это для платформ на основе ASCII.

Дополнительные символы, вставленные в базу данных UTF8, не повреждают данные в базе данных. Дополнительный символ обрабатывается как два отдельных пользовательских символа, которые занимают 6 байтов. Oracle рекомендует переключиться на AL32UTF8 для полной поддержки дополнительных символов в наборе символов базы данных.

Кроме того, последняя кодовая точка в строке белка является селектором вариантов и является необязательной. Я видел это с помощью инспектора символов Unicode

После изменения базы данных NLS_CHARACTERSETпараметров для AL32UTF8испытания прошли.

agradl
источник