使用Types.NVARCHAR与Oracle JDBC驱动程序一起处理Cyrillic字符

6
我正在尝试使用"JDK 1.6中的国家字符集类型数据新方法",以获得一个标准的JDBC解决方案来处理西里尔字母,但是当执行到任何包含NVARCHAR类型的行时,例如:
preparedSelect.setObject(3, "суббота", Types.NVARCHAR);

然后我得到了这个异常:

java.sql.SQLException: Invalid column type
    at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:70)
    at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:131)
    at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:197)
    at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:261)
    at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:269)
    at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:490)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:7922)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:7502)
    at oracle.jdbc.driver.OraclePreparedStatement.setObject(OraclePreparedStatement.java:7975)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:222)

我也尝试使用setNString(),但是我得到了一个更奇怪的异常:
java.lang.AbstractMethodError: oracle.jdbc.driver.OraclePreparedStatementWrapper.setNString(ILjava/lang/String;)V

如果我使用java -Doracle.jdbc.defaultNChar=true myApplication和普通的Types.VARCHAR,那么俄语单词会被正确存储。但是由于我正在处理遗留应用程序,因此不能使用-Doracle.jdbc.defaultNChar=true,我无法控制生产环境的运行,我只是为其编写组件。此外,"NChar How-to自述文件"指出,“这种转换具有实质性的性能影响”。因此,当只有不到1%的表需要执行此转换时,将所有内容默认设置为NChar并不是明智之选。
我使用的是Oracle Thin Driver,并且在我的类路径中有ojdbc6.jar和orai18n.jar。
我正在寻找标准的JDBC解决方案。我不能使用任何带有“oracle”常量或方法。对我来说,OraclePreparedStatement不是一个选择。
我尝试使用Types.NVARCHAR和MSSQL Server,它可以正常运行。

你尝试过使用setNString()方法并查看是否有效吗? - Aravind Yarram
我编辑了我的问题,并更新了setNString()的相关内容。 - L. Holanda
排除一些显而易见的问题:你尝试过调用那个老旧的 setString() 方法了吗?我曾经成功地使用它来存储阿拉伯字符。 - user330315
是的,我尝试过了。在那里我只得到了倒置的问号('¿¿¿¿¿')。这是因为默认的Oracle字符集不是UTF8。我在我的本地机器上安装了Oracle XE,并且使用常规的setString()存储正常。但是我不能要求超过50个客户端更改他们的默认数据库字符集,因为他们已经使用了UTF16用于N字符集。 - L. Holanda
2个回答

2
我找到了解决方案!
我之前使用的是 ojdbc 11.2.0.1。当我改用 11.2.0.2 版本时,setNString() 就可以正常工作了。 但如果我使用 setObject()Type.NVARCHAR,仍然会出现相同的错误:java.sql.SQLException: Invalid column type。Oracle太让人失望了...
总之,解决方法就是改用 ojdbc 11.2.0.2版本。

0

我之前用以下方法成功让它工作了。这些方法是从一个更大的类中摘录出来的。

import com.mchange.v2.c3p0.C3P0ProxyStatement;
import oracle.jdbc.OraclePreparedStatement;

    static {
        try {
            SET_FORM_OF_USE_METHOD = OraclePreparedStatement.class.getDeclaredMethod("setFormOfUse", new Class[] { Integer.TYPE, Short.TYPE });
        } catch (NoSuchMethodException ex) {
            LOG.fatal("Can't find the setFormOfUse method", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }


    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws SQLException {
        if (st instanceof OraclePreparedStatement) {
            ((OraclePreparedStatement)st).setFormOfUse(index, OraclePreparedStatement.FORM_NCHAR); 
        } else if (st instanceof C3P0ProxyStatement) {
            try {
                C3P0ProxyStatement c3p0St = (C3P0ProxyStatement) st;
                c3p0St.rawStatementOperation(SET_FORM_OF_USE_METHOD, C3P0ProxyStatement.RAW_STATEMENT, new Object[]{index, OraclePreparedStatement.FORM_NCHAR});
            } catch (IllegalAccessException ex) {
                throw new UnexpectedException("Error calling setFormOfUse through C3P0", ex);
            } catch (IllegalArgumentException ex) {
                throw new UnexpectedException("Error calling setFormOfUse through C3P0", ex);
            } catch (InvocationTargetException ex) {
                throw new UnexpectedException("Error calling setFormOfUse through C3P0", ex);
            }
        } else {
            throw new IllegalArgumentException("Unkown PreparedStatement implementation: " + st.getClass() + ". Maybe an unknown connection pool is hiding the OraclePreparedStatement?");
        }

        st.setString(index, (String) value);
    }

nullSafeSet方法的结构是直接在OraclePreparedStatement实例上工作,或者在您拥有C3P0连接池的情况下,在C3P0ProxyStatement上工作;如果使用不同的连接池,则必须使用其他选项。

此方法适用于来自Oracle 10.2.0.4的ojdbc14.jar。


1
这个解决方案的问题在于它是依赖于Oracle的。我正在寻找一些符合JDBC标准的解决方案,因为我们的系统同时支持MSSQL Server和Oracle。看起来微软的JDBC驱动比Java本身更符合Java规范。 - L. Holanda
就ojdbc14.jar而言,可能没有标准的jdbc方法来处理它。我不确定您使用的驱动程序版本;无论如何,如果这有效,您可以将其用于Oracle,并恢复到其他所有内容的标准方式。 - Flavio
2
正如在“JDK 1.6中用于国家字符集类型数据的新方法”中所述,他们说“使用setFormOfUse方法……不建议,因为该方法将在未来的版本中被弃用”,并且他们还说:“如果使用setObject方法,则必须将目标数据类型指定为Types.NCHAR、Types.NCLOB、Types.NVARCHAR或Types.LONGNVARCHAR”。 - L. Holanda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接