最近剛完成一個項目,其中對數據庫的架構用到的是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文件中的配置,里面有些地方需要根據搭建項目的命名空間來修改,否則就會導致運行失敗。