Oracle中Clob类型处理解析
最近利用NHibernate映射類型為Clob字段在插入數(shù)據(jù)時(shí)發(fā)現(xiàn)當(dāng)字符的字節(jié)數(shù)(一個(gè)半角字符一個(gè)字節(jié),一個(gè)全角字符兩個(gè)字節(jié))在2000-4000之間時(shí)報(bào)錯(cuò)(ORA-01461:僅可以插入LONG列的LONG值賦值)。經(jīng)過不斷查找資料和自己的試驗(yàn)該問題終于得到解決,下邊我將自己的心得給大家做一個(gè)分享。
準(zhǔn)備
系統(tǒng)環(huán)境xp+.net2.0+oracle9i
表結(jié)構(gòu)(由于是測試,表結(jié)構(gòu)隨便建了一張)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是一個(gè)變量,存儲你要插入的字符串
cmd.ExecuteNonQuery();
情況分析:
當(dāng)data的長度大于4000時(shí)報(bào)錯(cuò)(ORA-01704:文字字符串過長),小于或等于4000時(shí)正常插入。
原因分析:
之所以會出現(xiàn)長度大于4000時(shí)報(bào)錯(cuò),是因?yàn)镺racle中有SQL語句中兩個(gè)單引號之間的字符數(shù)不能大于4000的限制。'"+ data +"'data在sql語句之間,當(dāng)data的值大于4000個(gè)字節(jié)時(shí)就會報(bào)錯(cuò)。
解決辦法:
這種方式比較棘手,但有更好的方式,下邊會講到。
方式2:采用參數(shù)形式。
代碼:
stringid=Guid.NewGuid().ToString();
OracleCommandcmd=Conn.CreateCommand();
cmd.CommandText="insertintoxx(id,test)values('"+id+"',:p1)";
OracleParameterp1=newOracleParameter("p1",OracleType.Clob);
p1.Value=data;//data是一個(gè)變量,存儲你要插入的字符串
cmd.Parameters.Add(p1);
cmd.ExecuteNonQuery();
情況分析:
采用這種方式能夠正常插入。所以推薦用這種方式。
原因分析:
無
解決辦法:
無
方式3:采用參數(shù)形式,但是參數(shù)類型寫為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是一個(gè)變量,存儲你要插入的字符串
cmd.Parameters.Add(p1);
cmd.ExecuteNonQuery();
情況分析:
為什么要寫這種方式,因?yàn)檫@種方式和采用NHibernate的方式很相似,先看看在這種方式會產(chǎn)生什么情況。當(dāng)data的字節(jié)數(shù)在0-2000之間時(shí)正常插入,大于4000時(shí)也正常插入,但在2000-4000時(shí)則失敗,報(bào)錯(cuò)(ORA-01461:僅可以插入LONG列的LONG值賦值)
原因分析:
沒有采用對應(yīng)的Oracle類型。
解決辦法:
采用OracleType.Clob
下邊采用NHibernate插入數(shù)據(jù),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"/>這里的驅(qū)動用的NHibernate.Driver.OracleClientDriver,其實(shí)是對微軟的OracleClient的封裝啦,其實(shí)內(nèi)部還是調(diào)用微軟的OracleClient的東東。引用System.Data.OracleClient.dll即可OracleClient。
做好上邊的配置后,便有了以下的方式
方式4:采用NHibernate
代碼:
stringid=Guid.NewGuid().ToString();
Xxxx=newXx();
xx.Test=data;//data是一個(gè)變量,存儲你要插入的字符串
xx.Id=id;
ISessionsession=SessionFactory.OpenSession();
session.Save(xx);
session.Flush();
情況分析:
當(dāng)data的字節(jié)數(shù)在0-2000之間時(shí)正常插入,大于4000時(shí)也正常插入,但在2000-4000時(shí)則失敗,報(bào)錯(cuò)(ORA-01461:僅可以插入LONG列的LONG值賦值).情況和方式3的情況一樣。
原因分析:
NHibernate在用OracleClient映射StringClob時(shí),設(shè)置參數(shù)類型為OracleType.NVarChar,導(dǎo)致插入有BUG。網(wǎng)上有人推測是OracleClient的BUG所致,理由是換用OracleDataAccess即可解決。
為什么說NHibernate將參數(shù)類型設(shè)置為OracleType.NVarChar呢?看下邊
找到NHibernate的源代碼,把它加入你的工程。記得不要移動NHibernate位置直接加入工程,直接在NHibernate的安裝目錄引用進(jìn)來。
2. 在Test解決方案中添加NHibernate的項(xiàng)目引用。
經(jīng)過上邊兩個(gè)步驟我們就可以跟蹤調(diào)試NHibernate了
跟蹤代碼session.Save(xx);看看它究竟做了啥。
當(dāng)我們跟進(jìn)CommandSetBatchingBatcher時(shí),可以得到以下信息(如圖中的調(diào)試信息)。CurrentBatch類型是OracleClientCommandSet,OracleClientCommandSet看源碼得知是對微軟的OracleCommandSet的封裝,因?yàn)檫@個(gè)類internal sealed class,所以我們的程序里是找不到這個(gè)類的,不過NHibernate通過反射使用了它的功能。OracleCommandSet可能用作批處理的,就是一次處理多個(gè)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);
}
}
跟蹤C(jī)urrentBatch可以看到
CommandText:
declare
type refcursortype is ref cursor;
begin
INSERT INTO z3 (test, id) VALUES (:p2, :p3);
:r1_4 := sql%rowcount;
end;
這里的p2就是我們的Clob類型字段的參數(shù)啦。
再看p2的OracleType是NVarChar,是不是有點(diǎn)明白啦,對了,跟我們3一樣,參數(shù)類型錯(cuò)掉了。
解決辦法:
使用NHibernate的自定義類型,不是太會,幸好網(wǎng)上有高人提供代碼,在此想高人致謝。這樣我們通過自定義類型來設(shè)置正確的OracleType即可。在項(xiàng)目中添加兩個(gè)類。
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類型的字段,存入中文時(shí)參數(shù)的OracleDbType必須設(shè)置為OracleDbType.Clob
//否則會變成亂碼(Oracle10gclient環(huán)境)
OracleParameterparam=cmd.Parameters[index]asOracleParameter;
if(param!=null)
{
param.OracleType=OracleType.Clob;// 關(guān)鍵就這里啦
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所在的程序集。
總結(jié)
以上是生活随笔為你收集整理的Oracle中Clob类型处理解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2.0米办公桌多少钱
- 下一篇: 常见的GC日志笔记