前阵子项目进入了维护期,相对轻松了一点,所以看了看IBatis的相关东西。最初的想法是学习一种OR/Mapping的框架,后面就找到了IBatis,后来通过阅读其相关的文档才明白IBatis相较之NHibernate这样的大OR/Mapping框架其实是一个轻量级的数据持久层的解决方案。简单的东西总是让人觉得入手比较容易,所以决定从IBatis开始,先学习使用,减少工作中的重复工作量,然后就是借鉴其中代码的编写风格和思路,学习更加深层次的一些东西。
看了一段时间的视频,然后开始看IBatis作者写的书《IBatis In Action》,也跃跃欲试的想开始编写一段自己的基于IBatis的程序了。但是就是这么简单的一个程序,就让我断断续续的用了三天的时间,不过总算是在这个周末下班前程序调试成功了,让人觉得很兴奋,这就把程序和我遇到问题的地方记录下来,希望给基于.NET、IBatis平台进行开发的同仁们一些帮助,当然如果有不准确或者错误的地方也非常希望得到大家的意见。
代码目的:
通过IBatis的QueryForList方法将数据库中的数据返回一个用对象(Object)表示的列表,并依次将结果打印在控制台上。
开发环境:
VS.NET 2005 Professional
C#
SQL Server 2000 sp4
IBatis Version: Source Revision 709676
DataAccess Version 1.9.2.0
DataMapper Version 1.6.2.0
对应的下载地址:http://ibatis.apache.org/dotnetdownloads.cgi
开发步骤:
1、建立一个控制台应用程序(ConsoleApplication1);
2、在项目中增加对IBatisNet.Common、IBatisNet.DataAccess、IBatisNet.DataMapper的引用;
(这里强烈建议引用通过Source Revision 709676自行进行编译以后生成的DLL,这样可以在编译的时候跟进到IBatis代码中进行排错)
3、在自动生成Program.cs代码文件中先建立一个类(Person),具体代码如下:
- public class Person
- {
- private int _personID;
- private string _userName;
- private string _password;
- private string _groupName;
- public int UserID
- {
- get { return (_personID); }
- set { _personID = value; }
- }
- public string UserName
- {
- get { return (_userName); }
- set { _userName = value; }
- }
- public string Password
- {
- get { return (_password); }
- set { _password = value; }
- }
- public string GroupName
- {
- get { return (_groupName); }
- set {_groupName = value;}
- }
- }
这个类就是映射数据库中关系数据的实体类(有些地方叫实体Entity,有些地方叫域Domain)。这里对每个类的字段(Field)都需要有属性(Property,即对应的Get/Set方法)这样IBatis框架才可以正确的将关系数据库中的数据映射到对应的实体类中,否则在运行时IBatis将会报错。
4、在程序的入口函数Main函数中增加如下代码,代码的逻辑非常简单,具体信息参见代码注释:
- static void Main(string[] args)
- {
- //生成一个IBatis.SqlMapper的实例,SqlMapper是IBatis的门面类(Facade)
- //使IBatis有统一的对外接口,以便外部模块进行调用,而不需要在一大堆的
- //类中寻找需要调用的方法。
- ISqlMapper myMapper = Mapper.Instance();
- //通过调用QueryForList方法,通过指定的SQL语句从数据库中获取对应数据的列表信息;
- //并将结果强制转换为ArrayList。
- ArrayList personList = (ArrayList)myMapper.QueryForList("SelectAll", null);
- //依次遍历获取的员工列表,并将对应员工的信息展示出来;
- for (int i = 0; i < personList.Count; i++)
- {
- //这里IBatis已经根据配置文件的配置将关系数据库中的数据填充到了对应的
- //实体类中,我们需要做的就是进行强制转换;
- Person p = (Person)personList[i];
- //打印员工的各个信息;
- Console.WriteLine("USER_ID = " + p.UserID.ToString());
- Console.WriteLine("USER_NAME = " + p.UserName);
- Console.WriteLine("PASSWORD = " + p.Password);
- Console.WriteLine("GROUPNAME = " + p.GroupName);
- Console.WriteLine("---------------------------------------------------------------");
- Console.WriteLine("");
- }
- Console.ReadLine();
- }
5、在Debug文件夹(如果没有需要编译一下控制台程序,在bin文件夹下会生成一个Debug文件夹)下新增三个XML文件,分别命名为SqlMap.config、providers.config、Person.xml。
其中SqlMap.config文件名不能进行修改。目前还不清楚如何设置就可以自定义SqlMap.config的名称,在跟踪IBatis源代码的时候,"SqlMap.config"是作为一个常量存在的,应该是IBatis的一个默认值,所以为了方便起见,我们这里还是使用SqlMap.config。
SqlMap.config:用于配置信息,这些信息将反映到IBatis.SqlMapper类中,比如:数据库的连接字符串,还有就是整合其他的如同Person.xml这样的文件。
SqlMap.config的内容如下所示:
- <?xml version="1.0" encoding="utf-8"?>
- <sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <settings>
- <setting useStatementNamespaces="false"/>
- <setting cacheModelsEnabled="true"/>
- </settings>
- <!-- ==== SqlClient configuration ========= -->
- <!-- Optional ( default ) -->
- <!-- Rem : If used with a Dao it will be ignored -->
- <!--配置数据库类型及连接字符串-->
- <database>
- <provider name="sqlServer1.1"/>
- <dataSource name="iBatisTest" connectionString="server=.;database=TestDB;user id=sa;password=sql2000;"/>
- </database>
- <!--配置一些SQL语句的XML文件-->
- <sqlMaps>
- <sqlMap resource="Person.xml"/>
- </sqlMaps>
- </sqlMapConfig>
Person.xml的内容如下所示:
- <?xml version="1.0" encoding="utf-8" ?>
- <!--这里要注意xmlns,一定要有这个属性,否则将无法通过XPath方法获取到对应的节点-->
- <sqlMap namespace="Person" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://ibatis.apache.org/mapping">
- <!--这里是用于设置一些类的别名,在后续的XML文件中Person就是表示ConsoleApplication1.Person类-->
- <alias>
- <typeAlias alias="Person" assembly="ConsoleApplication1.exe" type="ConsoleApplication1.Person" />
- </alias>
- <!--这里用于设置数据库字段和实体类字段之间的对应关系,其中property是实体类中属性的名称,而column是数据库中列的名称-->
- <resultMaps>
- <resultMap id="SelectAllResult" class="Person">
- <result property="UserID" column="USERID" />
- <result property="UserName" column="USERNAME" />
- <result property="Password" column="PASSWORD" />
- <result property="GroupName" column="GROUPNAME" />
- </resultMap>
- </resultMaps>
- <statements>
- <!--这里用于编写SQL语句,其中resultMap是前面用于进行列-字段映射的ID-->
- <select id="SelectAll" resultMap="SelectAllResult">
- SELECT * FROM USER_ACCOUNT
- </select>
- </statements>
- </sqlMap>
Providers.config主要保存了一些数据库驱动的配置,这个文件在下载的源代码文件中存在,可以直接Copy过来使用,内容就不再这里展示了。
6、最后一步就是建立对应的数据表,数据表结构从前面的Person.xml中的resultMap中设置的映射关系中可以看出,下面提供建立表的SQL代码:
- if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[USER_ACCOUNT]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
- drop table [dbo].[USER_ACCOUNT]
- GO
- CREATE TABLE [dbo].[USER_ACCOUNT] (
- [USERID] [int] IDENTITY (1, 1) NOT NULL ,
- [USERNAME] [varchar] (50) COLLATE Chinese_PRC_CI_AS NOT NULL ,
- [PASSWORD] [varchar] (100) COLLATE Chinese_PRC_CI_AS NOT NULL ,
- [GROUPNAME] [varchar] (100) COLLATE Chinese_PRC_CI_AS NOT NULL
- ) ON [PRIMARY]
- GO
至此一个Demo程序的搭建就完成了,在测试数据表中插入一些测试数据,运行后可以看到数据库中的数据已经按照预想的方式展现出来了,如下图所示:
在完成这个Demo程序的时候,遇到的最大的问题就是XML文件的配置问题,其中很多问题都是来源于XML的一些属性的配置,如很多配置文件中都存在xmlns属性,IBatis中需要通过这个属性,利用XPath查询语言来获取到对应的XML节点,从而进行下一步处理,如果xmlns不是对应的值(从代码看这些xmlns是作为常量写入代码中,还没发现可以配置的地方)那么将导致XPath查询失败,从而使运行报错(二期报错信息十分含糊。。。。),还有一个很奇怪的地方就是在IBatis代码中的:src-revision-709676/src-revision-709676/IBatisNet.Common/Utilities/Objects/ObjectFactory.cs文件,这个文件中存在一段代码:
- /// <summary>
- /// Constructor
- /// </summary>
- /// <param name="allowCodeGeneration"></param>
- public ObjectFactory(bool allowCodeGeneration)
- {
- if (allowCodeGeneration)
- {
- // Detect runtime environment and create the appropriate factory
- if (Environment.Version.Major >= 2)
- {
- #if dotnet2
- _objectFactory = new DelegateObjectFactory();
- #endif
- }
- else
- {
- _objectFactory = new EmitObjectFactory();
- }
- }
- else
- {
- _objectFactory = new ActivatorObjectFactory();
- }
- }
由于我使用的VS2005,对应的是.NET Framework 2.0所以满足第10行的判断调减,但是由于源代码中并没有定义dotnet2,导致对应的初始化代码_objectFactory = new DelegateObjectFactory();不会执行,从而引起后续的NullReferenceException,在代码最前面增加#define dotnet2然后重新编译代码就可以正常运行了,目前还不知道这个地方是作者有意为之还是一个小问题。
恩,今天就先写到这里,在调试的过程中还是学习到了很多相关知识,比如:XPath的语法,xmlns的含义等等。以后在慢慢深入的过程中一定能学习到更多。与大伙儿共勉。