Oracle的數(shù)據(jù)庫驅(qū)動(dòng)有兩種,oci和thin,沒有用過oci,這里用的是thin。 問題描述: 有一張表MESSAGE,里面有個(gè)字段是content varchar2(4000)。 如果直接用Statement來更新這個(gè)字段,是不會(huì)用任何問題的。因?yàn)镾tatement的原理就是直接將整個(gè)sql語句發(fā)到數(shù)據(jù)庫執(zhí)行。 但是,如果用PreparedStatement的setString()方法來設(shè)置這個(gè)字段,就有問題了,當(dāng)設(shè)置的字符串長度大于2000時(shí),就會(huì)報(bào)錯(cuò):”java.sql.SQLException: 數(shù)據(jù)大小超出此類型的最大值“。 問題分析: 這些天學(xué)著用iBatis,就遇到了上面的這個(gè)問題。因?yàn)槿绻趇Batis中使用內(nèi)聯(lián)參數(shù)(#做占位符),底層實(shí)現(xiàn)的時(shí)候就是用的PreparedStatement來實(shí)現(xiàn)的,所以就遇到了上面的問題。 至于為什么用PreparedStatement會(huì)出先這個(gè)問題,JavaEye上的一篇文章《當(dāng)PreparedStatement中的參數(shù)遇到大字符串》給出了很好的解釋(牛逼?。。?/p>

    
現(xiàn)在通過Oracle提供的JDBC文檔來詳細(xì)看看問題的來由。
我們都知道Oracle提供了兩種客戶端訪問方式OCI和thin,
在這兩種方式下,字符串轉(zhuǎn)換的過程如下:
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在轉(zhuǎn)換過程中對(duì)字符串的長度做了限制。這個(gè)限制和數(shù)據(jù)庫中字段的實(shí)際長度沒有關(guān)系。 所以,setCharacterStream()方法可以逃過字符轉(zhuǎn)換限制,也就成為了解決此問題的方案之一。 而JDBC對(duì)轉(zhuǎn)換字符長度的限制是為了轉(zhuǎn)換過程中的數(shù)據(jù)擴(kuò)展。 問題解決: 根據(jù)上面的分析,可以用PreparedStatement的setCharacterStream()方法來解決問題。 在iBatis中,可以使用下面的自定義類型處理器來處理(參考文章《用ibatis向oracle數(shù)據(jù)庫的varchar2(4000)列寫值的問題》


/**
 * String implementation of TypeHandler
 */
public class StringTypeHandler extends BaseTypeHandler implements TypeHandler {
 /**
 * 在使用oracle時(shí),如果在一個(gè)varchar2(4000)的字段上插入的字符過長(一般只能插入666個(gè)漢字,視字符集不同會(huì)有所不同),
 * 會(huì)導(dǎo)致失敗,具體報(bào)錯(cuò)會(huì)因驅(qū)動(dòng)版本不同而不同。
 * 如果使用setCharacterStream則可以插入最多的字符。因此,我將這個(gè)方法改了一下。
 */
 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;
 }
 }

 

其實(shí),上面的解決方案是比較麻煩的,今天發(fā)現(xiàn)了一個(gè)更好的解決方案,就是將oracle jdbc驅(qū)動(dòng)換成用于oracle10g第二版的驅(qū)動(dòng)ojdbc14.jar。就行了,唉,前面折騰了幾天的問題竟然可以這么容易地解決...


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