ADO.NET与ORM的比较(5):MyBatisNet实现CRUD

说明:这是一个系列文章,在前面的四篇当中周公分别讲述了利用ADO.NET、NHibernate、Linq to SQL及EntityFramework来实现CRUD功能(C:Create/R:Read/U:Update/D:Delete),在这里再讲述另一种框架,那就是MyBatisNet。MyBatisNet源自于iBatisNet,而iBatisNet又是受了Java平台上的iBatis的影响。
iBatis及iBatis.NET都是利用XML描述来执行存储过程或者SQL语句。与其它ORM框架相比,简单易学是iBatis及iBatis.NET的最大特点,简单易学并不意味着它们功能简单,实际上它们能提供强大的功能。不过iBatis及iBatis.NET现在已经分别更名为MyBatis和MyBatis.NET,它们原来的官方网站http://ibatis.apache.org/上已经有声明新的官方网站网址:http://www.mybatis.org(迄今为止,它们提供的手册里仍是称呼iBatis及iBatis.NET,不过这个不影响使用,在本篇中一律以MyBatisNet来称呼)。
在这里需要说明的是MyBatis并不是一个ORM框架,像NHibernate之类的ORM框架会为你生成全部的或者绝大部分的SQL语句,但是MyBatis没有提供这种功能。MyBatis利用你编写的存储过程或者SQL语句来建立对象与数据库之间的关系映射。相比较而言NHibernate自动生成SQL语句(也可以利用HQL语言),学习难度比较大,而MyBatisNet学习起来比较容易,并且因为自己编写SQL语句,所以比较适合数据量大对性能要求高的场合。MyBatis的工作原理图如下:

 

一、准备
要想在项目中使用MyBatisNet,就需要到它的官方网站http://www.mybatis.org下载相应的dll,根据官方网站的链接可以下载到IBatis.DataAccess.1.9.2.bin.zip和IBatis.DataMapper.1.6.2.bin.zip两个压缩文件,在这个压缩文件中包含了几乎我们需要的所有dll文件(如果使用MySQL等其它数据库,可能需要到数据库厂商网站下载相应的dll驱动),包含如下文件:
Castle.DynamicProxy.dll
IBatisNet.Common.dll
IBatisNet.Common.Logging.Log4Net.dll
IBatisNet.DataAccess.dll
IBatisNet.DataMapper.dll
log4net.dll
同时MyBatis还提供了一些辅助文件,如IBatisNet.Common.Logging.Log4Net.xml、IBatisNet.Common.xml、IBatisNet.DataAccess.xml、log4net.xml及IBatisNet.DataMapper.xml,将这些文件拷贝到VS的相应目录就可以在编写代码时获得程序的API说明,这个位置就是你的.NET Framework的安装目录,比如系统盘是C盘,这个位置就是C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/zh-CN。除此之外,还有一些xsd文件,如provider.xsd、SqlMap.xsd及SqlMapConfig.xsd,这些文件都是对应的xml文件的验证文件,利用这些文件就可以在VS中编辑这些文件时获得智能感知,从而减少出错的机会。假设你的系统是安装在C盘,如果你使用的是VS2005,那么就把这些文件拷贝到C:/Program Files/Microsoft Visual Studio 8/Xml/Schemas;如果你使用的是VS2008,那么就拷贝到C:/Program Files/Microsoft Visual Studio 9.0/Xml/Schemas;如果你使用的是VS2010,那么就拷贝到C:/Program Files/Microsoft Visual Studio 10.0/Xml/Schemas。

除了上面的准备工作之外,我们还需要几个配置文件,分别如下:
Providers.config文件
这个文件可以从下载的MyBatis压缩包中找到,它包含了常用数据库驱动程序清单,里面一个典型的节点如下:

这段XML代码想必大家也能猜得到大部分的属性的意思,在这里周公只讲两个需要注意的地方,一个是enabled属性,如果要启用某个数据库驱动就要将它的值设为true,还有一个就是parameterPrefix属性,表示参数化SQL语句中参数的前缀。

 

SqlMap.config文件
这是一个有关当前数据库信息及实体映射文件配置的文件。在这个文件里我们可以指定数据库连接的信息(账号、密码及主机等),还可以指定实体映射文件。
关于数据库连接的信息可以采用如下方式的配置:
首先在节点配置有关数据库连接的信息,在本实例中周公的配置如下:

上面的大部分属性的意思可以猜测得出来,唯一周公觉得需要说明的是selectKey属性,这是解决插入记录之后获取自增字段主键的值的,在不同的数据库中这个SQL语句可能会不同。
接着在节点中使用这些属性,在周公的运行环境中节点值如下:

当然,你也可以采用那种被注释的方式,也就是直接将连接字符串写好,而不是采用未注释的方式,不过个人感觉未注释的方式稍微容易维护一些,一旦数据库连接信息发生变动,集中修改节点中的值就可以了。
最后需要解决的是实体映射文件的问题。和NHibernate一样,MyBatis也是通过XML文件来解决数据记录与实体之间的映射关系的,关于这些映射文件如何编写周公稍后再说。这里要说的是在SqlMap.config文件中可以有两种方式引入外部的文件,一种是通过资源的方式,在文件中表现为resource,如,另外一种嵌入式资源的方式,在文件中表现为embedded,如,这就需要将该文件设置为嵌入式资源,如下图所示:
 
在本项目中有一个实体类,它就是MyBatisNetDemo程序集中的UserInfo类,它对应的XML映射文件是项目中的MapFiles文件下的UserInfo.xml。
在SqlMap.config文件中这部分的配置如下:

App.config文件
为了调试时能得到运行过程中的相关信息,需要配置Log4Net,关于Log4Net的用法周公博客上有详尽的说明,这里就不在赘述了。在本项目中App.config文件的内容如下:

做了上面的配置之外,还需要添加相关的dll引用,在本项目中所使用到的dll引用如下图所示:
 
至此,我们已经做好了所有的准备工作,可以进行下一步的编码工作了。

 

二、编码
编写实体类代码
在本项目中采用的数据表结构与本系列的第一篇一样(便于比较),如下:

CREATE TABLE [dbo].[UserInfo]( [UserID] [int] IDENTITY(1,1) NOT NULL, [UserName] [varchar](20) COLLATE Chinese_PRC_CI_AS NOT NULL, [RealName] [nvarchar](8) COLLATE Chinese_PRC_CI_AS NOT NULL, [Age] [tinyint] NOT NULL, [Sex] [bit] NOT NULL, [Mobile] [char](11) COLLATE Chinese_PRC_CI_AS NULL, [Phone] [char](11) COLLATE Chinese_PRC_CI_AS NULL, [Email] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL, CONSTRAINT [PK_UserInfo] PRIMARY KEY CLUSTERED ( [UserID] ASC )WITH (IGNORE_DUP_KEY = OFF) )

所对应的实体类的代码如下:

using System; using System.Collections.Generic; using System.Text; namespace MyBatisNetDemo { public class UserInfo { ///

/// 用户编号 /// public int UserID { get; set; } /// /// 用户名 /// public string UserName { get; set; } /// /// 真实姓名 /// public string RealName { get; set; } /// /// 年龄 /// public byte Age { get; set; } /// /// 性别 /// public bool Sex { get; set; } /// /// 电子邮件 /// public string Email { get; set; } /// /// 手机号 /// public string Mobile { get; set; } /// /// 电话 /// public string Phone { get; set; } } }

编写映射文件
前面说到了映射文件是数据库记录与实体类之间的桥梁,它指示了数据库字段与实体类属性之间如何建立联系,并且还指示了MyBatis如何去操作数据库。在本项目中的UserInfo.xml文件内容如下:

节点,每个
            where UserID = #value#
      ]]>
   
parameterClass="int"表示传入的参数值为int类型,resultMap="SelectAllResult" extends="SelectAllUser"表示数据库字段与实体类的映射关系如同id为SelectAllResult的节点所指示的那样,extends="SelectAllUser"表示它的SELECT子句前面部分和id为SelectAllUser的节点的id(这些id自然在整个项目中不能重复命名了),后面的参数是根据执行该id对应的SQL语句所需要的参数。
注意:在本项目中周公将providers.config和SqlMap.config文件设置成复制到输出目录,而将UserInfo.xml文件设置为嵌入式资源了。在实际操作中要注意这一点。整个项目的结构如下:


 

三、单元测试代码
为了照顾很多仍在使用NUnit作为单元测试工具的开发人员的习惯,我们的单元测试代码针对NUnit2.5.3,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; using MyBatisNetDemo; namespace NUnitTest { [TestFixture] public class MyBatisNetTest { private MyBatisNetCRUD instance = null; [SetUp] public void Initialize() { instance = new MyBatisNetCRUD(); } [Test] public void Count() { Assert.Greater(instance.Count(), 0); } [Test] public void Create() { UserInfo userInfo = new MyBatisNetDemo.UserInfo { UserName = "www1", Age = 40, Email = "[email protected]", Mobile = "13567891234", Phone = "02786543210", RealName = "test", Sex = false }; Assert.True(instance.Create(userInfo)); } [Test] public void Read() { UserInfo info =instance.Read(2); //Assert.IsNotNull(info); Assert.AreEqual(info.UserID, 2); } [Test] public void GetUserList() { IList userList = instance.GetUserList(); Assert.Greater(userList.Count,0); } [Test] public void GetUserListPaging() { IList userList = instance.GetUserList(10,20); Assert.Greater(userList.Count, 0); } [Test] public void Update() { UserInfo info = instance.Read(1); Assert.True(instance.Update(info)); } [Test] public void Delete() { int maxUserId = instance.GetMaxUserId(); Assert.True(instance.Delete(maxUserId)); } [Test] public void GetMaxUserId() { int result = instance.GetMaxUserId(); Assert.True(result > 0); } } }

上面的代码在NUnit2.5.3中测试通过。
四、总结
作为一种数据库与实体类的映射框架二不是一个ORM框架,和ADO.NET相比MyBatis不用太多考虑ADO.NET的细节(比如是用SqlConnection还是用OleDbConnecion,还有如何打开和关闭数据库连接等),因而比较容易跨数据库;和NHibernate这样的ORM相比它又能让我们更多地操纵如何与数据库交互,性能上更容易控制一些(前提是必须有比较精通书库的开发人员或者DBA),同时它也比NHibernate更容易学习一些。
当然MyBatis也有它的一些不足,比如在UserInfo.xml中我们要编写大量的SQL语句和添加很多