六步实现Spring.NET 与 NHibernate 的整合
最近刚完成一个项目,其中对数据库的架构用到的是Spring.NET 与 NHibernate相结合的产物。对于这两项技术,我自己也不是太熟悉,不过好在网上有很多关于这方面的介绍文档,在这里就不多说了。本文主要介绍的是通过这两者的结合,实现数据库的模型化,程序员从此不用再到数据库里面去建表,而是通过在项目中创建模型,修改配置文件,然后程序在运行的时候会自动的创建表以及在原有表中添加的列,但是这个不能删除原有表中的列。这个和EF数据库机制有相同的地方,就是在程序运行的时候,都可以自动的创建表。不同的地方在于,EF如果是在数据库中已经有表的情况下,如果模型改变了需要通过代码迁移或者把数据库删除了从新建一个同名的空的数据库才能将修改的模型映射到数据库中;Spring.NET 与 NHibernate相结合的话,就没那么多要求了,模型变了,直接运行程序就是了,添加的模型和字段会映射到数据库中,但是删除的模型和字段在数据库中就没有被删除,当然也可以重新创建同名的数据库来更新,但这样的话,原来的数据就没了。这个实验的环境:win10,sqlserver2008R2 版本Microsoft SQL Server Management Studio 10.50.1600.1,vs2013
言归正传,下面介绍如何搭建这样的框架:
第一步,建立这个类库项目DAL,在项目中建立两个文件夹HBM和Model,当然也可以不建,视情况而定。在model文件夹中建立原始模型类,这个模型代表数据库中的一个表;在HBM中建立一个和模型对应的xml文件,两个文件分别是:PeopleVO和PeopleVO.hbm.xml。
namespace DAL.Model { [Serializable] public class PeopleVO { public virtual long Id { set; get; } public virtual string Name { set; get; } public virtual int Age { set; get; } } }
PeopleVO.hbm.xml这个文件用于将PeopleVO与数据库中的表映射起来,主意这里的名称,一定要有.hbm。然后在这个文件的属性中,将生成操作设置为:嵌入的资源。文中需要注意class这一行,分别是文件对应的模型,命名空间,数据库表名。其他的可以看看网上关于NHibernate的介绍。
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="DAL.Model.PeopleVO, DAL" table="T_People"> <id name="Id" column="Id" type="Int64"> <generator class="identity"/> </id> <property name="Name" column="Name" type="String" /> <property name="Age" column="Age" type="Int32" /> <!--<property name="FilePostfix" column="FilePostfix" type="String" /> <property name="FilePath" column="FilePath" type="String" />--> <!--<property name="FileContent" column="FileContent" type="BinaryBlob" > <column name="FileContent" sql-type="Image" /> </property>--> </class> </hibernate-mapping>
第二步,建立类库项目Core,需要引入如下dll:Common.Logging.dll,Iesi.Collections.dll,NHibernate.dll,Spring.Aop.dll,Spring.Core.dll
Spring.Data.dll,Spring.Data.NHibernate30.dll。
创建类FieldSort和DbParameterItem
namespace Core { public class DbParameterItem { public string Name { get; set; } public object Value { get; set; } public SqlDbType DataType { get; set; } public DbParameterItem() { } public DbParameterItem(string name, SqlDbType datatype, object value) { this.Name = name; this.DataType = datatype; this.Value = value; } } public class FieldSort { public string FieldName { get; set; } public string FieldSortType { get; set; } public FieldSort(string fieldname, string fieldsorttype) { FieldName = fieldname; FieldSortType = fieldsorttype; } } }
创建一个接口IBaseDao
namespace Core { public interface IBaseDao<T> { object Insert(T d); void Update(T d); void Delete(T d); int Delete(string queryString); T GetById(object id); T GetByHQL<T>(string queryString); IList<T> GetList(string queryString); IList<T> GetList(string queryString, int count); IList<T> GetList(string queryString, object o); /// <summary> /// hql参数化 /// </summary> /// <param name="queryString"></param> /// <param name="parameters"></param> /// <returns></returns> IList<T> GetList(string queryString, Dictionary<string, string> parameters); IList<T> GetAll(string queryString); /// <summary> /// 根据SQL获取数据,支持参数化查询 /// </summary> /// <param name="sql"></param> /// <param name="items"></param> /// <returns></returns> DataTable GetDataTableBySQL(string sql, params DbParameterItem[] items); /// <summary> /// 分页 /// </summary> /// <param name="strsql">查询语句</param> /// <param name="pageNumber">当前页</param> /// <param name="pageSize">当前页显示数据条数</param> /// <returns></returns> DataTable GetPageDataTableBySQL(string strsql, int pageNumber, int pageSize, List<FieldSort> sortlist, params DbParameterItem[] items); /// <summary> /// 根据SQL返回记录数 /// </summary> /// <param name="sql"></param> /// <returns></returns> int GetTotalRecord(string sql, params DbParameterItem[] items); /// <summary> /// 执行SQL语句,返回影响的记录条数 /// </summary> /// <param name="sql"></param> /// <returns></returns> int ExecuteSQL(string sql); IList<T> GetListByHQL<T>(string queryString); /// <summary> /// 查询并限制返回记录数 /// </summary> /// <param name="queryString"></param> /// <param name="count">返回记录条数</param> /// <returns></returns> IList<T> GetAllLimitByNum(string queryString, int count); /// <summary> /// 执行hql语句 /// </summary> /// <param name="hql"></param> /// <returns></returns> bool ExecuteHql(string hql); } }
创建BaseDao类,实现接口IBaseDao和基类HibernateDaoSupport, 这需要添加using Spring.Data.NHibernate.Generic.Support;它是基类HibernateDaoSupport的命名空间。
public class BaseDao<T> : HibernateDaoSupport, IBaseDao<T> { public object Insert(T d) { return base.HibernateTemplate.Save(d); } public void Update(T d) { base.HibernateTemplate.Update(d); } public void Delete(T d) { base.HibernateTemplate.Delete(d); } public int Delete(string queryString) { return base.HibernateTemplate.Delete(queryString); } public T GetById(object id) { if (id == null) { return default(T); } return base.HibernateTemplate.Get<T>(id); } public T GetByHQL<T>(string queryString) { T result = default(T); IList<T> list = base.HibernateTemplate.Find<T>(queryString); if (list != null && list.Count > 0) { result = list[0]; } return result; } public IList<T> GetList(string queryString) { return base.HibernateTemplate.Find<T>(queryString); } public IList<T> GetList(string queryString, int count) { IQuery query = base.Session.CreateQuery(queryString); query.SetFirstResult(0); query.SetMaxResults(count); return query.List<T>(); } public IList<T> GetList(string queryString, object o) { return base.HibernateTemplate.Find<T>(queryString, o); } public IList<T> GetList(string queryString, Dictionary<string, string> parameters) { IQuery query = base.Session.CreateQuery(queryString); foreach (string item in parameters.Keys) { query.SetString(item, parameters[item]); } return query.List<T>(); } public IList<T> GetAll(string queryString) { return base.HibernateTemplate.LoadAll<T>(); } public DataTable GetDataTableBySQL(string sql, params DbParameterItem[] items) { DataTable result = new DataTable(); IDbCommand command = null; try { command = this.SessionFactory.OpenSession().Connection.CreateCommand(); command.CommandText = sql; HandleDbCommand(command, items);//参数化处理 if (command is SqlCommand) { SqlDataAdapter dataAdapter = new SqlDataAdapter(command as SqlCommand); dataAdapter.Fill(result); } } catch (Exception ex) { throw ex; } finally { if (command != null) { command.Connection.Close(); command.Parameters.Clear(); } } return result; } public System.Data.DataTable GetPageDataTableBySQL(string strsql, int pageNumber, int pageSize, List<FieldSort> sortlist, params DbParameterItem[] items) { string strTempSql = string.Empty; //默认为Id Asc if (sortlist == null) { strTempSql = "SELECT t.* FROM (SELECT a.*, ROW_NUMBER( ) OVER( order by a.id ASC ) AS RN FROM ({0}) AS a) t WHERE t.RN >= {1} and t.RN <= {2}"; } else { StringBuilder sb = new StringBuilder(); foreach (FieldSort item in sortlist) { sb.Append(item.FieldName + " " + item.FieldSortType + ","); } strTempSql = "SELECT t.* FROM (SELECT a.*, ROW_NUMBER( ) OVER( order by " + sb.ToString().TrimEnd(',') + " ) AS RN FROM ({0}) AS a) t WHERE t.RN >= {1} and t.RN <= {2}"; } int startNum = (pageNumber - 1) * pageSize + 1; int endNum = pageNumber * pageSize; DataTable result = new DataTable(); IDbCommand command = null; try { command = this.SessionFactory.OpenSession().Connection.CreateCommand(); command.CommandText = string.Format(strTempSql, strsql, startNum, endNum); HandleDbCommand(command, items);//参数化处理 if (command is SqlCommand) { SqlDataAdapter dataAdapter = new SqlDataAdapter(command as SqlCommand); dataAdapter.Fill(result); } } catch (Exception ex) { throw ex; } finally { if (command != null) { command.Connection.Close(); command.Parameters.Clear(); } } return result; } public int GetTotalRecord(string sql, params DbParameterItem[] items) { string querySql = string.Format("select count(1) as totalNumber from ({0}) t ", sql); return int.Parse(this.GetDataTableBySQL(querySql, items).Rows[0]["totalNumber"].ToString()); } public int ExecuteSQL(string sql) { int afecctCount = 0; try { afecctCount = this.Session.CreateSQLQuery(sql).ExecuteUpdate(); } catch (Exception ex) { throw ex; } return afecctCount; } public IList<T> GetListByHQL<T>(string queryString) { return base.HibernateTemplate.Find<T>(queryString); } public IList<T> GetAllLimitByNum(string queryString, int count) { IQuery query = this.SessionFactory.OpenSession().CreateQuery(queryString); query.SetFirstResult(0); //开始记录 query.SetMaxResults(count); //查询出来的记录数 return query.List<T>(); } public bool ExecuteHql(string hql) { int rows = 0; try { rows = this.Session.CreateQuery(hql).ExecuteUpdate(); } catch (Exception ex) { throw ex; } return rows > 0; } /// <summary> /// 参数化处理 /// </summary> /// <param name="command"></param> /// <param name="items"></param> private void HandleDbCommand(IDbCommand command, params DbParameterItem[] items) { if (items == null || items.Length <= 0) { return; } if (command is SqlCommand) { foreach (DbParameterItem item in items) { if (item != null) { if (!command.CommandText.Contains("@" + item.Name)) continue; SqlParameter para = new SqlParameter(item.Name, item.DataType); para.Value = item.Value; command.Parameters.Add(para); } } } } }
创建类ObjectFactoryHelper和Helper
public sealed class ObjectFactoryHelper { private static ObjectFactoryHelper instance = null; private IApplicationContext ctx;//这里需要添加引用Spring.Context 要引入Spring.Core.dll private static object objLocked = new object(); private ObjectFactoryHelper() { //ctx = new XmlApplicationContext("file://App.Config"); ctx = ContextRegistry.GetContext(); } public static ObjectFactoryHelper GetInstance() { if (instance == null) { lock (objLocked) { if (instance == null) { try { instance = new ObjectFactoryHelper(); } catch (Exception ex) { throw ex; } } } } return instance; } public T CreateObject<T>() { return (T)ctx.GetObject(typeof(T).Name); } public object CreateObject(string name) { return ctx.GetObject(name); } public bool IsContainsObject(string name) { return ctx.ContainsObject(name); } } public class Helper { public static T GetObject<T>() { return ObjectFactoryHelper.GetInstance().CreateObject<T>(); } }
第三步,创建类库项目DTO,建立一个Model文件夹,这里面的类主要是和DAL项目中的模型的类相对应,用来满足项目的业务需要。这些类中的字段实际上都是DAL数据模型中的字段, 不同之处就是,这些类中的字段可能是数据库表中的一部分,也可能是几个表的字段,总之是根据业务要求来建立。在这里为了简单起见,创建一个字段和DAL中模型一样的类People, 主要是为了实现数据库中对应表的增删改查。
namespace DTO.Model { public class People { public long Id { set; get; } public string Name { set; get; } public int Age { set; get; } } }
第四步,建立类库项目Contract,需要引入项目DTO,添加一个文件夹Interface,专门用来存放接口的,这些接口主要是定义对数据库表的操作,在这里创建一个接口IPeopleMgrFacade
namespace Contract.Interface { public interface IPeopleMgrFacade { long AddNewPeople(People newUser); List<People> GetAllByHQL(string where); bool UpdatePeople(People newUser); int DeletePeople(string queryString); bool DeleteUserByID(long ID); } }
第五步,建立类库项目DoMain,先引入前面四步添加的项目。创建两个文件夹Repository和Service。Repository中的类主要负责对数据库的操作,Service中的类实现Contract项目中对应的接口,调用Repository中的类来实现具体的方法。首先在Repository中创建PeopleDao类
namespace DoMain.Repository { public class PeopleDao { public IBaseDao<PeopleVO> DaoManager { get; set; } //新增 public long AddNewPeople(People newUser) { PeopleVO uvo = new PeopleVO(); uvo.Name = newUser.Name; uvo.Age = newUser.Age; object o = DaoManager.Insert(uvo);//返回值是ID if (o != null) { return long.Parse(o.ToString()); } return 0; } //查询 public List<People> GetAllByHQL(string where) { IList<PeopleVO> uservolist = DaoManager.GetList("from PeopleVO where 1=1 " + where); if (uservolist == null) { return null; } List<People> userlist = new List<People>(); foreach (PeopleVO o in uservolist) { People u = new People(); u.Id = o.Id; u.Age = o.Age; u.Name = o.Name; userlist.Add(u); } return userlist; } //修改 public bool UpdatePeople(People newUser) { PeopleVO uvo = GetById(newUser.Id); if (uvo == null) return false; uvo.Name = newUser.Name; uvo.Age = newUser.Age; DaoManager.Update(uvo); return true; } private PeopleVO GetById(long ID) { return (PeopleVO)DaoManager.GetById(id: ID); } //删除 public int DeletePeople(string queryString) { return DaoManager.Delete("from PeopleVO where 1=1 " + queryString); } //删除 public bool DeleteUserByID(long ID) { PeopleVO uvo = GetById(ID); if (uvo == null) return false; DaoManager.Delete(uvo); return true; } } }
在Service中创建类PeopleService,实现接口IPeopleMgrFacade
namespace DoMain.Service { public class PeopleService : IPeopleMgrFacade { public PeopleDao pDao { get; set; } public long AddNewPeople(DTO.Model.People newUser) { return pDao.AddNewPeople(newUser); } public List<DTO.Model.People> GetAllByHQL(string where) { return pDao.GetAllByHQL(where); } public bool UpdatePeople(DTO.Model.People newUser) { return pDao.UpdatePeople(newUser); } public int DeletePeople(string queryString) { return pDao.DeletePeople(queryString); } public bool DeleteUserByID(long ID) { return pDao.DeleteUserByID(ID); } } }
第六步,创建一个主项目,前面五步中创建的项目都要引入,在项目中创建一个文件夹Configration,在这里创建两个xml文件,Core.xml和Domain.xml,这两个文件的属性复
制到输出目录项选择始终复制,生成操作要选择内容
Core.xml:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net" xmlns:db="http://www.springframework.net/database">
<!-- 数据库和Nhibernate的相关配置 -->
<db:provider id="DbProvider" provider="SqlServer-2.0" connectionString="server=.;database=test1;uid=sa;pwd=123456;"/>
<!--SessionFactory对象,其中包括一些比较重要的属性 -->
<object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate30">
<property name="DbProvider" ref="DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>DAL</value>
<!--这里是DAL的命名空间,看PeopleVO的namespace就知道了-->
</list>
</property>
<property name="HibernateProperties">
<dictionary>
<entry key="hibernate.connection.provider" value="NHibernate.Connection.ConnectionProvider"/>
<entry key="dialect" value="NHibernate.Dialect.MsSql2008Dialect"/>
<entry key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
<entry key="use_outer_join" value="true"/>
<entry key="show_sql" value="true"/>
<!--自动建表(反向映射)-->
<entry key="hbm2ddl.auto" value="update"/>
<entry key="adonet.batch_size" value="10"/>
<entry key="command_timeout" value="60"/>
<!--显式启用二级缓存-->
<entry key="cache.use_second_level_cache" value="true"/>
<!--启动查询缓存-->
<entry key="cache.use_query_cache" value="false"/>
<entry key="query.substitutions" value="true 1, false 0, yes 'Y', no 'N"/>
<entry key="hbm2ddl.keywords" value="none"/>
<!--<entry key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>-->
<entry key="proxyfactory.factory_class" value="Spring.Data.NHibernate.Bytecode.ProxyFactoryFactory, Spring.Data.NHibernate30"/>
</dictionary>
</property>
<property name="ExposeTransactionAwareSessionFactory" value="true" />
</object>
<!--将id为NHibernateSessionFactory的对象注入到HibernateTemplate中-->
<!--数据适配模板-->
<object id="HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
<property name="SessionFactory" ref="NHibernateSessionFactory" />
<property name="TemplateFlushMode" value="Auto" />
<property name="CacheQueries" value="true" />
</object>
<!--事务控制管理器-->
<object id="transactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate30">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
</object>
<!--事务拦截器-->
<object id="TransactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data">
<property name="TransactionManager" ref="transactionManager"/>
<property name="TransactionAttributeSource">
<object type="Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data"/>
</property>
</object>
<!--事务代理工厂-->
<object id="BaseTransactionManager" type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject" abstract="true">
<property name="PlatformTransactionManager" ref="transactionManager"/>
<property name="TransactionAttributes">
<name-values>
<add key="Save*" value="PROPAGATION_REQUIRED"/>
<add key="Set*" value="PROPAGATION_REQUIRED"/>
<add key="Finish*" value="PROPAGATION_REQUIRED"/>
<add key="Update*" value="PROPAGATION_REQUIRED"/>
<add key="Delete*" value="PROPAGATION_REQUIRED"/>
<add key="Add*" value="PROPAGATION_REQUIRED"/>
<add key="Get*" value="PROPAGATION_REQUIRED,readOnly"/>
<add key="Find*" value="PROPAGATION_REQUIRED,readOnly"/>
<add key="Load*" value="PROPAGATION_REQUIRED,readOnly"/>
<add key="Search*" value="PROPAGATION_SUPPORTS,readOnly"/>
<add key="*" value="PROPAGATION_REQUIRED"/>
</name-values>
</property>
</object>
</objects>
Domain.xml:
<?xml version="1.0" encoding="utf-8" ?> <objects xmlns="http://www.springframework.net" xmlns:v="http://www.springframework.net/validation"> <!-- People(用户)--> <object id="IPeopleMgrFacade" parent="BaseTransactionManager"> <!--IPeopleMgrFacade是Contract中对应的接口--> <property name ="Target"> <object type="DoMain.Service.PeopleService, DoMain"> <property name="pDao" ref="PDao"/> <!--pDao是PeopleService中定义的一个PeopleDao的实例对象--> </object> </property> </object> <object id="PDao" type="DoMain.Repository.PeopleDao, DoMain" > <property name="DaoManager" ref="IPeopleDaoManager"/> <!--DaoManager是PeopleDao中的一个实例对象--> </object> <object id="IPeopleDaoManager" type="Core.BaseDao<DAL.Model.PeopleVO>, Core"> <property name="HibernateTemplate" ref="HibernateTemplate"/> </object> <!--如果需要添加新的数据库表,参照上面的内容添加配置--> </objects>
在添加模型的时候,除了按照前面几步添加外,还要对Domain中的内容进行修改,具体怎么修改,参照Domain中的内容就是了。在项目中添加一个应用程序配置文件App.config,
这个文件可能有的项目一创建好就有了,我这里创建的WPF应用程序项目,没有这个文件,所以就建了一个。
文件内容如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" /> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" /> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" /> </parsers> <context> <resource uri="config://spring/objects" /> <resource uri="~/Configration/Core.xml" /> <resource uri="~/Configration/Domain.xml" /> </context> <objects xmlns="http://www.springframework.net"></objects> </spring> </configuration>
代码中的<resource uri="~/Configration/Core.xml" />和 <resource uri="~/Configration/Domain.xml" />表示两个文件
最后在项目中添加一个方法,调用IPeopleMgrFacade中的GetAllByHQL函数,其实也不一定要调用这个函数,只要是与数据库有关的任何一个函数都行,这样就可以在数据库中自动创建表了。还有一点要注意的是,在运行程序之前,首先应在数据库中创建 对应的数据库,否则连数据库都没有,怎么创建表
private void Button_Click(object sender, RoutedEventArgs e)
{
IPeopleMgrFacade pService = Helper.GetObject<IPeopleMgrFacade>();
pService.GetAllByHQL("");
}
到此,整个项目就搭建完了,运行之后,在数据库test1中会自动创建表T_People。尤其要注意的就是Domain.xml和Core.xml文件中的配置,里面有些地方需要根据搭建项目的命名空间来修改,否则就会导致运行失败。