简体   繁体   English

ORA-12704:执行可以为空的NVARCHAR的多行INSERT时字符集不匹配

[英]ORA-12704: character set mismatch when performing multi-row INSERT of nullable NVARCHAR's

Consider the following table where one of the columns is of type nullable NVARCHAR : 请考虑下表,其中一列的类型为nullable NVARCHAR

CREATE TABLE CHARACTER_SET_MISMATCH_TEST (
    ID NUMBER(10) NOT NULL,
    VALUE NVARCHAR2(32)
);

Now, I want to insert multiple data tuples into this table using the multi-row INSERT (with sub-query) syntax: 现在,我想使用多行INSERT (带子查询)语法将多个数据元组插入到此表中:

INSERT
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    SELECT ?, ? FROM DUAL
    UNION ALL
    SELECT ?, ? FROM DUAL;

If NVARCHAR values are either both NULL or both non- NULL , everything runs fine and I observe exactly 2 rows inserted. 如果NVARCHAR值既为NULL又为非NULL ,则一切运行正常,我观察到正好插入了2行。 If, however, I mix NULL and non- NULL values within a single PreparedStatement , I immediately receive an ORA-12704: character set mismatch error: 但是,如果我在单个PreparedStatement混合NULL和非NULL值,我会立即收到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)

Here's the code which reproduces the issue: 这是重现问题的代码:

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

Strangely, the above unit test passes just fine if I explicitly cast my parameters to NCHAR : 奇怪的是,如果我将参数显式地转换为NCHAR ,则上面的单元测试通过就好了:

INSERT
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    SELECT ?, TO_NCHAR(?) FROM DUAL
    UNION ALL
    SELECT ?, TO_NCHAR(?) FROM DUAL;

or switch to the INSERT ALL syntax: 或切换到INSERT ALL语法:

INSERT ALL
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    VALUES (?, ?)
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    VALUES (?, ?)
    SELECT * FROM DUAL;

But what's wrong with the original code? 但原始代码有什么问题?

Can you try to use following sql instead: 您可以尝试使用以下sql代替:

SELECT ?, cast(? as nvarchar2(32)) FROM DUAL
UNION ALL
SELECT ?, cast(? as nvarchar2(32)) FROM DUAL;

I think your error because null by default is varchar2 type and there is type mismatch in union all part of your sql. 我认为您的错误,因为默认情况下为null是varchar2类型,并且在您的sql的所有部分中都存在类型不匹配。 Btw to check that you can run this sql without insert part and see if error still exits or not. 顺便去检查你是否可以运行这个没有插入部分的SQL,看看是否仍然存在错误。

If you could intercept actual query that is sent to DB I guess it looks similiar to: 如果您可以拦截发送到DB的实际查询,我猜它看起来类似于:

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

DBFiddle Demo DBFiddle演示


In Oracle if you do: 在Oracle中如果你这样做:

SELECT N'abc' FROM dual
UNION ALL
SELECT 'abc' FROM dual

You will get error: 你会收到错误:

ORA-12704: character set mismatch ORA-12704:字符集不匹配

From UNION ALL doc : 来自UNION ALL doc

If component queries select character data, then the datatype of the return values are determined as follows: 如果组件查询选择字符数据,则返回值的数据类型确定如下:

  • If both queries select values of datatype CHAR of equal length, then the returned values have datatype CHAR of that length. 如果两个查询都选择相等长度的数据类型CHAR的值,则返回的值具有该长度的数据类型CHAR。 If the queries select values of CHAR with different lengths, then the returned value is VARCHAR2 with the length of the larger CHAR value. 如果查询选择具有不同长度的CHAR值,则返回的值为VARCHAR2,其长度为较大的CHAR值。

  • If either or both of the queries select values of datatype VARCHAR2, then the returned values have datatype VARCHAR2. 如果其中一个或两个查询选择数据类型VARCHAR2的值,则返回的值具有数据类型VARCHAR2。

So returning to your working approaches: 所以回到你的工作方法:

1) Same data type(explicit conversion) 1)相同的数据类型(显式转换)

INSERT
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    SELECT ?, TO_NCHAR(?) FROM DUAL
    UNION ALL
    SELECT ?, TO_NCHAR(?) FROM DUAL;

2) Two "independent" INSERTs : 2)两个“独立” INSERTs

INSERT ALL
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    VALUES (?, ?)
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    VALUES (?, ?)
    SELECT * FROM DUAL;

3) "If NVARCHAR values are either both NULL or both non-NULL, everything runs fine and I observe exactly 2 rows inserted" - same data type so it works fine 3)“如果NVARCHAR值既可以是NULL也可以都是非NULL,一切运行正常,我确实观察到2行插入” - 相同的数据类型,所以它工作正常

INSERT
    INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
    SELECT ?, ? FROM DUAL
    UNION ALL
    SELECT ?, ? FROM DUAL;

Finally case where there is NULL and NOT NULL value will generate error. 最后有NULLNOT NULL值的情况会产生错误。 It clearly indicates that mapping is not valid. 它清楚地表明映射无效。 I believe it is related to: 我认为这与以下内容有关:

Valid SQL-JDBC Data Type Mappings : 有效的SQL-JDBC数据类型映射

 ┌────────────────────────┬──────────────────────────────────────────┐ │ These SQL data types: │ Can be materialized as these Java types: │ ├────────────────────────┼──────────────────────────────────────────┤ │ NVARCHAR2 │ no (see Note) │ └────────────────────────┴──────────────────────────────────────────┘ 

Note: The types NCHAR and NVARCHAR2 are supported indirectly. 注意: 间接支持NCHAR和NVARCHAR2类型。 There is no corresponding java.sql.Types type, but if your application calls formOfUse(NCHAR) , then these types can be accessed. 没有相应的java.sql.Types类型,但如果您的应用程序调用formOfUse(NCHAR) ,则可以访问这些类型。

And NCHAR, NVARCHAR2, NCLOB and the defaultNChar Property in JDK 1.5 : NCHAR,NVARCHAR2,NCLOB和JDK 1.5中的defaultNChar属性

By default, the oracle.jdbc.OraclePreparedStatement interface treats the data type of all the columns in the same way as they are encoded in the database character set. 默认情况下,oracle.jdbc.OraclePreparedStatement接口以与在数据库字符集中编码相同的方式处理所有列的数据类型。 However, since Oracle Database 10g, if you set the value of oracle.jdbc.defaultNChar system property to true, then JDBC treats all character columns as being national-language. 但是,从Oracle数据库10g开始,如果将oracle.jdbc.defaultNChar系统属性的值设置为true,则JDBC会将所有字符列视为国家语言。

The default value of defaultNChar is false. defaultNChar的默认值为false。 If the value of defaultNChar is false, then you must call the setFormOfUse(, OraclePreparedStatement.FORM_NCHAR) method for those columns that specifically need national-language characters. 如果defaultNChar的值为false,则必须为那些特别需要本地语言字符的列调用setFormOfUse(,OraclePreparedStatement.FORM_NCHAR)方法。

So your could will look like: 所以你可以看起来像:

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

One more thought: Oracle treats empty string same as NULL so below code should also work fine: 还有一个想法:Oracle将空字符串视为NULL因此在代码下面也可以正常工作:

pstmt.setInt(1, 0);
pstmt.setNString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setNString(4, "");

I recommend you three check. 我建议你三检查。

First change this part: 首先改变这一部分:

pstmt.setInt(1, 0);
pstmt.setNString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setNull(4, Types.NVARCHAR);

to this: 对此:

pstmt.setInt(1, 0);
pstmt.setString(2, "NVARCHAR");
pstmt.setInt(3, 1);
pstmt.setString(4, null);

(I think its not your problem. its only a recommend because it may solve some database character set problem) (我认为这不是你的问题。它只是一个推荐,因为它可以解决一些数据库字符集问题)

Second check your connection pool character set: prefer to set "UTF-8". 第二步检查你的连接池字符集:更喜欢设置“UTF-8”。 something like this spring.datasource.connectionProperties=useUnicode=true;characterEncoding=utf-8; 像这样的东西spring.datasource.connectionProperties = useUnicode = true; characterEncoding = utf-8;

or may be you set it in application server or may be you handle it in the code. 或者您可能是在应用程序服务器中设置它,或者您可能在代码中处理它。

Third you must check your insert statement with sql tools like plsql developer or ... and test this statement directly: 第三,你必须 pqlql developer或者sql工具检查你的insert语句,并直接测试这个语句:

INSERT INTO CHARACTER_SET_MISMATCH_TEST (ID, VALUE)
SELECT 1, 'test' FROM DUAL
UNION ALL
SELECT 2, null FROM DUAL;

or even this: 甚至这个:

SELECT 1 aa, 'test' bb FROM DUAL
UNION ALL
SELECT 2 aa, null bb FROM DUAL;

If you got the error again. 如果你再次收到错误。 its because your database character set and not related to your code. 它是因为您的数据库字符集与您的代码无关。

I hope this help. 我希望这有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM