Oracle中Clob类型处理解析
最近利用NHibernate映射類型為Clob字段在插入數據時發現當字符的字節數(一個半角字符一個字節,一個全角字符兩個字節)在2000-4000之間時報錯(ORA-01461:僅可以插入LONG列的LONG值賦值)。經過不斷查找資料和自己的試驗該問題終于得到解決,下邊我將自己的心得給大家做一個分享。
準備
系統環境xp+.net2.0+oracle9i
表結構(由于是測試,表結構隨便建了一張)XX
|
字段名 |
類型 |
|
ID |
VARCHAR2(70) |
|
TEST |
CLOB |
測試
方式1:直接將CLOB的值拼寫在SQL語句中。
代碼:
stringid=Guid.NewGuid().ToString();
OracleCommandcmd=Conn.CreateCommand();
cmd.CommandText="insertintoxx(id,test)values('"+id+"','"+data+"')";//data是一個變量,存儲你要插入的字符串
cmd.ExecuteNonQuery();
情況分析:
當data的長度大于4000時報錯(ORA-01704:文字字符串過長),小于或等于4000時正常插入。
原因分析:
之所以會出現長度大于4000時報錯,是因為Oracle中有SQL語句中兩個單引號之間的字符數不能大于4000的限制。'"+ data +"'data在sql語句之間,當data的值大于4000個字節時就會報錯。
解決辦法:
這種方式比較棘手,但有更好的方式,下邊會講到。
方式2:采用參數形式。
代碼:
stringid=Guid.NewGuid().ToString();
OracleCommandcmd=Conn.CreateCommand();
cmd.CommandText="insertintoxx(id,test)values('"+id+"',:p1)";
OracleParameterp1=newOracleParameter("p1",OracleType.Clob);
p1.Value=data;//data是一個變量,存儲你要插入的字符串
cmd.Parameters.Add(p1);
cmd.ExecuteNonQuery();
情況分析:
采用這種方式能夠正常插入。所以推薦用這種方式。
原因分析:
無
解決辦法:
無
方式3:采用參數形式,但是參數類型寫為OracleType. NVarChar
代碼:
stringid=Guid.NewGuid().ToString();
OracleCommandcmd=Conn.CreateCommand();
cmd.CommandText="insertintoxx(id,test)values('"+id+"',:p1)";
OracleParameterp1=newOracleParameter("p1",OracleType.NVarChar);
p1.Value=data;//data是一個變量,存儲你要插入的字符串
cmd.Parameters.Add(p1);
cmd.ExecuteNonQuery();
情況分析:
為什么要寫這種方式,因為這種方式和采用NHibernate的方式很相似,先看看在這種方式會產生什么情況。當data的字節數在0-2000之間時正常插入,大于4000時也正常插入,但在2000-4000時則失敗,報錯(ORA-01461:僅可以插入LONG列的LONG值賦值)
原因分析:
沒有采用對應的Oracle類型。
解決辦法:
采用OracleType.Clob
下邊采用NHibernate插入數據,NHibernate具體怎用不在本次討論范圍。
NHibernate采用的版本為1.2.1.4000。
下邊大至把簡要配置寫下。
App.config
<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<configSections>
<sectionname="nhibernate"type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</configSections>
<nhibernate>
<addkey="hibernate.connection.provider"value="NHibernate.Connection.DriverConnectionProvider"/>
<addkey="hibernate.connection.driver_class"value="NHibernate.Driver.OracleClientDriver"/>
<addkey="hibernate.connection.isolation"value="ReadCommitted"/>
<addkey="hibernate.dialect"value="NHibernate.Dialect.Oracle9Dialect"/>
<addkey="hibernate.connection.connection_string"
value="Data Source=Orcl_192.168.0.232;User ID =icqs_test;Password=icqs_test"/>
<addkey="show_sql"value="true"/>
<add
key="hibernate.adonet.batch_size"
value="100"
/>
</nhibernate>
</configuration>
xx.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespaceTest.Enties
{
[Serializable]
publicclassXx
{
publicXx()
{
}
privatestringid;
publicvirtualstringId
{
get{returnid;}
set{id=value;}
}
publicvirtualstringTest
{
get{returntest;}
set{test=value;}
}
privatestringtest;
}
}
xx.hbm.xml
<?xmlversion="1.0"?>
<hibernate-mappingxmlns="urn:nhibernate-mapping-2.2"namespace="Test.Enties"assembly="Test">
<classname="Xx"table="xx"lazy="true">
<idname="Id"column="id"type="String">
<generatorclass="assigned"/>
</id>
<propertycolumn="test"type="StringClob"name="Test"length="2147483647"/>
</class>
</hibernate-mapping>
說明:
<addkey="hibernate.connection.driver_class"value="NHibernate.Driver.OracleClientDriver"/>這里的驅動用的NHibernate.Driver.OracleClientDriver,其實是對微軟的OracleClient的封裝啦,其實內部還是調用微軟的OracleClient的東東。引用System.Data.OracleClient.dll即可OracleClient。
做好上邊的配置后,便有了以下的方式
方式4:采用NHibernate
代碼:
stringid=Guid.NewGuid().ToString();
Xxxx=newXx();
xx.Test=data;//data是一個變量,存儲你要插入的字符串
xx.Id=id;
ISessionsession=SessionFactory.OpenSession();
session.Save(xx);
session.Flush();
情況分析:
當data的字節數在0-2000之間時正常插入,大于4000時也正常插入,但在2000-4000時則失敗,報錯(ORA-01461:僅可以插入LONG列的LONG值賦值).情況和方式3的情況一樣。
原因分析:
NHibernate在用OracleClient映射StringClob時,設置參數類型為OracleType.NVarChar,導致插入有BUG。網上有人推測是OracleClient的BUG所致,理由是換用OracleDataAccess即可解決。
為什么說NHibernate將參數類型設置為OracleType.NVarChar呢?看下邊
找到NHibernate的源代碼,把它加入你的工程。記得不要移動NHibernate位置直接加入工程,直接在NHibernate的安裝目錄引用進來。
2. 在Test解決方案中添加NHibernate的項目引用。
經過上邊兩個步驟我們就可以跟蹤調試NHibernate了
跟蹤代碼session.Save(xx);看看它究竟做了啥。
當我們跟進CommandSetBatchingBatcher時,可以得到以下信息(如圖中的調試信息)。CurrentBatch類型是OracleClientCommandSet,OracleClientCommandSet看源碼得知是對微軟的OracleCommandSet的封裝,因為這個類internal sealed class,所以我們的程序里是找不到這個類的,不過NHibernate通過反射使用了它的功能。OracleCommandSet可能用作批處理的,就是一次處理多個SQL語句的,不是太了解,誰知道請指教。
CommandSetBatchingBatcher的源碼
internalclassOracleClientCommandSet:DbCommandSet<OracleConnection,OracleCommand>
{
privatestaticSystem.TypeoracleCmdSetType;
staticOracleClientCommandSet()
{
AssemblysysDataOracleClient=Assembly.Load("System.Data.OracleClient,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089");
oracleCmdSetType=sysDataOracleClient.GetType("System.Data.OracleClient.OracleCommandSet");
Debug.Assert(oracleCmdSetType!=null,"CouldnotfindOracleCommandSet!");
}
protectedoverrideobjectCreateInternalCommandSet()
{
returnActivator.CreateInstance(oracleCmdSetType,true);
}
}
跟蹤CurrentBatch可以看到
CommandText:
declare
type refcursortype is ref cursor;
begin
INSERT INTO z3 (test, id) VALUES (:p2, :p3);
:r1_4 := sql%rowcount;
end;
這里的p2就是我們的Clob類型字段的參數啦。
再看p2的OracleType是NVarChar,是不是有點明白啦,對了,跟我們3一樣,參數類型錯掉了。
解決辦法:
使用NHibernate的自定義類型,不是太會,幸好網上有高人提供代碼,在此想高人致謝。這樣我們通過自定義類型來設置正確的OracleType即可。在項目中添加兩個類。
PatchForOracleLobField.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Data;
usingSystem.Text;
usingNHibernate;
usingNHibernate.SqlTypes;
usingNHibernate.UserTypes;
namespaceTest.type
{
publicabstractclassPatchForOracleLobField:IUserType
{
publicPatchForOracleLobField()
{
}
publicboolIsMutable
{
get{returntrue;}
}
publicSystem.TypeReturnedType
{
get{returntypeof(String);}
}
publicSqlType[]SqlTypes
{
get
{
returnnewSqlType[]{NHibernateUtil.String.SqlType};
}
}
publicobjectDeepCopy(objectvalue)
{
returnvalue;
}
publicnewboolEquals(objectx,objecty)
{
returnx==y;
}
publicintGetHashCode(objectx)
{
returnx.GetHashCode();
}
publicobjectAssemble(objectcached,objectowner)
{
returnDeepCopy(cached);
}
publicobjectDisassemble(objectvalue)
{
returnDeepCopy(value);
}
publicobjectNullSafeGet(IDataReaderrs,string[]names,objectowner)
{
returnNHibernate.NHibernateUtil.StringClob.NullSafeGet(rs,names[0]);
}
publicabstractvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex);
publicobjectReplace(objectoriginal,objecttarget,objectowner)
{
returnoriginal;
}
}
}
OracleClobField.cs
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Data;
usingSystem.Data.OracleClient;
usingSystem.Text;
namespaceTest.type
{
publicclassOracleClobField:PatchForOracleLobField
{
publicoverridevoidNullSafeSet(IDbCommandcmd,objectvalue,intindex)
{
if(cmdisOracleCommand)
{
//CLob、NClob類型的字段,存入中文時參數的OracleDbType必須設置為OracleDbType.Clob
//否則會變成亂碼(Oracle10gclient環境)
OracleParameterparam=cmd.Parameters[index]asOracleParameter;
if(param!=null)
{
param.OracleType=OracleType.Clob;// 關鍵就這里啦
param.IsNullable=true;
}
}
NHibernate.NHibernateUtil.StringClob.NullSafeSet(cmd,value,index);
}
}
}
然后在映射文件中修改類型即可。
Com.Dic.Icqs.Entities.Type.OracleClobField,Com.Dic.Icqs.Entities
修改前:
<propertycolumn="test"type="StringClob"name="Test"length="2147483647"/>
修改后:
<propertycolumn="test"type="Test.type.OracleClobField,Test"name="Test"length="2147483647"/>
Test.type.OracleClobField是類的完整名,Test即OracleClobField所在的程序集。
總結
以上是生活随笔為你收集整理的Oracle中Clob类型处理解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2.0米办公桌多少钱
- 下一篇: 常见的GC日志笔记