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