• Oracle中Clob类型处理解析


      最近利用NHibernate映射类型为Clob字段在插入数据时发现当字符的字节数(一个半角字符一个字节,一个全角字符两个字节)在2000-4000之间时报错(ORA-01461:仅可以插入LONG列的LONG值赋值)。经过不断查找资料和自己的试验该问题终于得到解决,下边我将自己的心得给大家做一个分享。

    准备

    系统环境 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采用的版本为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所在的程序集。

  • 相关阅读:
    Atitit.atiRI  与 远程调用的理论and 设计
    Atitit.提升 升级类库框架后的api代码兼容性设计指南
    Atitit.研发管理软件公司的软资产列表指南
    Atitit.软件开发的三层结构isv金字塔模型
    Atitit.加密算法ati Aes的框架设计
    Atittit.研发公司的组织架构与部门架构总结
    IIS HTTP Error 500.24
    Visual Studio 快捷键
    软件学习遐想
    navigator属性
  • 原文地址:https://www.cnblogs.com/yefengmeander/p/2887908.html
Copyright © 2020-2023  润新知