Oracle的数据库驱动有两种,oci和thin,没有用过oci,这里用的是thin。 问题描述: 有一张表MESSAGE,里面有个字段是content varchar2(4000)。 如果直接用Statement来更新这个字段,是不会用任何问题的。因为Statement的原理就是直接将整个sql语句发到数据库执行。 但是,如果用PreparedStatement的setString()方法来设置这个字段,就有问题了,当设置的字符串长度大于2000时,就会报错:”java.sql.SQLException: 数据大小超出此类型的最大值“。 问题分析: 这些天学着用iBatis,就遇到了上面的这个问题。因为如果在iBatis中使用内联参数(#做占位符),底层实现的时候就是用的PreparedStatement来实现的,所以就遇到了上面的问题。 至于为什么用PreparedStatement会出先这个问题,JavaEye上的一篇文章《当PreparedStatement中的参数遇到大字符串》给出了很好的解释(牛逼啊!)

    
现在通过Oracle提供的JDBC文档来详细看看问题的来由。
我们都知道Oracle提供了两种客户端访问方式OCI和thin,
在这两种方式下,字符串转换的过程如下:
1、JDBC OCI driver:
在JDBC文档中是这么说的:
“If the value of NLS_LANG is set to a character set other than US7ASCII or WE8ISO8859P1, then thedriver uses UTF8 as the client character set. This happens automatically and does not require any user intervention. OCI then converts the data from the database character set to UTF8. The JDBC OCI driver then passes the UTF8 data to the JDBC Class Library where the UTF8 data is converted to UTF-16. ”
 
2、JDBC thin driver:
JDBC文档是这样的:
“If the database character set is neither ASCII (US7ASCII) nor ISO Latin1 (WE8ISO8859P1), then the JDBC thin driver must impose size restrictions for SQL CHAR bind parameters that are more restrictive than normal database size limitations. This is necessary to allow for data expansion during conversion.The JDBC thin driver checks SQL CHAR bind sizes when a setXXX() method (except for the setCharacterStream() method) is called. If the data size exceeds the size restriction, then the driver returns a SQL exception (SQLException: Data size bigger than max size for this type) from the setXXX() call. This limitation is necessary to avoid the chance of data corruption when conversion of character data occurs and increases the length of the data. This limitation is enforced in the following situations:

(1)Using the JDBC thin driver (2)Using binds (not defines) (3)Using SQL CHAR datatypes (4)Connecting to a database whose character set is neither ASCII (US7ASCII) nor ISO Latin1 (WE8ISO8859P1) When the database character set is neither US7ASCII nor WE8ISO8859P1, the JDBC thin driver converts Java UTF-16 characters to UTF-8 encoding bytes for SQL CHAR binds. The UTF-8 encoding bytes are then transferred to the database, and the database converts the UTF-8 encoding bytes to the database character set encoding.” 原来是JDBC在转换过程中对字符串的长度做了限制。这个限制和数据库中字段的实际长度没有关系。 所以,setCharacterStream()方法可以逃过字符转换限制,也就成为了解决此问题的方案之一。 而JDBC对转换字符长度的限制是为了转换过程中的数据扩展。 问题解决: 根据上面的分析,可以用PreparedStatement的setCharacterStream()方法来解决问题。 在iBatis中,可以使用下面的自定义类型处理器来处理(参考文章《用ibatis向oracle数据库的varchar2(4000)列写值的问题》


/**
 * String implementation of TypeHandler
 */
public class StringTypeHandler extends BaseTypeHandler implements TypeHandler {
 /**
 * 在使用oracle时,如果在一个varchar2(4000)的字段上插入的字符过长(一般只能插入666个汉字,视字符集不同会有所不同),
 * 会导致失败,具体报错会因驱动版本不同而不同。
 * 如果使用setCharacterStream则可以插入最多的字符。因此,我将这个方法改了一下。
 */
 public void setParameter(PreparedStatement ps, int i, Object parameter,
 String jdbcType) throws SQLException {
 //原来的代码
 //ps.setString(i, ((String) parameter));

 //以下是我加的代码,为了尽量提高效率,作了一下判断
 String s = (String) parameter;
 if (s.length() < 667) { //以字符中全是汉字为底线。
 ps.setString(i, s);
 } else {
 ps.setCharacterStream(i, new StringReader(s), s.length());
 }
 }

 public Object getResult(ResultSet rs, String columnName) throws
 SQLException {
 Object s = rs.getString(columnName);
 if (rs.wasNull()) {
 return null;
 } else {
 return s;
 }
 }

 public Object getResult(ResultSet rs, int columnIndex) throws SQLException {
 Object s = rs.getString(columnIndex);
 if (rs.wasNull()) {
 return null;
 } else {
 return s;
 }
 }

 public Object getResult(CallableStatement cs, int columnIndex) throws
 SQLException {
 Object s = cs.getString(columnIndex);
 if (cs.wasNull()) {
 return null;
 } else {
 return s;
 }
 }

 public Object valueOf(String s) {
 return s;
 }
 }

 

其实,上面的解决方案是比较麻烦的,今天发现了一个更好的解决方案,就是将oracle jdbc驱动换成用于oracle10g第二版的驱动ojdbc14.jar。就行了,唉,前面折腾了几天的问题竟然可以这么容易地解决...


文章来源:http://localhost/wp2/?p=63