最近這幾天一直看MyBatis相關(guān)資料.配置一下開發(fā)環(huán)境,在上一篇MyBatis For .NET學習筆記:開篇大概介紹一下MyBatis框架開源項目版本以及起源. 其實MyBatis的前身是IBatis. 而對應的.NET版本也是從Java版本中移植過來.這點更是體現(xiàn)在官方把MyBatis移植到Google Code上之后體現(xiàn)出來文檔之間差異: Java文檔和編碼實例完整而實用. 而對應MyBatis 的.NEt 版本你會發(fā)現(xiàn)除了兩個提供的User Guid和iBATIS.NET SDK for .NET 2.0文檔 整個My Batis for Google Code中很難再發(fā)現(xiàn)有點價值的資料.
另外一個就是MyBatis版本問題. 作為開源項目MyBatis[.NET]目前官方提供最新版本DataMapper 1.6.2./ DataAccess 1.9.2與以前版本類似DataMapper 1.6.1中部分隱射實體的配置文件的語法發(fā)生改變. 也就是高版本在語法上有些不想下兼容味道.這也就導致各個版本之間實例異常問題和相對應實例各不相同,版本間參考性具有差距. 這讓人情何以堪啊. 這也是我最近看官方文檔摸索時一個很頭疼的問題. 當然到目前為止對MyBatis原理了解尚未深入.可能目前看法并不可取.只是最近摸索MyBatis出現(xiàn)眾多問題而無法找到相關(guān)參考資料解決問題.
既然是一個成熟的ORM框架.可以先快速項目中使用.來從實際項目中從全局角度來大體預覽一下MyBatis框架的特點.會更加速理解MyBatis. 可能這篇文章寫得有點長[只是估計].這個過程也是初學者角度摸索MyBatis的整個過程.難免會有紕漏 還望指正.當然也遇到很多大大小小的問題.本篇將詳細講述如何配置MyBais并構(gòu)建項目采用MyBatis實現(xiàn)數(shù)據(jù)的CRUD操作.更深層原理將在后面章節(jié)講述.先讓我們會如何使用MyBatis.
<1>構(gòu)建MyBatis For .NET環(huán)境
如果你對MyBatis了解還不夠多.可以參考MyBatis For Wiki,另外當然就是官方.在開始使用MyBatis之前需要在My Batis for Google Code上下載四個文件:
其中DataMapper和DataAccess是核心的引用文件.而另外兩個分別對應說明官方提供UserGuid文檔.其實可以從文件可以看出MyBaits框架有兩個核心的組成部分,一個是SQL Maps,另一個是Data Access Objects也就是常說的[DAO].當然官方在USer Guid做了很經(jīng)典解釋[后篇會翻譯出來].這里簡述一下個人使用后理解.相對于ORM框架中Nhibernate中把實體EntityModel與數(shù)據(jù)庫表Table通過XML文件建立映射關(guān)系.而MyBatis的不同是MyBatis用一個簡單的XML文件來實現(xiàn)從實體到SQL statements[SQL]的映射.這就意味什么呢?
首先DataTable數(shù)據(jù)庫表結(jié)構(gòu)和實體徹底的失去關(guān)聯(lián).數(shù)據(jù)庫表結(jié)構(gòu)改動將不再直接體現(xiàn)在EntityModel. 實體Model把這種依賴關(guān)系轉(zhuǎn)移到一個人為可控因素的SQL Statements[sql語句]上來.也就沒有Nhibernate中存在的Scheme概念.這也說明另外一個問題.Myibatis 并不會為程序員在運行期自動生成SQL 執(zhí)行。具體的SQL 需要程序員編寫,然后通過映射配置文件,將SQL所需的參數(shù),以及返回的結(jié)果字段映射到指定Model.相對NHibernate等“一站式”O(jiān)RM機制而言Myibatis 以SQL開發(fā)的工作量和數(shù)據(jù)庫移植性上的靈活性,為系統(tǒng)設計提供了更大的自由空間. 這也是看了諸多ORM后選擇MyBatis一個原因之一. 當然關(guān)于MyBatis與Nhibernate之間特點討論會專門拿出一片文章來闡述.
很明顯DataMapper是MyBatis框架的核心.DataAccess Objects[DAO]將動態(tài)的、可插入的 DAO 組件很容易地換入換出,可以使用 iBATIS Data Access Objects API 幫助隱藏持久性層實現(xiàn)的細節(jié).創(chuàng)建簡單的組件,提供對數(shù)據(jù)的訪問,而無需將實現(xiàn)的詳細說明展示給應用程序的其余部分.
解壓文件發(fā)現(xiàn)6個可用DLL:
根據(jù)官方的文檔說明如下:
IBatisNet.Common.DLL-[Assembly of classes shared by DataAccess and DataMapper]
IBatisNet.DataAccess.Dll-[核心的DataAccess組件DLL]
IBatisNet.DataMapper.DLL-[核心的數(shù)據(jù)隱射DataMap組件DLL]
Castyle.DynamicProxy.DLL-[實現(xiàn)為DataMapper動態(tài)生成代理]
Log4Net.Dll-[Log4日志組件.]
有了這些引用DLL組件如下來看MyBatis For .Net在項目中如何工作使用,.實現(xiàn)數(shù)據(jù)的對象的CRUD操作.
<2>實現(xiàn)MyBatis For .NET框架CRUD
快速了解MyBatis構(gòu)造和原理 來用一個實現(xiàn)項目來實現(xiàn)MyBatis框架數(shù)據(jù)基礎CRUD操作.更加直觀的體現(xiàn)MyBatis構(gòu)建的原理.為了演示理解方便這里做了一個項目架構(gòu)最簡單純凈的三層. 項目結(jié)構(gòu)如下:
簡單介紹External-Bin中需要引用的DLL. 為了區(qū)別在命名詳見下劃線后綴不再贅述.EntityModel需要添加IBatisNet.Common.DLL引用.首先演示前需要一個獨立數(shù)據(jù)庫以及一個獨立數(shù)據(jù)表Prodcut Tables 創(chuàng)建SQL 語句:
1: CREATE DATABASE BaseCardDB
2: GO
4: USE BaseCardDB
5: GO
7: CREATE TABLE Product
8: (
9: ProductID INT PRIMARY KEY IDENTITY(1,1) NOT NULL,
10: Product_Name VARCHAR(50) NOT NULL,
11: Product_Company VARCHAR(100) NOT NULL,
12: SignOn_Data DATETIME NOT NULL DEFAULT(GETDATE()),
13: Update_Data DATETIME
14: )
15: GO
在EntityModel項目中構(gòu)建實體Product操作對象.:
1: namespace EntityModel
2: {
3: public class Product
4: {
5: public int ProductId { get; set; }
6: public string ProductName { get; set; }
7: public string ProductCompany { get; set; }
8: public DateTime SignDate { get; set; }
9: public DateTime UpdateData { get; set; }
10: }
11: }
走到這往往讓我們聯(lián)想到NhiBernate中實現(xiàn)實體對象與數(shù)據(jù)庫表之間映射關(guān)系的XML文件.但在MyBatis中則提出來出來對應操作的SQL語句之間映射.這時實現(xiàn)數(shù)據(jù)映射關(guān)系的核心部分.定義個在CustomerWeb_UI中定義對應ProductMap.XML:格式如下
1: <?xml version="1.0" encoding="utf-8" ?>
2: <sqlMap namespace="EntityModel" xmlns="http://ibatis./mapping"
3: xmlns:xsi="http://www./2001/XMLSchema-instance">
4: <statements>
5: <select id="GetAllProducts" parameterClass="string" resultClass="hashtable">
6: SELECT * FROM Product WHERE Product_Company=#companyname#
7: </select>
8: <insert id="InsertProduct" parameterClass="EntityModel.Product">
9: INSERT dbo.Product ( Product_Name ,Product_Company)
10: VALUES (#ProductName# , #ProductCompany#)
11: </insert>
12: <delete id="DeleteProduct" parameterClass="int" restultClass="int">
13: DELETE FROM dbo.Product WHERE ProductID=#ProductId#
14: </delete>
15: <update id="UpdateProduct" parameterClass="EntityModel.Product" restltClass="int">
16: UPDATE Product SET Product_Name=#ProductName# WHERE ProductID=#ProductId#
17: </update>
18: <select id="SelectAllProduct" resultClass="List">
19: SELECT * FROM Product
20: </select>
21: </statements>
22: </sqlMap>
有必要說說這個定義實體操作與對應操作的SQl Statement語句之間隱射文件的語法規(guī)則.首先<SQlMap>標簽是用來表示映射關(guān)系. NameSpace最好指定對應實體命名空間.<Statements>則對應定義CRUD四種類型的操作SQL語句. 其中Id作為操作間的唯一標識.ParameterClass則指定操作參數(shù)的類型. ResultsClass這個下面會說到.參數(shù)可以是對應實體的屬性.也可以是程序中執(zhí)行SQL語句中傳遞的參數(shù).但是參數(shù)大小寫敏感必須一致. 對應參數(shù)采用#Paramter#格式進行區(qū)分. 如上定義CRUD四個操作外加一個獲得所有數(shù)據(jù)操作.共5個SQL語句.
其實針對MyBatis中需要提供的配置文件共有4種[Important Level]:
Dao.config- 數(shù)據(jù)訪問配置文件 用來指定配置DAO以及指定providers.config文件的位置和數(shù)據(jù)源的信息
Providers.config-由框架使用的文件來查找你選定的數(shù)據(jù)庫提供的定義訪問參數(shù). 這個通用的官方支持數(shù)據(jù)已達到10種.文件定義固定.[提前寫好數(shù)據(jù)庫訪問配置 與Dao.Config進行關(guān)聯(lián).]
SqlMap.config和數(shù)據(jù)映射定義XML文件[實體映射文件ProductMap.xml] -這是一個有關(guān)當前數(shù)據(jù)庫信息及實體映射文件配置的文件。在這個文件里可以指定數(shù)據(jù)庫連接的信息[用戶名、密碼及主機等),還可以指定實體映射文件.類似[ProductMap.xml]
如上三個配置文件為了演示目的 放到CustomerWeb_UI項目根目錄:.
如上配置中其中Privoiders.Config需要作為資源文件嵌入項目:
建立操作的實體以及對應的映射文件后.來看一下Provide.Config其實就是指定項目要支持的數(shù)據(jù)配置參數(shù).這個相對比較固定.是配置文件中變化最小的一個配置文件.在項目中可以通過配置文件操作類動態(tài)切換底層支持的數(shù)據(jù)庫.底層數(shù)據(jù)庫支持已經(jīng)不再成為項目移植的瓶頸.目前支持12中數(shù)據(jù)庫采用的是SQlServer 2005版本配置:
1: <!--SqlServer 2.0 SQl-->
2: <provider
3: name="sqlServer2.0"
4: enabled="true"
5: description="Microsoft SQL Server, provider V2.0.0.0 in framework .NET V2.0"
6: assemblyName="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
7: connectionClass="System.Data.SqlClient.SqlConnection"
8: commandClass="System.Data.SqlClient.SqlCommand"
9: parameterClass="System.Data.SqlClient.SqlParameter"
10: parameterDbTypeClass="System.Data.SqlDbType"
11: parameterDbTypeProperty="SqlDbType"
12: dataAdapterClass="System.Data.SqlClient.SqlDataAdapter"
13: commandBuilderClass=" System.Data.SqlClient.SqlCommandBuilder"
14: usePositionalParameters = "false"
15: useParameterPrefixInSql = "true"
16: useParameterPrefixInParameter = "true"
17: parameterPrefix="@"
18: allowMARS="false"
19: />
在這個配置參數(shù)需要說明的是Enable屬性,默認情況是設置為False的.如果要啟用某個數(shù)據(jù)庫驅(qū)動就要將它的值設為true,還有一個就是parameterPrefix屬性,表示參數(shù)化SQL語句中參數(shù)的前綴,在SQlServer中參數(shù)前綴就是@標識. 細節(jié)容易導致編程出線一些很奇怪的 異常.大多數(shù)情況下需要檢查配置文件. 在來說所這個SQLMap.則是指定當前數(shù)據(jù)庫連接信息和當前實體映射文件.具體配置如下:
1: <?xml version="1.0" encoding="utf-8"?>
2: <sqlMapConfig xmlns="http://ibatis./dataMapper"
3: xmlns:xsi="http://www./2001/XMLSchema-instance">
4:
5: <!--BAsic Setting About Configuration-->
6: <settings>
7: <setting useStatementNamespaces="false"/>
8: <setting cacheModelsEnabled="true"/>
9: </settings>
10:
11: <providers resource="providers.config"/>
12: <!--DataBase Connection Configuration-->
13: <database>
14: <provider name="sqlServer2.0" />
15: <dataSource connectionString="data source=(local);database=BaseCardDB;user id=sa;password=chenkai;" />
16: </database>
17:
18: <sqlMaps>
19: <sqlMap resource="Maps/CustomerMap.xml"/>
20: <sqlMap resource="Maps/ProductMap.xml"/>
21: </sqlMaps>
22:
23: </sqlMapConfig>
對應標識標簽SQlMapConfig下. Settings對應全局設置. useStatementNamespaces屬性則是指定是否使用命名空間方式來識別.對于初學者而言.為了保證快速上手.建議先把這些細節(jié)概念放置一邊.建議設置成FAlse.不啟用. Provider節(jié)點則是指定Provide.Config的文件路勁. DataBase節(jié)點則會使找到Provide.Config中指定數(shù)據(jù)配置.目前項目中采用的是Sql2005即SQlServer2.0. SqlMaps節(jié)點這時指定了映射文件的路徑. 到了這兒忘了說一下CustomerWeb_UI項目需要添加的引用:
在MyBatis同樣支持Log4Net組件方式來查看執(zhí)行整個過程.當然測試框架也對Nunit很好的支持.可以在執(zhí)行時啟用Log4日志記錄.找到項目的Web.Config添加如下Log4支持配置:
<log4net> <!-- Define some output appenders --> <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="log.txt"/> <param name="AppendToFile" value="true"/> <param name="MaxSizeRollBackups" value="2"/> <param name="MaximumFileSize" value="100KB"/> <param name="RollingStyle" value="Size"/> <param name="StaticLogFileName" value="true"/> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="[Header]\r\n"/> <param name="Footer" value="[Footer]\r\n"/> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n"/> </layout> </appender> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"/> </layout> </appender> <!-- Set root logger level to ERROR and its appenders --> <root> <level value="DEBUG"/> <appender-ref ref="RollingLogFileAppender"/> <appender-ref ref="ConsoleAppender"/> </root> <!-- Print only messages of level DEBUG or above in the packages --> <logger name="IBatisNet.DataMapper.Configuration.Cache.CacheModel"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataMapper.Configuration.Statements.PreparedStatementFactory"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataMapper.LazyLoadList"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataAccess.DaoSession"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataMapper.SqlMapSession"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.Common.Transaction.TransactionScope"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataAccess.Configuration.DaoProxy"> <level value="DEBUG"/> </logger> </log4net>
支持Log4之后我們基本完成對一個Product實體操作對象全部配置.如下就來編寫數(shù)據(jù)訪問類.添加DataAccess.DLL引用:
1: //添加引用
2: using IBatisNet.Common;
3: using IBatisNet.DataMapper;
4: using IBatisNet.DataMapper.Configuration;
5: using IBatisNet.Common.Utilities;
6: using IBatisNet.DataAccess;
7: using IBatisNet.DataAccess.Configuration;
8: using IBatisNet.DataAccess.Interfaces;
9: using IBatisNet.Common.Logging;
數(shù)據(jù)訪問對象Product對象底層數(shù)據(jù)庫操作類ProductService.采用SqlMapper對象來進行數(shù)據(jù)封裝操作.其實了解過項目源碼的人應該知道.MyBatis曾使用NPetShop中病開放相關(guān)的源碼.在源碼你會看到一個另外一層對SQlmapper應用動態(tài)封裝. 然后與數(shù)據(jù)訪問層進行隔離開來.其實作者是對Sqlmapper進行一定重寫. 這里暫時用最為簡單SQlMapper對象進行數(shù)據(jù)操作.類似我們要插入一條Product記錄:
1: public void InsertProduct(Product getproduct)
2: {
3: ISqlMapper _getsqlManager = null;
4: DomSqlMapBuilder getdombuilder = new DomSqlMapBuilder();
5:
6: if (getdombuilder != null)
7: ProductService._getsqlmapper = getdombuilder.Configure() as SqlMapper;
8: _getsqlManager = Mapper.Instance();
9:
10: if (_getsqlManager!=null)
11: _getsqlManager.Insert("InsertProduct", getproduct);
12: }
執(zhí)行插入操作:
1: //插入一條數(shù)據(jù)
2: ProductService_BL getproductBl = new ProductService_BL();
3: getproductBl.InsertProduct(new Product()
4: {
5: ProductName = "Deskshop-54",
6: SignDate=DateTime.Now,
7: ProductCompany = "Auto-Desk"
8: });
插入結(jié)果:
ok.來完整分析一下這條數(shù)據(jù)插入流程.通過Instace()方法初始化SqlMapper對象 在執(zhí)行插入數(shù)據(jù)時.在來回來同看看.ProductMap.xml映射文件中關(guān)于InsertProduct配置:
1: <!--Insert Product-->
2: <insert id="InsertProduct" parameterClass="EntityModel.Product">
3: INSERT dbo.Product ( Product_Name ,Product_Company)
4: VALUES (#ProductName# , #ProductCompany#)
5: </insert>
執(zhí)行InsertOperator方法時sqlMapper對象通過查找配置中的InsertProduct下SQL語句進行執(zhí)行插入操作. 當然配置則指定參數(shù)的類型是EntityModel.Product.這里要說一下.如果單一寫一個Product類名.可能在執(zhí)行是爆出異常提示不識別該參數(shù)類型.所以對于分層架構(gòu)來說最好帶上命名空間.
當然在初步調(diào)試嘗試建立SQlMaper初始化時會碰見如下無法找到DAo.Config文件異常:
Dao.config用來建立數(shù)據(jù)庫連接信息.另外一個就是用來識別SQlMap.Config文件.Dao.Config設置路勁對外可執(zhí)行程序是可見的.設置Dao.Config文件作為編譯內(nèi)容:
如下來更新一下這條記錄.更新操作中當然可以使用SQlMapper實例的Update方法進行操作.:
1: //更新操作
2: public string UpdateProductById(Product getproduct)
3: {
4: string getresult = string.Empty;
5: if (_getsqlManager == null)
6: _getsqlManager = Mapper.Instance();
7: else
8: {
9: _getsqlManager.BeginTransaction();
10: try
11: {
12: getresult = _getsqlManager.Update("UpdateProduct", getproduct).ToString();
13: _getsqlManager.CommitTransaction();
14: }
15: catch
16: {
17: _getsqlManager.RollBackTransaction();
18: throw;
19: }
20: }
21: return getresult;
22: }
參數(shù)依然指定一個Product對象.但在數(shù)據(jù)更新過程中加入MyBatis的事務處理.當更新數(shù)據(jù)時發(fā)生異常則回滾當前操作保持原來數(shù)據(jù).另外一個增加返回值.string類型.執(zhí)行代碼:
1: ProductService_BL getproductBl = new ProductService_BL();
2: string getresult = getproductBl.UpdateProduct(new Product
3: {
4: ProductId = 1,
5: ProductName = "Widows phone 7 Device",
6: ProductCompany = "Tommy Frank and MS Team"
7: });
執(zhí)行結(jié)果:
對應的配置的ProductMap.XML隱射配置附帶返回值Update:
1: <update id="UpdateProduct" parameterClass="EntityModel.Product" restltClass="int">
2: UPDATE Product SET Product_Name=#ProductName# WHERE ProductID=#ProductId#
3: </update>
當然在初步調(diào)試時也出現(xiàn)各種各樣的異常.當你調(diào)試會發(fā)現(xiàn)的大多出現(xiàn)異常源自于配置文件中出現(xiàn)錯誤,類似我們在實例化SqlMapper對象時提示:
SQlMap.Config的重要職能之一就是指定當前項目使用數(shù)據(jù)庫連接具體配置信息. 另外一個就是Dao.Config使其配置可見.這很重要.這里需要在SQlMap.config中添加一個<DataBase>節(jié)點用來配置數(shù)據(jù)庫連接.當然異常信息在MyBatis中初步調(diào)試時很常見.
以上只是說明如何讓MyBatis在一個簡單純凈的三層架構(gòu)如何去進行基礎數(shù)據(jù)的CRUd操作. 讓它快速工作起來.你會發(fā)現(xiàn).從定義一個操作實體到編寫隱射文件.指定數(shù)據(jù)庫連接 操作.都異常簡單.最終所有數(shù)據(jù)操作都交給對應數(shù)據(jù)操作的SQL Statement[sql語句來完成].在一定程度上增加靈活性.MyBatis中提出的新概念比較少.相對NhiberNate學習曲線較小.容易上手使用.
這篇文章整整采用一周業(yè)余時間來驗證. 如下為驗證的源碼項目: