ORA-12704: несоответствие набора символов при выполнении многорядной вставки нулевых NVARCHAR
рассмотрим следующую таблицу, где один из столбцов имеет тип nullable NVARCHAR
:
sql prettyprint-override">CREATE TABLE CHARACTER_SET_MISMATCH_TEST (
ID NUMBER(10) NOT NULL,
VALUE NVARCHAR2(32)
);
теперь я хочу вставить несколько кортежей данных в эту таблицу, используя многорядный INSERT
(с подзапросом) синтаксис:
INSERT
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT ?, ? FROM DUAL
UNION ALL
SELECT ?, ? FROM DUAL;
если NVARCHAR
значения либо оба NULL
или какNULL
, все работает нормально, и я наблюдаю ровно 2 строки вставлены. Если, Однако, я смешиваю NULL
и неNULL
значения в пределах одного PreparedStatement
, я немедленно получаю ORA-12704: character set mismatch
ошибка:
java.sql.SQLException: ORA-12704: character set mismatch
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:452)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:400)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:884)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:471)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:199)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:535)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:238)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1385)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1709)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:4364)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:4531)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:5575)
вот код, который воспроизводит проблему:
package com.example;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import javax.sql.DataSource;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import oracle.jdbc.pool.OracleConnectionPoolDataSource;
import oracle.jdbc.pool.OracleDataSource;
public final class Ora12704Test {
@NonNull
private static final String SQL = "INSERT INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE) SELECT ?, ? FROM DUAL UNION ALL SELECT ?, ? FROM DUAL";
@Nullable
private static DataSource dataSource;
@Nullable
private Connection conn;
@BeforeClass
public static void setUpOnce() throws SQLException {
dataSource = new OracleConnectionPoolDataSource();
((OracleDataSource) dataSource).setURL("jdbc:oracle:thin:@:1521:XE");
}
@BeforeMethod
public void setUp() throws SQLException {
this.conn = dataSource.getConnection("SANDBOX", "SANDBOX");
}
@AfterMethod
public void tearDown() throws SQLException {
if (this.conn != null) {
this.conn.close();
}
this.conn = null;
}
@Test
public void testNullableNvarchar()
throws SQLException {
try (final PreparedStatement pstmt = this.conn.prepareStatement(SQL)) {
pstmt.setInt(1, 0);
pstmt.setNString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setNull(4, Types.NVARCHAR);
final int rowCount = pstmt.executeUpdate();
assertThat(rowCount, is(2));
}
}
}
как ни странно, приведенный выше модульный тест проходит просто отлично, если я явно приведу свои параметры к NCHAR
:
INSERT
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT ?, TO_NCHAR(?) FROM DUAL
UNION ALL
SELECT ?, TO_NCHAR(?) FROM DUAL;
или переключиться на INSERT ALL
синтаксис:
INSERT ALL
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
VALUES (?, ?)
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
VALUES (?, ?)
SELECT * FROM DUAL;
а что не так с исходным кодом?
3 ответов
если бы вы могли перехватить фактический запрос, который отправляется в БД, я думаю, он выглядит похожим на:
INSERT
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT 0, 'abc' FROM DUAL
UNION ALL
SELECT 1, CAST(NULL AS NVARCHAR2(100)) FROM DUAL;
-- ORA-12704: character set mismatch
-- or
INSERT
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT 0, N'abc' FROM DUAL
UNION ALL
SELECT 1, CAST(NULL AS VARCHAR2(100)) FROM DUAL;
-- ORA-12704: character set mismatch
в Oracle, если вы:
SELECT N'abc' FROM dual
UNION ALL
SELECT 'abc' FROM dual
вы получите ошибку:
ORA-12704: несоответствие набора символов
если запросы компонентов выбирают символьные данные, то тип данных возвращаемые значения определяются следующим образом:
если оба запроса выбирают значения типа данных CHAR равной длины, то возвращаемые значения имеют тип данных CHAR этой длины. Если запросы выбирают значения CHAR разной длины, то возвращается значение VARCHAR2 с длиной большего значения CHAR.
если один или оба запроса выбирают значения типа данных VARCHAR2, то возвращаемые значения имеют тип данных VARCHAR2.
Итак, возвращаясь к вашим рабочим подходам:
1) тот же тип данных (явное преобразование)
INSERT
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT ?, TO_NCHAR(?) FROM DUAL
UNION ALL
SELECT ?, TO_NCHAR(?) FROM DUAL;
2) два "независимых" INSERTs
:
INSERT ALL
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
VALUES (?, ?)
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
VALUES (?, ?)
SELECT * FROM DUAL;
3)" Если значения NVARCHAR являются либо нулевыми, либо ненулевыми, все работает нормально, и я наблюдаю точно вставленные строки 2 " -тот же тип данных, поэтому он отлично работает
INSERT
INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT ?, ? FROM DUAL
UNION ALL
SELECT ?, ? FROM DUAL;
наконец-то случай где есть NULL
и NOT NULL
значение будет генерировать ошибку. Это ясно указывает на то, что сопоставление недопустимо. Я считаю, что это связано с:
допустимые сопоставления типов данных SQL-JDBC:
┌────────────────────────┬──────────────────────────────────────────┐ │ These SQL data types: │ Can be materialized as these Java types: │ ├────────────────────────┼──────────────────────────────────────────┤ │ NVARCHAR2 │ no (see Note) │ └────────────────────────┴──────────────────────────────────────────┘
Примечание.: типы NCHAR и NVARCHAR2 поддерживаются косвенно. нет соответствующей java.язык SQL.Types type, но если ваше приложение вызывает formOfUse (NCHAR), то эти типы могут быть доступны.
и типа nchar, NVARCHAR2, NCLOB и свойство defaultNChar в JDK 1.5:
по умолчанию, oracle.интерфейс jdbc.Интерфейс OraclePreparedStatement обрабатывает тип данных всех столбцов так же, как они кодируются в наборе символов базы данных. Однако, начиная с Oracle Database 10g, если вы установите значение oracle.интерфейс jdbc.системное свойство defaultNChar имеет значение true, затем JDBC обрабатывает все символьные столбцы как национальные.
значение по умолчанию defaultNChar является ложным. Если значение defaultNChar равно false, необходимо вызвать setFormOfUse (, OraclePreparedStatement.FORM_NCHAR) метод для тех столбцов, которые специально нуждаются в символах национального языка.
так что ваш может выглядеть:
pstmt.setInt(1, 0);
pstmt.setFormOfUse(2, OraclePreparedStatement.FORM_NCHAR);
pstmt.setNString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setFormOfUse(4, OraclePreparedStatement.FORM_NCHAR);
pstmt.setNull(4, Types.NVARCHAR);
еще одна мысль: Oracle обрабатывает пустую строку так же, как NULL
поэтому ниже код также должен работать нормально:
pstmt.setInt(1, 0);
pstmt.setNString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setNString(4, "");
можете ли вы попробовать использовать следующий sql вместо этого:
SELECT ?, cast(? as nvarchar2(32)) FROM DUAL
UNION ALL
SELECT ?, cast(? as nvarchar2(32)) FROM DUAL;
Я думаю, что ваша ошибка, потому что null по умолчанию является типом varchar2, и есть несоответствие типов в union всей части вашего sql. Кстати, чтобы проверить, что вы можете запустить этот sql без вставки части и посмотреть, если ошибка все еще выходит или нет.
Я рекомендую вам три чека.
первый изменить эту часть:
pstmt.setInt(1, 0);
pstmt.setNString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setNull(4, Types.NVARCHAR);
для этого:
pstmt.setInt(1, 0);
pstmt.setString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setString(4, null);
(Я думаю, это не твоя проблема. его только рекомендуют, потому что он может решить некоторую проблему набора символов базы данных)
второй проверьте набор символов пула соединений: предпочитаете установить "UTF-8". что-то вроде этого весна.источник.connectionProperties=useUnicode=true; characterEncoding=utf-8;
или может быть, вы установите его на сервере приложений или можете обрабатывать его в коде.
третий вы должны Проверьте инструкцию insert с помощью sql tools как разработчик plsql или ... и проверьте это утверждение напрямую:
INSERT INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT 1, 'test' FROM DUAL
UNION ALL
SELECT 2, null FROM DUAL;
или даже так:
SELECT 1 aa, 'test' bb FROM DUAL
UNION ALL
SELECT 2 aa, null bb FROM DUAL;
если вы снова получили ошибку. потому что ваш набор символов базы данных и не связано с вашим кодом.
Я надеюсь, что это поможет.