zoukankan      html  css  js  c++  java
  • 转Oracle中Clob类型处理解析(NHibernate long值绑定long列,或者是直接oraclecommand的插入的时候报错都可以用,ORA01461:仅可以插入LONG列的LONG值赋值)

    来源:http://blog.csdn.net/pojianbing/article/details/2789426     非常感谢这位IT人员,困扰了好久的问题啊

    最近利用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 + "' data在sql语句之间,当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类型是OracleClientCommandSet,OracleClientCommandSet看源码得知是对微软的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类型字段的参数啦。

    再看p2的OracleType是NVarChar,是不是有点明白啦,对了, 跟我们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所在的程序集

  • 相关阅读:
    常用知识点集合
    LeetCode 66 Plus One
    LeetCode 88 Merge Sorted Array
    LeetCode 27 Remove Element
    LeetCode 26 Remove Duplicates from Sorted Array
    LeetCode 448 Find All Numbers Disappeared in an Array
    LeetCode 219 Contains Duplicate II
    LeetCode 118 Pascal's Triangle
    LeetCode 119 Pascal's Triangle II
    LeetCode 1 Two Sum
  • 原文地址:https://www.cnblogs.com/smthts/p/2447253.html
Copyright © 2011-2022 走看看