MyBatis For .NET学习笔记[2]:配置环境

最近这几天一直看 MyBatis 相关资料. 配置一下开发环境, 在上一篇MyBatis For .NET 学习笔记: 开篇大概介绍一下 MyBatis 框架开源项目版本以及起源. 其实 MyBatis 的前身是 IBatis. 而对应的.NET 版本也是从 Java 版本中移植过来. 这点更是体现在官方把 MyBatis 移植到 Google Code 上之后体现出来文档之间差异: Java 文档和编码实例完整而实用. 而对应 MyBatis 的.NEt 版本你会发现除了两个提供的 User Guid 和 iBATIS.NET SDK for .NET 2.0 文档 整个My Batis for Google Code中很难再发现有点价值的资料.

另外一个就是 MyBatis 版本问题. 作为开源项目 MyBatis[.NET] 目前官方提供最新版本 DataMapper 1.6.2./ DataAccess 1.9.2 与以前版本类似 DataMapper 1.6.1 中部分隐射实体的配置文件的语法发生改变. 也就是高版本在语法上有些不想下兼容味道. 这也就导致各个版本之间实例异常问题和相对应实例各不相同, 版本间参考性具有差距. 这让人情何以堪啊. 这也是我最近看官方文档摸索时一个很头疼的问题. 当然到目前为止对 MyBatis 原理了解尚未深入. 可能目前看法并不可取. 只是最近摸索 MyBatis 出现众多问题而无法找到相关参考资料解决问题.

既然是一个成熟的 ORM 框架. 可以先快速项目中使用. 来从实际项目中从全局角度来大体预览一下 MyBatis 框架的特点. 会更加速理解 MyBatis. 可能这篇文章写得有点长 [只是估计]. 这个过程也是初学者角度摸索 MyBatis 的整个过程. 难免会有纰漏 还望指正. 当然也遇到很多大大小小的问题. 本篇将详细讲述如何配置 MyBais 并构建项目采用 MyBatis 实现数据的 CRUD 操作. 更深层原理将在后面章节讲述. 先让我们会如何使用 MyBatis.

<1> 构建 MyBatis For .NET 环境

如果你对 MyBatis 了解还不够多. 可以参考MyBatis For Wiki, 另外当然就是官方. 在开始使用 MyBatis 之前需要在My Batis for Google Code上下载四个文件:

2011-03-18_162036

 

 

 

 

 

 

 

其中 DataMapper 和 DataAccess 是核心的引用文件. 而另外两个分别对应说明官方提供 UserGuid 文档. 其实可以从文件可以看出 MyBaits 框架有两个核心的组成部分,一个是 SQL Maps,另一个是 Data Access Objects 也就是常说的 [DAO]. 当然官方在 USer Guid 做了很经典解释[后篇会翻译出来]. 这里简述一下个人使用后理解. 相对于 ORM 框架中 Nhibernate 中把实体 EntityModel 与数据库表 Table 通过 XML 文件建立映射关系. 而 MyBatis 的不同是 MyBatis 用一个简单的 XML 文件来实现从实体到 SQL statements[SQL] 的映射. 这就意味什么呢?

首先 DataTable 数据库表结构和实体彻底的失去关联. 数据库表结构改动将不再直接体现在 EntityModel. 实体 Model 把这种依赖关系转移到一个人为可控因素的 SQL Statements[sql 语句] 上来. 也就没有 Nhibernate 中存在的 Scheme 概念. 这也说明另外一个问题.Myibatis 并不会为程序员在运行期自动生成 SQL 执行。具体的 SQL 需要程序员编写,然后通过映射配置文件,将 SQL 所需的参数,以及返回的结果字段映射到指定 Model. 相对 NHibernate 等“一站式”ORM 机制而言 Myibatis 以 SQL 开发的工作量和数据库移植性上的灵活性,为系统设计提供了更大的自由空间. 这也是看了诸多 ORM 后选择 MyBatis 一个原因之一. 当然关于 MyBatis 与 Nhibernate 之间特点讨论会专门拿出一片文章来阐述.

很明显 DataMapper 是 MyBatis 框架的核心.DataAccess Objects[DAO] 将动态的、可插入的 DAO 组件很容易地换入换出,可以使用 iBATIS Data Access Objects API 帮助隐藏持久性层实现的细节. 创建简单的组件,提供对数据的访问,而无需将实现的详细说明展示给应用程序的其余部分.

解压文件发现 6 个可用 DLL:

2011-03-18_164848

 

 

 

 

 

根据官方的文档说明如下:

IBatisNet.Common.DLL-[Assembly of classes shared by DataAccess and DataMapper]

IBatisNet.DataAccess.Dll-[核心的 DataAccess 组件 DLL]

IBatisNet.DataMapper.DLL-[核心的数据隐射 DataMap 组件 DLL]

Castyle.DynamicProxy.DLL-[实现为 DataMapper 动态生成代理]

Log4Net.Dll-[Log4 日志组件.]

有了这些引用 DLL 组件如下来看 MyBatis For .Net 在项目中如何工作使用,. 实现数据的对象的 CRUD 操作.

<2> 实现 MyBatis For .NET 框架 CRUD

快速了解 MyBatis 构造和原理 来用一个实现项目来实现 MyBatis 框架数据基础 CRUD 操作. 更加直观的体现 MyBatis 构建的原理. 为了演示理解方便这里做了一个项目架构最简单纯净的三层. 项目结构如下:

2011-03-18_173102

 

 

 

 

 

 

 

简单介绍 External-Bin 中需要引用的 DLL. 为了区别在命名详见下划线后缀不再赘述.EntityModel 需要添加IBatisNet.Common.DLL 引用.首先演示前需要一个独立数据库以及一个独立数据表 Prodcut Tables 创建 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 项目中构建实体 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:  }

走到这往往让我们联想到 NhiBernate 中实现实体对象与数据库表之间映射关系的 XML 文件. 但在 MyBatis 中则提出来出来对应操作的 SQL 语句之间映射. 这时实现数据映射关系的核心部分. 定义个在 CustomerWeb_UI 中定义对应 ProductMap.XML: 格式如下

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <sqlMap namespace="EntityModel" xmlns="http://ibatis.apache.org/mapping" 
   3:          xmlns:xsi="http://www.w3.org/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 语句之间隐射文件的语法规则. 首先 <SQlMap> 标签是用来表示映射关系. NameSpace 最好指定对应实体命名空间.<Statements> 则对应定义 CRUD 四种类型的操作 SQL 语句. 其中 Id 作为操作间的唯一标识.ParameterClass 则指定操作参数的类型. ResultsClass 这个下面会说到. 参数可以是对应实体的属性. 也可以是程序中执行 SQL 语句中传递的参数. 但是参数大小写敏感必须一致. 对应参数采用 #Paramter# 格式进行区分. 如上定义 CRUD 四个操作外加一个获得所有数据操作. 共 5 个 SQL 语句.

其实针对 MyBatis 中需要提供的配置文件共有 4 种[Important Level]:

Dao.config- 数据访问配置文件 用来指定配置 DAO 以及指定 providers.config 文件的位置和数据源的信息

Providers.config- 由框架使用的文件来查找你选定的数据库提供的定义访问参数. 这个通用的官方支持数据已达到 10 种. 文件定义固定.[提前写好数据库访问配置 与 Dao.Config 进行关联.]

SqlMap.config和数据映射定义 XML 文件 [实体映射文件 ProductMap.xml] - 这是一个有关当前数据库信息及实体映射文件配置的文件。在这个文件里可以指定数据库连接的信息 [用户名、密码及主机等),还可以指定实体映射文件. 类似 [ProductMap.xml]

如上三个配置文件为了演示目的 放到CustomerWeb_UI项目根目录:.

2011-03-21_094957

 

 

 

 

 

 

 

 

 

 

 

如上配置中其中 Privoiders.Config 需要作为资源文件嵌入项目:

2011-03-21_095101

 

 

 

 

 

 

建立操作的实体以及对应的映射文件后. 来看一下 Provide.Config 其实就是指定项目要支持的数据配置参数. 这个相对比较固定. 是配置文件中变化最小的一个配置文件. 在项目中可以通过配置文件操作类动态切换底层支持的数据库. 底层数据库支持已经不再成为项目移植的瓶颈. 目前支持 12 中数据库采用的是 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:      />

在这个配置参数需要说明的是 Enable 属性, 默认情况是设置为 False 的. 如果要启用某个数据库驱动就要将它的值设为 true,还有一个就是 parameterPrefix 属性,表示参数化 SQL 语句中参数的前缀, 在 SQlServer 中参数前缀就是 @标识. 细节容易导致编程出线一些很奇怪的 异常. 大多数情况下需要检查配置文件. 在来说所这个 SQLMap. 则是指定当前数据库连接信息和当前实体映射文件. 具体配置如下:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper"  
   3:                xmlns:xsi="http://www.w3.org/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 属性则是指定是否使用命名空间方式来识别. 对于初学者而言. 为了保证快速上手. 建议先把这些细节概念放置一边. 建议设置成 FAlse. 不启用. Provider 节点则是指定 Provide.Config 的文件路劲. DataBase 节点则会使找到 Provide.Config 中指定数据配置. 目前项目中采用的是 Sql2005 即 SQlServer2.0. SqlMaps 节点这时指定了映射文件的路径. 到了这儿忘了说一下 CustomerWeb_UI 项目需要添加的引用:

2011-03-21_115619

 

 

 

 

 

 

 

 

在 MyBatis 同样支持 Log4Net 组件方式来查看执行整个过程. 当然测试框架也对 Nunit 很好的支持. 可以在执行时启用 Log4 日志记录. 找到项目的 Web.Config 添加如下 Log4 支持配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<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] &lt;%X{auth}&gt; - %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 实体操作对象全部配置. 如下就来编写数据访问类. 添加 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;

数据访问对象 Product 对象底层数据库操作类 ProductService. 采用 SqlMapper 对象来进行数据封装操作. 其实了解过项目源码的人应该知道.MyBatis 曾使用 NPetShop 中病开放相关的源码. 在源码你会看到一个另外一层对 SQlmapper 应用动态封装. 然后与数据访问层进行隔离开来. 其实作者是对 Sqlmapper 进行一定重写. 这里暂时用最为简单 SQlMapper 对象进行数据操作. 类似我们要插入一条 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:          }

执行插入操作:

   1:          // 插入一条数据
<pre><span class="lnum">   2:  </span>        ProductService_BL getproductBl = <span class="kwrd">new</span> ProductService_BL();</pre>

<pre class="alt"><span class="lnum">   3:  </span>        getproductBl.InsertProduct(<span class="kwrd">new</span> Product()</pre>

<pre><span class="lnum">   4:  </span>            {</pre>

<pre class="alt"><span class="lnum">   5:  </span>                ProductName = <span class="str">"Deskshop-54"</span>,</pre>

<pre><span class="lnum">   6:  </span>                SignDate=DateTime.Now,</pre>

<pre class="alt"><span class="lnum">   7:  </span>                ProductCompany = <span class="str">"Auto-Desk"</span></pre>

<pre><span class="lnum">   8:  </span>            });</pre>

插入结果:

2011-03-21_164852

 

 

 

 

 

 

ok. 来完整分析一下这条数据插入流程. 通过 Instace() 方法初始化 SqlMapper 对象 在执行插入数据时. 在来回来同看看.ProductMap.xml 映射文件中关于 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>

执行 InsertOperator 方法时 sqlMapper 对象通过查找配置中的 InsertProduct 下 SQL 语句进行执行插入操作. 当然配置则指定参数的类型是 EntityModel.Product. 这里要说一下. 如果单一写一个 Product 类名. 可能在执行是爆出异常提示不识别该参数类型. 所以对于分层架构来说最好带上命名空间.

当然在初步调试尝试建立 SQlMaper 初始化时会碰见如下无法找到 DAo.Config 文件异常:

2011-03-16_094951

 

 

 

 

 

 

 

 

 

Dao.config 用来建立数据库连接信息. 另外一个就是用来识别 SQlMap.Config 文件.Dao.Config 设置路劲对外可执行程序是可见的. 设置 Dao.Config 文件作为编译内容:

2011-03-21_172158

 

 

 

 

 

 

 

如下来更新一下这条记录. 更新操作中当然可以使用 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:  }

参数依然指定一个 Product 对象. 但在数据更新过程中加入 MyBatis 的事务处理. 当更新数据时发生异常则回滚当前操作保持原来数据. 另外一个增加返回值.string 类型. 执行代码:

   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:     });

执行结果:

2011-03-21_175218

 

 

 

 

 

 

对应的配置的 ProductMap.XML 隐射配置附带返回值 Update:

   1:  <update id="UpdateProduct" parameterClass="EntityModel.Product" restltClass="int">
   2:     UPDATE Product SET Product_Name=#ProductName# WHERE ProductID=#ProductId#
   3:  </update>

当然在初步调试时也出现各种各样的异常. 当你调试会发现的大多出现异常源自于配置文件中出现错误, 类似我们在实例化 SqlMapper 对象时提示:

2011-03-16_160335

 

 

 

 

 

 

 

 

 

 

 

SQlMap.Config 的重要职能之一就是指定当前项目使用数据库连接具体配置信息. 另外一个就是 Dao.Config 使其配置可见. 这很重要. 这里需要在 SQlMap.config 中添加一个 <DataBase> 节点用来配置数据库连接. 当然异常信息在 MyBatis 中初步调试时很常见.

以上只是说明如何让 MyBatis 在一个简单纯净的三层架构如何去进行基础数据的 CRUd 操作. 让它快速工作起来. 你会发现. 从定义一个操作实体到编写隐射文件. 指定数据库连接 操作. 都异常简单. 最终所有数据操作都交给对应数据操作的 SQL Statement[sql 语句来完成]. 在一定程度上增加灵活性.MyBatis 中提出的新概念比较少. 相对 NhiberNate 学习曲线较小. 容易上手使用.

这篇文章整整采用一周业余时间来验证. 如下为验证的源码项目:

 /Files/chenkai/BatisDemonstarate_DUI.rar