设为首页 收藏本站
查看: 378|回复: 0

[经验分享] Oracle中Clob类型处理解析

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-7-16 09:34:23 | 显示全部楼层 |阅读模式
最近利用NHibernate映射类型为Clob字段在插入数据时发现当字符的字节数(一个半角字符一个字节,一个全角字符两个字节)在2000-4000之间时报错(ORA-01461:仅可以插入LONG列的LONG值赋值)。经过不断查找资料和自己的试验该问题终于得到解决,下边我将自己的心得给大家做一个分享。
准备
系统环境 xp+.net2.0+oracle9i

表结构(由于是测试,表结构随便建了一张) 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的安装目录引用进来。

%E6%97%A0%E6%A0%87%E9%A2%98.jpg

   2. 在Test解决方案中添加NHibernate的项目引用。
%E6%97%A0%E6%A0%87%E9%A2%981.jpg
经过上边两个步骤我们就可以跟踪调试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一样,参数类型错掉了。
%E6%97%A0%E6%A0%87%E9%A2%982.jpg

解决办法:
   使用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所在的程序集。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-22147-1-1.html 上篇帖子: Oracle中Clob类型处理解析:ORA-01461:仅可以插入LONG列的LONG值赋值 下篇帖子: Oracle数据库导入导出 Oracle
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表