分享

简单实现Hibernate分表的select,insert,delete及所有操作

 CevenCheng 2010-09-01
作者:caocao(网络隐士),http://www.http://www. 
转载请注明来源:http://www./topic/142404 

由于项目需要隐士最近在.Net下面搞NHibernate来实现分表操作,参考了大量资料和JavaEye论坛上火热的讨论(这里谢过各位高人),结合对NHibernate代码的研读,隐士找到了一个简单的实现方式,由于NHibernate和Hibernate同宗同源,隐士觉得这个简单的实现方式同样可以适用于Java环境里的Hibernate,所以下面的代码是C#的。如果管理员觉得不适合发在Java版,请转.Net版。 

先讨论一下论坛上讨论过的已有做法: 
1、用JDBC直接开搞,出处:http://www./topic/133832 
如果直接搞,就没有必要用Hibernate了。隐士要找的是在NHibernate框架下实现分表的所有操作。 
2、对每个表建模,出处:http://www./topic/133832 
如果是几十张表,建几十个模,累死,代码还很不好写,将来的维护也是个大问题。如果要增加一张表,代码可能要改死。 
3、直接构造select来实现读取,出处:http://www./topic/29514 
仅仅实现了select,而且必须把表名写死,没有实现其它操作,也不利于开发和维护。 
4、Hibernate 3.0里面的dynamic models可能可以实现,出处:http://www./topic/13167 
这个没有研究过,加上NHibernate还没有跟上Hibernate 3.0,所以没有该功能,也无法研究。 

再讨论一下隐士的几个思路: 
1、实现MultiTablesEntityPersister 
在hbm.xml的class里可以指定persister来加载自己实现的persister,是不是可以实现MultiTablesEntityPersister来掌控全局呢?经过隐士大量试验表明几乎不可能,Hibernate认准了一个class对应一张table,大量代码在AbstractEntityPersister里写死了,如果要实现分表需求,基本相当于要重写小半个Hibernate。隐士决定另找出路。 
2、实现SessionFactory,Session,Table 
如果可以通过继承实现SessionFactory,Session,Table来实现分表需求,那也不错。经隐士研究源码,发现没有希望,接口都定死了,一些关键部分被seal,private,internal了,类似Java里的final,private,anonymous。除非改NHibernate源码,这是隐士所不希望的,这样改开源的源码实在是不应该。 

几个思路都被否决后,隐士转向拿Configuration开刀,毕竟hbm.xml里的配置是在Configuration里解析的。一阵分析后发现Configuration把解析工作外包给HbmBinder,在HbmBinder里隐士找到了这句: 
Java代码 
  1. tableName = mappings.NamingStrategy.TableName(tableNode.Value);  

哈哈,这句就是万恶之源了,原来可以通过Configuration.SetNamingStrategy(INamingStrategy namingStrategy)来注入我们自己的命名规范。隐士想到此处眼前豁然开朗,只要在Configuration.BuildSessionFactory前注入NamingStrategy,搞出来的SessionFactory就对分表这件事根本不知道,而且对于特定的class只认特定的table。不过也带来一个副作用,就是有多少个分表,就要准备多少个SessionFacotry,再想想未必是副作用,SessionFactory维持的缓存就不会跨表打架,可以说这个想法是解决得很不错的。 

接着隐士动手开始试验,以下代码基于NHibernate-1.2.0.GA,MySQL 5.0,不过对于Java的Hibernate几乎可以原封不动拿来用,这步留待看官们自己做了。 

隐士随便写了个系统负载表,里面放几个字段。这表也有实际意义,比如有一台机器用来集中监控几十台机器,监控数据都放在一张表里会慢死的,一台机器一张表,干净。 
Java代码 
  1. CREATE TABLE IF NOT EXISTS `system_1_loads` (  
  2.   `loggingDate` datetime NOT NULL default '2006-01-01 00:00:00',  
  3.   `cpuUsage` float NOT NULL default '0',  
  4.   `memoryUsage` float NOT NULL default '0',  
  5.   `bytesReceivedPerSecond` int(32default '0',  
  6.   `bytesSentPerSecond` int(32default '0',  
  7.   PRIMARY KEY  (`loggingDate`)  
  8. ) ENGINE=MyISAM DEFAULT CHARSET=utf8;  
  9.   
  10. ... system_2_loads ... system_3_loads ... ... system_10_loads  


hibernate.cfg.xml照常规配置,一点都不需要改动,这里隐士不贴了。 

SystemLoadDO.cs照常规写。 
Java代码 
  1. namespace DBPartitionTest  
  2. {  
  3.     public class SystemLoadDO  
  4.     {  
  5.         #region Member Variables  
  6.         protected DateTime _loggingDate;  
  7.         protected float _cpuUsage;  
  8.         protected float _memoryUsage;  
  9.         protected int _bytesReceivedPerSecond;  
  10.         protected int _bytesSentPerSecond;  
  11.         #endregion  
  12.         ...  
  13.     }  
  14. }  


SystemLoadDO.hbm.xml照常规写,注意class节点里的table,隐士写了system_{0}_loads,看官说了,这个不能用呀,不要紧,反正后面会被NamingStrategy给替换掉。 
Java代码 
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="DBPartitionTest" assembly="DBPartitionTest">  
  3.   <class name="SystemLoadDO" table="system_{0}_loads">  
  4.     <id name="LoggingDate" type="DateTime" unsaved-value="2006-01-01 00:00:00">  
  5.       <column name="loggingDate" sql-type="datetime" not-null="true" unique="true" index="PRIMARY"/>  
  6.       <generator class="assigned" />  
  7.     </id>  
  8.     <property name="CpuUsage" type="float">  
  9.       <column name="cpuUsage" sql-type="float" not-null="true" />  
  10.     </property>  
  11.     <property name="MemoryUsage" type="float">  
  12.       <column name="memoryUsage" sql-type="float" not-null="true" />  
  13.     </property>  
  14.     <property name="BytesReceivedPerSecond" type="Int32">  
  15.       <column name="bytesReceivedPerSecond" sql-type="int" not-null="false" />  
  16.     </property>  
  17.     <property name="BytesSentPerSecond" type="Int32">  
  18.       <column name="bytesSentPerSecond" sql-type="int" not-null="false" />  
  19.     </property>  
  20.   </class>  
  21. </hibernate-mapping>  


PartitionNamingStrategy.cs这个抽象类实现了接口INamingStrategy,它的构造函数接受一个数字索引,通过改写方法TableName来实现替换表名的工作,其他还是留给DefaultNamingStrategy。 
Java代码 
  1. namespace DBPartitionTest  
  2. {  
  3.     public abstract class PartitionNamingStrategy : INamingStrategy  
  4.     {  
  5.         private int index;  
  6.         public int Index  
  7.         {  
  8.             get { return index; }  
  9.         }  
  10.         private string partitionTableName;  
  11.         public string PartitionTableName  
  12.         {  
  13.             get { return partitionTableName; }  
  14.         }  
  15.   
  16.         public abstract string PartitionTableFormat { get;}  
  17.   
  18.         public PartitionNamingStrategy(int index)  
  19.         {  
  20.             this.index = index;  
  21.             this.partitionTableName = string.Format(PartitionTableFormat, index); // 根据索引构造新的表名  
  22.         }  
  23.   
  24.         #region INamingStrategy  
  25.   
  26.         public string ClassToTableName(string className)  
  27.         {  
  28.             return DefaultNamingStrategy.Instance.ClassToTableName(className);  
  29.         }  
  30.   
  31.         public string PropertyToColumnName(string propertyName)  
  32.         {  
  33.             return DefaultNamingStrategy.Instance.PropertyToColumnName(propertyName);  
  34.         }  
  35.   
  36.         public string TableName(string tableName)  
  37.         {  
  38.             if (PartitionTableFormat.Equals(tableName)) // 这句来实现表名替换  
  39.                 return PartitionTableName;  
  40.             return DefaultNamingStrategy.Instance.TableName(tableName);  
  41.         }  
  42.   
  43.         public string ColumnName(string columnName)  
  44.         {  
  45.             return DefaultNamingStrategy.Instance.ColumnName(columnName);  
  46.         }  
  47.   
  48.         public string PropertyToTableName(string className, string propertyName)  
  49.         {  
  50.             return DefaultNamingStrategy.Instance.PropertyToTableName(className, propertyName);  
  51.         }  
  52.   
  53.         #endregion  
  54.     }  
  55. }  


SystemLoadsNamingStrategy.cs是PartitionNamingStrategy的实现类,只需要实现属性PartitionTableFormat,注意这里必须返回和SystemLoadDO.hbm.xml里一样的串。实际项目里可以不必把这个串写死在代码里,Java这里就太简单了,直接Spring里配一个Bean就搞定了,隐士这里只是验证想法。 
Java代码 
  1. namespace DBPartitionTest  
  2. {  
  3.     public class SystemLoadsNamingStrategy : PartitionNamingStrategy  
  4.     {  
  5.         public SystemLoadsNamingStrategy(int index)  
  6.             : base(index)  
  7.         {  
  8.         }  
  9.   
  10.         public override string PartitionTableFormat  
  11.         {  
  12.             get { return "system_{0}_loads"; }  
  13.         }  
  14.     }  
  15. }  


Program.cs是程序入口,这段代码生成了10个Configuration,10个Configuration创建了10个SessionFactory,每个SessionFactory互不干扰,自己认自己的分表操作,运行结果太长隐士就不附了。这里具体几张表也可以做在配置文件里,这样增加表、减少表可以做到不改代码。 
Java代码 
  1. namespace DBPartitionTest  
  2. {  
  3.     public class Program  
  4.     {  
  5.         public static void Test()  
  6.         {  
  7.             for (int i = 1; i < 11; ++i)  
  8.             {  
  9.                 Configuration configuration = new Configuration().SetNamingStrategy(new SystemLoadsNamingStrategy(i)).Configure();  
  10.                 ISessionFactory sessionFactory = configuration.BuildSessionFactory();  
  11.   
  12.                 ISession session = null;  
  13.                 try  
  14.                 {  
  15.                     session = sessionFactory.OpenSession();  
  16.                     SystemLoadDO systemLoadDO = new SystemLoadDO();  
  17.                     systemLoadDO.LoggingDate = DateTime.Now;  
  18.                     systemLoadDO.CpuUsage = 80;  
  19.                     systemLoadDO.MemoryUsage = 70;  
  20.                     Console.WriteLine(systemLoadDO.LoggingDate.ToString());  
  21.                     session.Save(systemLoadDO);  
  22.                     session.Flush();  
  23.                     ICriteria criteria = session.CreateCriteria(typeof(SystemLoadDO));  
  24.                     criteria.AddOrder(Order.Desc("LoggingDate"));  
  25.                     criteria.SetFirstResult(0);  
  26.                     criteria.SetMaxResults(1);  
  27.                     systemLoadDO = criteria.UniqueResult<SystemLoadDO>();  
  28.                     Console.WriteLine(systemLoadDO.LoggingDate.ToString());  
  29.                     systemLoadDO.BytesReceivedPerSecond = 1024;  
  30.                     session.Flush();  
  31.                     session.Delete(systemLoadDO);  
  32.                     session.Flush();  
  33.                 }  
  34.                 catch (Exception e)  
  35.                 {  
  36.                     Console.WriteLine(e.InnerException);  
  37.                     Console.WriteLine(e.StackTrace);  
  38.                     Console.WriteLine(e.Message);  
  39.                 }  
  40.                 finally  
  41.                 {  
  42.                     if (session != null)  
  43.                         session.Close();  
  44.                 }  
  45.             }  
  46.         }  
  47.   
  48.         static void Main(string[] args)  
  49.         {  
  50.             Test();  
  51.         }  
  52.     }  
  53. }  


总结一下,这个方法的优点是秉承了Hibernate的设计思路,没有修改Hibernate源码,而且是通过Hibernate所允许的方式来进行操作,可以说拿到SessionFactory后所有操作都是和不分表一样的,而且DO实例通过不同的SessionFactory保存、删除就可实现跨表复制、删除。由于SessionFactory不一样,所以缓存维护也没有影响。唯一的缺点就是要维护和分表数量一样的SessionFactory,貌似也就是多写几行代码而已。 
隐士这里说完了,希望大家一起讨论。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多