Oracle中Clob型別處理解析(NHibernate long值繫結long列,或者是直接oraclecommand的插入的時候報錯都能用,ORA-01461:僅可以插入LONG列的LONG值賦值
最近利用NHibernate對映型別為Clob欄位在插入資料時發現當字元的位元組數(一個半形字元一個位元組,一個全形字元兩個位元組)在2000-4000之間時報錯(ORA-01461:僅可以插入LONG列的LONG值賦值)。經過不斷查詢資料和自己的試驗該問題終於得到解決,下邊我將自己的心得給大家做一個分享。
準備
系統環境 xp+.net4.0+oracle11g
表結構(由於是測試,表結構隨便建了一張) XX
欄位名
型別
ID VARCHAR2(70) TEST CLOB
測試
方式1:直接將CLOB的值拼寫在SQL語句中。
程式碼:
- string id = Guid.NewGuid().ToString();
- OracleCommand cmd = Conn.CreateCommand();
- cmd.CommandText = "insert into xx(id,test) values('" + id + "','" + data + "')";// data是一個變數,儲存你要插入的字串
- cmd.ExecuteNonQuery();
情況分析:
當data的長度大於4000時報錯(ORA-01704:文字字串過長),小於或等於4000時正常插入。
原因分析:
之所以會出現長度大於4000時報錯,是因為Oracle中有SQL語句中兩個單引號之間的字元數不能大於4000的限制。'" + data + "' data在sql語句之間,當data的值大於4000個位元組時就會報錯。
解決辦法:
這種方式比較棘手,但有更好的方式,下邊會講到 。
方式2:採用引數形式。
程式碼:
- string id = Guid.NewGuid().ToString();
- OracleCommand cmd = Conn.CreateCommand();
- cmd.CommandText = "insert into xx(id,test) values('" + id + "',:p1)";
- OracleParameter p1 = new OracleParameter("p1", OracleType.Clob);
- p1.Value = data; // data是一個變數,儲存你要插入的字串
- cmd.Parameters.Add(p1);
- cmd.ExecuteNonQuery();
情況分析:
採用這種方式能夠正常插入。所以推薦用這種方式。
原因分析:
無
解決辦法:
無
方式3:採用引數形式,但是引數型別寫為OracleType. NVarChar
程式碼:
- string id = Guid.NewGuid().ToString();
- OracleCommand cmd = Conn.CreateCommand();
- cmd.CommandText = "insert into xx(id,test) values('" + id + "',:p1)";
- OracleParameter p1 = new OracleParameter("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
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<nhibernate>
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.OracleClientDriver" />
<add key="hibernate.connection.isolation" value="ReadCommitted"/>
<add key="hibernate.dialect" value="NHibernate.Dialect.Oracle9Dialect" />
<add key="hibernate.connection.connection_string"
value="Data Source=Orcl_192.168.0.232;User ID =icqs_test;Password=icqs_test" />
<add key="show_sql" value="true" />
<add
key="hibernate.adonet.batch_size"
value="100"
/>
</nhibernate>
</configuration>
xx.cs
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace Test.Enties
- {
- [Serializable]
- public class Xx
- {
- public Xx()
- {
- }
- private string id;
- public virtual string Id
- {
- get { return id; }
- set { id = value; }
- }
- public virtual string Test
- {
- get { return test; }
- set { test = value; }
- }
- private string test;
- }
- }
xx.hbm.xml
<?xml version="1.0" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Test.Enties" assembly="Test">
<class name="Xx" table="xx" lazy="true">
<id name="Id" column="id" type="String">
<generator class="assigned"/>
</id>
<property column="test" type="StringClob" name="Test" length="2147483647" />
</class>
</hibernate-mapping>
說明:
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.OracleClientDriver" />這裡的驅動用的NHibernate.Driver.OracleClientDriver,其實是對微軟的OracleClient的封裝啦,其實內部還是呼叫微軟的OracleClient的東東。引用System.Data.OracleClient.dll即可OracleClient。
做好上邊的配置後,便有了以下的方式
方式4:採用NHibernate
程式碼:
- string id = Guid.NewGuid().ToString();
- Xx xx = new Xx();
- xx.Test = data; // data是一個變數,儲存你要插入的字串
- xx.Id = id;
- ISession session = 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的原始碼
- internal class OracleClientCommandSet : DbCommandSet<OracleConnection, OracleCommand>
- {
- private static System.Type oracleCmdSetType;
- static OracleClientCommandSet()
- {
- Assembly sysDataOracleClient = 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, "Could not find OracleCommandSet!");
- }
- protected override object CreateInternalCommandSet()
- {
- return Activator.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
- using System;
- using System.Collections.Generic;
- using System.Data;
- using System.Text;
- using NHibernate;
- using NHibernate.SqlTypes;
- using NHibernate.UserTypes;
- namespace Test.type
- {
- public abstract class PatchForOracleLobField : IUserType
- {
- public PatchForOracleLobField()
- {
- }
- public bool IsMutable
- {
- get { return true; }
- }
- public System.Type ReturnedType
- {
- get { return typeof(String); }
- }
- public SqlType[] SqlTypes
- {
- get
- {
- return new SqlType[] { NHibernateUtil.String.SqlType };
- }
- }
- public object DeepCopy(object value)
- {
- return value;
- }
- public new bool Equals(object x, object y)
- {
- return x == y;
- }
- public int GetHashCode(object x)
- {
- return x.GetHashCode();
- }
- public object Assemble(object cached, object owner)
- {
- return DeepCopy(cached);
- }
- public object Disassemble(object value)
- {
- return DeepCopy(value);
- }
- public object NullSafeGet(IDataReader rs, string[] names, object owner)
- {
- return NHibernate.NHibernateUtil.StringClob.NullSafeGet(rs, names[0]);
- }
- public abstract void NullSafeSet(IDbCommand cmd, object value, int index);
- public object Replace(object original, object target, object owner)
- {
- return original;
- }
- }
- }
OracleClobField.cs
- using System;
- using System.Collections.Generic;
- using System.Data;
- using System.Data.OracleClient;
- using System.Text;
- namespace Test.type
- {
- public class OracleClobField : PatchForOracleLobField
- {
- public override void NullSafeSet(IDbCommand cmd, object value, int index)
- {
- if (cmd is OracleCommand)
- {
- //CLob、NClob型別的欄位,存入中文時引數的OracleDbType必須設定為OracleDbType.Clob
- //否則會變成亂碼(Oracle 10g client環境)
- OracleParameter param = cmd.Parameters[index] as OracleParameter;
- 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
修改前:
<property column="test" type="StringClob" name="Test" length="2147483647" />
修改後:
<property column="test" type="Test.type.OracleClobField, Test " name="Test" length="2147483647" />
Test.type.OracleClobField是類的完整名,Test 即OracleClobField所在的程式集