代码改变世界

iBATIS In Action:什么是iBATIS(一)

2007-08-19 20:39  Anders Cui  阅读(20102)  评论(49编辑  收藏  举报

在上一章中我们详细讨论了iBATIS的哲学观,以及这个框架的来历。我们也说过,iBATIS是一个混合式的解决方案(hybrid solution),借鉴了多种操作关系数据库的方法的理念。那么iBATIS到底是什么呢?这一章就来回答这个问题。

iBATIS是一种data mapperMartin Fowler在他的《Patterns of Enterprise Application Architecture》一书中是这样描述Data Mapper的:

一个映射层,在对象和数据库间传递数据,并保持两者与映射层本身相独立。.

注:Mapper是在两个独立对象间建立通信关系的一种对象。

Martin很好地区分了数据映射(Data Mapping)和元数据映射(Metadata Mapping),后者正是O/RM工具的依据,这种工具将数据库的表和列映射到应用程序中的类和字段(field),也就是说它将数据库的元数据映射到类的元数据。图2.1显示了类和数据库表的O/R 映射的情形。在这种情况下,类的每个字段映射到了表中的一个相应的列。

译注:在C#中通常在类的属性(Property)与表的列间进行映射。

iBATIS则与之不同,它不是直接在类与数据表或字段与列之间进行关联,而是把SQL语句的参数(parameter)和返回结果(result)映射至类。在本书的剩余部分您将看到,iBATIS是处于类和数据表之间的一个中间层,这使得它在类和数据表之间进行映射时更加灵活,而不需要数据库模型或对象模型(object model)的任何修改。我们所说的中间层实际上就是SQL,它使得iBATIS能够更好地分离数据库和对象模型的设计,这样就相对减少了两者间的耦合。图2.2说明了iBATIS如何使用SQL映射数据。


从图2.2可以看到,iBATIS的映射层正是SQL。您只管写您的SQLiBATIS会为您处理类的属性和数据表的列之间的映射。有鉴于此,同时也为了消除与其它各种映射方式引起的混淆,iBATIS团队通常称这种Data MapperSQL Mapper

2.1 SQL 映射

任何SQL语句都可看作是一组输入和输出。输入的值是参数(parameter),通常出现在WHERE子句中。输出的值则是出现在SELECT子句中的列。图2.3描述了这种观点。


这种方式的优势在于SQL语句给开发人员带来了很大的灵活性。我们可以轻松地操作数据使之与对象模型匹配而无需修改后台的数据库设计。此外,开发人员可以使用内置的数据库函数或存储过程来返回多个不同的表或结果,SQL的强大能力变得信手拈来。

iBATIS使用一个简单的XML描述文件来映射SQL语句的输入和输出。列表2.1显示了一个描述文件的示例。


此处我们可以看到一条SELECT SQL语句,它返回的是地址信息。根据<select>元素我们可以了解它接受一个整型数作为参数,也就是WHERE子句中标记为#id#的部分。我们也可了解结果为Address类型的一个实例,这里假定Address类包含了与SELECT子句中每一列的别名名称相同的属性。例如,别名为id的列将会映射至Address类的id属性。信不信由你,这就是映射一条接受整型参数、返回Address对象的SQL语句所需的全部了。执行这条语句使用的Java代码是:

Address address = (Address) sqlMap.queryForObject("getAddress", new Integer(5));

我们的SQL映射方式可移植性很强,可以应用到任何特性完整的编程语言。比如,使用iBATIS.NETC#代码为:

Address address = (Address) sqlMap.QueryForObject("getAddress", 5);

当然,关于映射还有更多的高级选项,特别是返回结果(result)相关的,我们将会在第二部分(iBATIS基础)进行讨论。现在,理解iBATIS的特性和优点以及其工作原理更为重要。

2.2 工作原理

最根本的一点是,iBATIS可用于替代ADO.NETADO.NET提供的API非常强大,却繁复而冗长。考虑下面的ADO.NET代码:

public Employee GetEmployee(int number)
{
    Employee employee 
= null;

    
string connString = "Server=(local);Database=iBatisInAction;Uid=sa;Pwd=sa;";
    
// Our SQL is buried here.
    string sql = "SELECT * FROM EMPLOYEE WHERE EMPLOYEE_NUMBER = @EmployeeNumber";

    SqlConnection conn 
= null;
    SqlCommand command 
= null;
    SqlDataReader reader 
= null;
    
try
    {
        conn 
= new SqlConnection(connString);
        conn.Open();
        command 
= new SqlCommand(sql, conn);
        command.Parameters.Add("@EmployeeNumber", SqlDbType.Int).Value = number;
        reader 
= command.ExecuteReader(CommandBehavior.CloseConnection);
        
while (reader.Read())
        {
            employee 
= new Employee();
            employee.Id 
= Convert.ToInt32(reader["ID"]);
            employee.EmployeeNumber 
= Convert.ToInt32(reader["EMPLOYEE_NUMBER"]);
            employee.FirstName 
= reader["FIRST_NAME"as string;
            employee.LastName 
= reader["LAST_NAME"as string;
            employee.Title 
= reader["TITLE"as string;
        }
    }
    
finally
    {
        
try
        {
            
if (reader != null)
            {
                reader.Close();
            }
        }
        
finally
        {
            
if (conn != null)
            {
                conn.Close();
            }
        }                
    }
    
return employee;
}

不难看出ADO.NET API的复杂。每一行都是必须的,因而无法精简代码。最好也只能做到将一些公用代码提取到工具方法中,尤其是在释放资源的那部分代码。

译注:微软的SqlHelper类就是这样的工具类。

实际上,iBATIS会以近似于ADO.NET的方式运行。iBATIS会连接到数据库,设置参数,执行语句,获取结果,然后关闭和释放资源。但您需要写的代码则显著减少。代码清单2.3显示了在iBATIS中执行相同语句所需的代码。

<select id="GetEmployee" parameterClass="int" resultClass="Employee">
    SELECT
        ID AS Id,
        EMPLOYEE_NUMBER AS EmployeeNumber,
        FIRST_NAME AS FirstName,
        LAST_NAME AS LastName,
        TITLE AS Title
    From Employee
    Where EMPLOYEE_NUMBER = #EmployeeNumber#
</select>

两者实在是没可比性。iBATIS的代码要简练得多,而且也更容易维护。我们将在本章的后面部分讨论iBATIS的更多好处。不过现在,您也许想知道如何在代码中执行它。就如在您在前面的例子中所见的,它只需一行简单的代码:

Employee emp = sqlMap.QueryForObject<Employee>("GetEmployee", number);

再不需要其它的了。这一行代码会执行SQL语句,设置参数值,返回一个C#对象作为结果。SQL被很好地封装在了XML文件中。iBATIS会管理所有藏在“幕后”的资源,其结果则于清单2.2中的ADO.NET代码相同。

上述方式引出一个问题,iBATIS会以相同的方式工作于所有系统?它是否特别适合于某些特定类型的应用?下面几个小节将回答这个问题,先看看iBATIS是多么适合小型应用程序。

2.2.1 在小型、简单的系统中使用iBATIS

小的应用程序通常只使用单个数据库,用户界面和领域模型(domain model)也较为简单。业务逻辑非常基础,甚至在一些简单的CRUDCreateReadUpdateDelete)应用程序中根本不存在。有三种原因使得iBATIS很适合于这种小型的应用程序。

首先,iBATIS本身就是小巧而简单的。它不需要服务器和任何其它中间件(middleware)。不需要任何额外机制的支持。iBATIS不依赖于其它第三方组件。一份最小的iBATIS安装只需引用一个dll文件和244KB的磁盘空间。除了SQL映射文件,再不需要其它安装,因此只需几分钟时间,您就可以拥有一个可以使用的数据持久层了。

其次,iBATIS不会影响到既有的应用程序或数据库的设计。因此,如果您有一个小型应用程序,已经有了部分实现,甚至已经发布了,都可以使用iBATIS对持久层进行重构。因为iBATIS的简单,它不会使您的程序结构过于复杂,这一点O/RM工具或代码生成器未必能够保证,因为它们总是基于对应用程序或数据库所作的某种假设。

最后,如果您已经经历过一段时间的软件开发,那么应该同意,小的软件系统成长为大的系统几乎是不可避免的。所有成功的软件都有着成长的趋势。值得庆幸的是,iBATIS也适合于大型软件系统,它可以满足企业级应用程序的需要。

2.2.2 在大型的,企业级系统中使用iBATIS

iBATIS是为企业级应用程序而设计的。首先要说的是,iBATIS在此领域相比于其它解决方案拥有诸多优势。从大规模(large-scale)的应用程序到企业级系统,iBATIS的原创者都曾有机会参与过,这些系统通常涉及多个数据库,而他却都无法对其进行管理和控制。在第一章中我们讨论了各种类型的数据库,包括企业数据库,proprietary数据库和遗留数据库。我们在编写iBATIS时很大程度上是为了能够处理这一类的数据库。最终,iBATIS拥有大量特性,使得它很适合于企业级系统。

首先的一点已在前面提到过,但是它如此重要,值得再次声明:iBATIS不对您的数据库或对象模型作任何假设。不管这两种设计间是如何得不匹配,iBATIS总会有效。此外,iBATIS不对您的企业系统的架构作任何假设。不论您的数据库是按业务功能纵向划分,还是从技术上横向划分,iBATIS都能够将它与您的OO应用程序有效地整合起来。

其次,iBATIS可以有效地处理很大规模的数据。iBATIS支持像row handler这样的特性,可以对大量记录进行批处理。它还支持获取某个范围内的数据,这样我们可以仅仅获取当前必须的数据。如果您有10000条记录,但是只需要第500-600条记录,iBATIS可以轻松实现。iBATIS支持driver hint,从而可以高效地完成这些操作。

最后,iBATIS允许将对象以多种方式映射至数据库。企业应用系统的功能以单一模式实现的情况是很少的。很多企业级系统需要在白天进行事务处理,而在夜间进行数据批处理操作。iBATIS允许以多种方式映射,保证了每种业务处理都能以尽可能高效的方式进行。iBATIS还支持多种访问策略。您可以选择都某些数据进行延迟加载,通过SQL来加载那些复杂属性,避免带来严重的性能问题。

看到这里,您也许觉得这很像一个促销广告。那何不说说为什么需要iBATIS呢?我们将在2.5节中做更详细的说明。为公平起见,我们会在稍后的2.4节中讨论一些您不需要使用iBATIS的情况。

2.3 为什么要使用iBATIS

2.3.1 简单

iBATIS是当前公认的最简单的持久层框架之一。简单是iBATIS团队设计目标的核心,其重要性几乎要超过其它任何方面。它的简单是通过它构建的基础来达到的:ADO.NETSQLiBATIS对于.NET程序员来说是简单的,因为它使用起来像ADO.NET,只不过代码少了许多。几乎所有您对ADO.NET的了解都适用于iBATIS,我们可以把iBATIS视作以XML格式描述的ADO.NET代码。前面说过,iBATIS包含很多ADO.NET所不具备的架构上的优点,我们会在后面讨论。iBATIS对于数据库管理员和SQL开发人员来说也是简单的。几乎任何拥有SQL编程经验的开发人员都很容易理解iBATIS的配置文件。

2.3.2 生产力

一个好框架要考虑的第一要旨是使开发人员更具生产力。通常框架会处理一些通用任务,减少重复性(boilerplate)的编码,解决复杂的架构问题。在一项由意大利Java用户组进行的案例研究(http://www.jugsardegna.org/vqwiki/jsp/Wiki?IBatisCaseStudy)中,Fabrizio Gianneschi发现iBATIS可以减少持久层的代码达62%之多。SQL依然需要手工编写,但如您先前所见,SQL不是问题——不管您是使用JDBC还是ADO.NET

译注:这项研究将一个项目由原来的JDBC转换为iBATIS代码,得出了上述结论。但对于ADO.NET是何结果就不得而知了,但相信也能减少大量代码。

2.3.3 性能

性能这个主题能够引发框架作者、用户甚至商用软件商的争论。事实上,在较低层次上来看,任何框架都会存在或多或少的性能损失。一般地,比较一下手工编写的ADO.NETiBATIS,在一个for循环中遍历一百万次,会发现ADO.NET更具优势。幸运的是,在当今应用程序开发中,这并不是关键的性能点。更为重要的是,如何从数据库中获取数据,何时获取它,以及获取的频率。例如,从数据库中获取分页后的列表数据能显著地提升应用程序的性能,因为这样就避免了一次加载过多的数据。类似的,使用像延迟加载(lazy load)这样的特性可以避免在给定的用例下加载不必要的数据。另一方面,如果我们确定需要加载复杂的对象属性,而这些属性来自于多个数据表,那么使用单条SQL来加载数据也可以极大地改善性能。iBATIS提供了多种性能优化策略,我们将在后面讨论。现在,我们要知道,通常要以简单的方式来配置iBATIS,但它的性能会像ADO.NET一样好,甚至更好。另一个需要考虑的地方是,并不是所有的ADO.NET代码都能编写得很好。ADO.NET是一个复杂的API,要想正确使用需要注意很多地方。不幸的是,很多ADO.NET代码编写的不好,导致其性能甚至不如iBATIS

2.3.4 分离关注点

在典型的ADO.NET代码中,有时会看到数据库资源如连接、结果集散布在程序各个层中。在一些糟糕的程序中,我们会看到数据库连接、语句出现在表现层。这实在是恶梦般的经历。在第一章中我们曾讨论过应用程序分层的重要性。我们看到了应用程序如何在较高层次上分层,持久层是如何处于中间层次的。iBATIS提供了此种分层的支持,它会管理所有数据持久相关的资源,如数据库连接,语句和结果集等。它提供了数据库无关的接口和API,帮助应用程序中的其它层能够与任何数据持久相关的资源保持独立。使用iBATIS,我们面对的是真正的对象,而不是任意的结果集。iBATIS使保持良好的分层变成一件容易的事。

2.3.5 分工

有的数据库管理员很是珍爱他们的数据库,以至于不愿意其它人为其编写SQL。还有的人很擅长编写SQL,其他人都希望由他们来写SQL。不管处于何种情况,我们总是应该好好利用团队成员的优势。如果团队里有人特别擅长编写SQL,而不太喜欢编写C#代码,那就让他们尽情地写SQL吧。iBATIS使之成为可能。因为SQL语句和应用程序代码分离得非常清楚,SQL开发人员可以按其固有的方式进行开发,而不用担心什么字符串的拼接。即使有开发人员同时开发C#代码和SQL,如果DBA想优化数据库的性能,只要说“让我看看SQL”。如果使用ADO.NET就没这么简单了,因为SQL往往包含在一连串的字符串拼接中,或者是由遍历和条件动态动态生成。使用O/RM会更糟糕,我们必须运行程序,然后在日志中输出语句,即使找到了,也不能做任何事情。iBATIS使得任何人都可以自由地开发、查看、修改SQL语句。

2.3.6 可移植性:Java.NET以及其它

iBATIS可移植性是很强的。这得益于其相对简单的设计,它可以实现于几乎任何语言和平台上。在编写本书的时候,iBATIS支持三种最流行的开发平台:JavaRubyC#

在当前配置文件还不是完全平台兼容的,但我们已有计划向这个目标靠近。更为重要的是,其概念方式是可移植性很强的。这样我们所有应用程序的设计可以保持一致。对于语言和应用程序的类型来说,iBATIS比任何其它框架支持得都多。如果在您的程序中一致性非常重要,那么iBATIS将是很好的选择。

2.3.7 开源和可信度

前面我们说这一节是“促销广告”。事实上,iBATIS是免费的,开源的软件。无论您使用与否,我们不会从中获得任何收益。您已经购买了这本书,这就是我们所“赚”的钱了。开源软件最大的优势之一是可信。我们没有任何理由扭曲事实或者欺骗您。坦率地说,iBATIS不是所有问题的最佳解决方案。下面我们来点商用软件文档中绝少出现的内容,讨论一下何种情况下不必使用iBATIS,给出一些其它可行的方案。

2.4 何时不用iBATIS

每一种框架都建立在规则和约束之上。较低层次的框架如ADO.NET提供了灵活、完整的特性,却更难使用。较高层次的框架如O/RM工具非常易用,减少了很多工作量,但它们建立更多的假设和约束之上,使得它们不能应用于更多的应用程序。

iBATIS是一个中等层次的框架。它较ADO.NET要高,又比O/RM工具要低。这样iBATIS实际上处于一个独特的位置,它有自己适用的范围。在前面几节中,我们讨论了iBATIS为何可用于各种类型的应用程序,包括小型的,富客户端的,大型的,企业级的以及Web应用程序——还有其它处于中间层次的应用程序。那么何时iBATIS不适合使用呢?下面几个小节中我们会详细描述这样的几种情况,也会推荐一些可选的方案。

2.4.1 如果您拥有完全的控制权直至永远

如果您获得保证,可以完全地控制应用程序和数据库的设计,那您实在是太幸运了。不过这在企业环境或核心竞争力不在软件开发方面的业务中并不多见。但是,如果您在一家软件公司工作,开发受保护的产品,并对其有着完全的控制权,那么可能就是这种情况。

如果您拥有完全的控制权,那么就有足够的理由使用O/RM工具,如NHibernate。您可以充分利用O/RM带来的设计上的好处和开发效率的提高。这样也许和一个企业数据库组或要集成的遗留系统不存在任何冲突。此外,数据库可能和应用程序部署在一起,这样的数据库属于应用程序数据库(见第1章)。使用Hibernate的项目的一个好例子是AtlassianJIRA。他们提供了问题跟踪软件作为产品,他们对其有完全的控制。

一定要考虑应用程序未来会有何变化。如果有可能失去对数据库的控制权,那么就该仔细考虑它对您的持久层策略带来的影响。

2.4.2 如果您的程序中SQL完全是动态生成的

如果您的程序的核心功能是SQL的动态生成,那么iBATIS是错误的选择。iBATIS有着很强大的动态SQL特性,支持高级查询,甚至是一些动态的更新功能。但如果程序中的每条语句都是动态生成的,那么您最好还是使用原生的ADO.NET,或者构建自己的框架。

iBATIS的强大很大程度上体现在允许开发人员自由地手工编写SQL,直接操作SQL。如果大部分SQL都是动态生成的,那么这个优势无疑就丧失掉了。

2.4.3 如果您不是使用关系型数据库

当前已经有可用于非关系型数据库的ADO.NET Provider了,如针对文本文件的,MS Excel的,XML的,还有其它类型的数据存储。尽管已经有人成功地将其应用在iBATIS中,对于大多数用户,我们还是不推荐这样使用。

iBATIS不会对您的环境做出很多假设。但它仍然希望您使用的是一款真正的关系型数据库,同时支持事务和相对标准的SQL及存储过程。即使是一些知名的数据库也会有不支持关系型数据库关键特性的情况。MySQL的早期版本不支持事务,因此iBATIS用起来就不太好。好在现在的MySQL已经支持事务,还提供了不错的ADO.NET Provider

译注:看看这里ADO.NET支持多少类型的数据存储。

如果您不是使用真正的关系型数据库,我们建议您使用原生的ADO.NET甚至是更低层次的文件I/O API

2.4.4 如果iBATIS不能奏效

iBATIS社区的需求在不断增加,所以有很多特性正在开发过程中。但是iBATIS有自己的方向和设计宗旨,这有时可能会发生冲突。人们在开发过程中可能会做一些惊人的事情,在有些情况下,iBATIS不能奏效,因为需求太过复杂。尽管我们可以添加功能来满足这些需求,却会带来极大的复杂性或者超出了iBATIS框架的范围之外。最终,我们决定不去改变框架。为满足这些需求,我们会设法提供可插入(pluggable)的接口,这样您就可以扩展iBATIS来满足几乎任何需要。有时情况很简单,它就是不能使用。这时,最好去选择更好的解决方案,而不是对iBATIS(任何框架都是如此)勉为其难。

关于WhyWhy not就到此为止吧,下一次我们来看一个简单的例子。

译注:这一部分就是不停地为iBATIS美言,像我很喜欢它的,都有点不好意思了。客观地说,iBATIS很不错,适用的程序类型很广泛,但这也导致了它在某些时候不如一些更“专一”的框架好用。据我所知,在Java社区,iBATIS的使用率比起Hibernate来还是有不小的差距。它的SQL是它的灵活和强大之处,维护起来却是个问题。但我感觉如果你要使用ADO.NET,一般情况下iBATIS会给你带来不小的帮助。可以看看Petshop里面的数据持久层,它维护起来要比iBATIS难很多。

下载本文相关代码

第一次想这么严格地翻译一些东西,很多地方比较生硬,大家有何建议,请提一下,帮我改进下 :)