Oracle中Clob類型處理解析

   最近利用NHibernate映射類型爲Clob字段在插入數據時發現當字符的字節數(一個半角字符一個字節,一個全角字符兩個字節)在<?xml:namespace prefix = st1 ns = "Tencent" />2000-4000之間時報錯(ORA-01461:僅可以插入LONG列的LONG值賦值)。經過不斷查找資料和自己的試驗該問題終於得到解決,下邊我將自己的心得給大家做一個分享。

準備<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

系統環境 xp+.net2.0+oracle9i

 

表結構(由於是測試,表結構隨便建了一張) XX 

字段名

類型

ID

VARCHAR2(70)

TEST

CLOB

 

測試

 

方式1:直接將CLOB的值拼寫在SQL語句中。

代碼:

  1. string id = Guid.NewGuid().ToString();
  2. OracleCommand cmd = Conn.CreateCommand();
  3. cmd.CommandText = "insert into xx(id,test) values('" + id + "','" + data + "')";// data是一個變量,存儲你要插入的字符串
  4. cmd.ExecuteNonQuery();

情況分析:

     當data的長度大於4000時報錯(ORA-01704:文字字符串過長),小於或等於4000時正常插入。

原因分析:

   之所以會出現長度大於4000時報錯,是因爲Oracle中有SQL語句中兩個單引號之間的字符數不能大於4000的限制。'" + data + "' datasql語句之間,當data的值大於4000個字節時就會報錯。

解決辦法:

   這種方式比較棘手,但有更好的方式,下邊會講到

 

方式2:採用參數形式。

代碼:

  1. string id = Guid.NewGuid().ToString();
  2. OracleCommand cmd = Conn.CreateCommand();
  3. cmd.CommandText = "insert into xx(id,test) values('" + id + "',:p1)";
  4. OracleParameter p1 = new OracleParameter("p1", OracleType.Clob);
  5. p1.Value = data; // data是一個變量,存儲你要插入的字符串
  6. cmd.Parameters.Add(p1);
  7. cmd.ExecuteNonQuery();

情況分析:

    採用這種方式能夠正常插入。所以推薦用這種方式。

原因分析:

  無

解決辦法:

  

 

方式3:採用參數形式,但是參數類型寫爲OracleType. NVarChar

代碼:

  1. string id = Guid.NewGuid().ToString();
  2. OracleCommand cmd = Conn.CreateCommand();
  3. cmd.CommandText = "insert into xx(id,test) values('" + id + "',:p1)";
  4. OracleParameter p1 = new OracleParameter("p1", OracleType. NVarChar);
  5. p1.Value = data; // data是一個變量,存儲你要插入的字符串 
  6. cmd.Parameters.Add(p1);
  7. cmd.ExecuteNonQuery();

情況分析:

    爲什麼要寫這種方式,因爲這種方式和採用NHibernate的方式很相似,先看看在這種方式會產生什麼情況。 data的字節數在0-2000之間時正常插入,大於4000時也正常插入,但在2000-4000時則失敗,報錯(ORA-01461:僅可以插入LONG列的LONG值賦值)

原因分析:

   沒有采用對應的Oracle類型。

解決辦法:

   採用OracleType.Clob

 

 

下邊採用NHibernate插入數據,NHibernate具體怎用不在本次討論範圍。

NHibernate採用的版本爲<?xml:namespace prefix = st2 ns = "urn:schemas-microsoft-com:office:smarttags" />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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. namespace Test.Enties
  5. {
  6.     [Serializable]
  7.     public class Xx
  8.     {
  9.         public Xx()
  10.         {
  11.         }
  12.         private string id;
  13.         public virtual string Id
  14.         {
  15.             get { return id; }
  16.             set { id = value; }
  17.         }
  18.         public virtual string Test
  19.         {
  20.             get { return test; }
  21.             set { test = value; }
  22.         }
  23.         private string test;
  24.     }
  25. }

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

代碼:

  1. string id = Guid.NewGuid().ToString();
  2. Xx xx = new Xx();
  3. xx.Test = data; // data是一個變量,存儲你要插入的字符串
  4. xx.Id = id;
  5. ISession session = SessionFactory.OpenSession();
  6. session.Save(xx);
  7. 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呢?看下邊

 

  1. 找到NHibernate的源代碼,把它加入你的工程。記得不要移動NHibernate位置直接加入工程,直接在NHibernate的安裝目錄引用進來。

 

 

   2. 在Test解決方案中添加NHibernate的項目引用。

經過上邊兩個步驟我們就可以跟蹤調試NHibernate

 

  1. 跟蹤代碼session.Save(xx);看看它究竟做了啥。

當我們跟進CommandSetBatchingBatcher時,可以得到以下信息(如圖中的調試信息)。CurrentBatch類型是OracleClientCommandSetOracleClientCommandSet看源碼得知是對微軟的OracleCommandSet的封裝,因爲這個類internal sealed class,所以我們的程序裏是找不到這個類的,不過NHibernate通過反射使用了它的功能。OracleCommandSet可能用作批處理的,就是一次處理多個SQL語句的,不是太瞭解,誰知道請指教。

 

CommandSetBatchingBatcher的源碼

  1. internal class OracleClientCommandSet : DbCommandSet<OracleConnection, OracleCommand>
  2.     {
  3.         private static System.Type oracleCmdSetType;
  4.         static OracleClientCommandSet()
  5.         {
  6.             Assembly sysDataOracleClient = Assembly.Load("System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
  7.             oracleCmdSetType = sysDataOracleClient.GetType("System.Data.OracleClient.OracleCommandSet");
  8.             Debug.Assert(oracleCmdSetType != null"Could not find OracleCommandSet!");
  9.         }
  10.         protected override object CreateInternalCommandSet()
  11.         {
  12.             return Activator.CreateInstance(oracleCmdSetType, true);
  13.         }
  14. }

跟蹤CurrentBatch可以看到

CommandText:

declare

type refcursortype is ref cursor;

begin

INSERT INTO z3 (test, id) VALUES (:p2, :p3);

:r1_4 := sql%rowcount;

end;

 

這裏的p2就是我們的Clob類型字段的參數啦。

再看p2OracleTypeNVarChar,是不是有點明白啦,對了, 跟我們3一樣,參數類型錯掉了。

 

 

 

解決辦法:

   使用NHibernate的自定義類型,不是太會,幸好網上有高人提供代碼,在此想高人致謝。這樣我們通過自定義類型來設置正確的OracleType即可。在項目中添加兩個類。

   PatchForOracleLobField.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Text;
  5. using NHibernate;
  6. using NHibernate.SqlTypes;
  7. using NHibernate.UserTypes;
  8. namespace Test.type
  9. {
  10.     public abstract class PatchForOracleLobField : IUserType
  11.     {
  12.         public PatchForOracleLobField()
  13.         {
  14.         }
  15.         public bool IsMutable
  16.         {
  17.             get { return true; }
  18.         }
  19.         public System.Type ReturnedType
  20.         {
  21.             get { return typeof(String); }
  22.         }
  23.         public SqlType[] SqlTypes
  24.         {
  25.             get
  26.             {
  27.                 return new SqlType[] { NHibernateUtil.String.SqlType };
  28.             }
  29.         }
  30.         public object DeepCopy(object value)
  31.         {
  32.             return value;
  33.         }
  34.         public new bool Equals(object x, object y)
  35.         {
  36.             return x == y;
  37.         }
  38.         public int GetHashCode(object x)
  39.         {
  40.             return x.GetHashCode();
  41.         }
  42.         public object Assemble(object cached, object owner)
  43.         {
  44.             return DeepCopy(cached);
  45.         }
  46.         public object Disassemble(object value)
  47.         {
  48.             return DeepCopy(value);
  49.         }
  50.         public object NullSafeGet(IDataReader rs, string[] names, object owner)
  51.         {
  52.             return NHibernate.NHibernateUtil.StringClob.NullSafeGet(rs, names[0]);
  53.         }
  54.         public abstract void NullSafeSet(IDbCommand cmd, object value, int index);
  55.         public object Replace(object original, object target, object owner)
  56.         {
  57.             return original;
  58.         }
  59.     }
  60. }

 

OracleClobField.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.OracleClient;
  5. using System.Text;
  6. namespace Test.type
  7. {
  8.     public class OracleClobField : PatchForOracleLobField
  9.     {
  10.         public override void NullSafeSet(IDbCommand cmd, object value, int index)
  11.         {
  12.             if (cmd is OracleCommand)
  13.             {
  14.                 //CLob、NClob類型的字段,存入中文時參數的OracleDbType必須設置爲OracleDbType.Clob
  15.                 //否則會變成亂碼(Oracle 10g client環境)
  16.                 OracleParameter param = cmd.Parameters[index] as OracleParameter;
  17.                 if (param != null)
  18.                 {
  19.                     param.OracleType = OracleType.Clob;// 關鍵就這裏啦
  20.                     param.IsNullable = true;
  21.                 }
  22.             }
  23.             NHibernate.NHibernateUtil.StringClob.NullSafeSet(cmd, value, index);
  24.         }
  25.     }
  26. }

 

然後在映射文件中修改類型即可。

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所在的程序集。

 
發佈了30 篇原創文章 · 獲贊 2 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章