iBATIS、Hibernate和JPA:哪一款最适合你

iBATIS、Hibernate和JPA:哪一款最适合你

http://article.yeeyan.org/view/213582/180283


在本文中我们介绍并比较两种最流行的开源持久框架:iBATISHibernate,我们还会讨论到Java Persistence APIJPA。我们介绍每种解决方案并讨论其所规定的品质,以及在广泛的应用场景中其各自的长处和缺点。然后我们会基于诸如性能、移植性、复杂性以及对数据模型改变的适应性等因素来比较iBATISHibernateJPA

如果你是一个刚起步的Java程序员,新接触持久性概念的话,那么就把阅读此文当作是接受一次这一主题以及大部分流行的开源持久性解决方案的启蒙。如果你对这三种解决方案都很熟悉,并且只想要一个简单的比较的话,那么你会在“比较持久化技术”一节中找到相应的内容。

理解持久性< o<="" span=""><>

持久性(persistence是数据的一个属性,其确保即使是在应用的生命周期之外数据也是可用的。对于像Java这样的面向对象语言来说,持久性确保了即使是在创建对象的应用停止执行之后对象的状态仍是可访问的。

存在多种实现持久性的方法。传统的解决这一问题的方法是使用文件系统来把需要的信息存储在平面文件(flat file)中。这一方法很难用来管理大量的数据,因为数据分布在不同的文件中。使用平面文件系统的话维护数据的一致性也是一个问题,因为相同的信息可能会被重复放在各个文件中。在平面文件中查找数据很耗时,特别是如果这些文件还未排序的话。还有,文件系统对并发访问的支持有限,因为它们不能确保数据的完整性。基于上述种种原因,在寻求持久性时,文件系统并不被视为一个良好的数据存储解决方案。

当前最常见的方法是使用数据库,其充当巨大量数据的存储库。存在许多种类型的数据库:关系型的、层次结构型的、网络型的、面向对象型的等等。这些数据库,以及它们的数据库管理系统(DBMS),不仅提供持久性能力,而且管理其所持久的信息。关系数据库是最被广泛使用的类型,关系数据库中的数据被建模成一组相互关联的表。

企业级应用的出现普及了n层架构,其目的是通过把表现、业务和数据库相关代码分离到应用的不同层级(或是层面)中来提升可维护性。分离了业务逻辑和数据库代码的层面即是持久层,其保持了应用相对于底层的数据库技术的独立性。适当的位置上有了这一强健的层面,开发者就不再需要操心数据的持久性。持久层封装了存储和检索关系型数据库中的数据的方式。

Java应用传统上使用JDBCJava Database ConnectivityAPI来把数据持久到关系数据库中。JDBC API使用SQL语句来完成创建(create)、读取(read)、更新(update)和删除(delete)(CRUD)操作。JDBC代码内嵌在Java类中——换句话说,这类代码与业务逻辑紧密耦合在一起。这类代码还在很大程度上依赖于SQL,而SQL并非是跨数据库的标准;这使得从一种数据库移植到另一种数据库变得困难起来。

关系数据库技术强调的是数据及其之间的关系,而用于Java中的面向对象范式却并非关注数据本身,而是关注执行于数据之上的操作。因此,当这两种技术需要携手合作时,就会存在利益冲突。而且,关系数据库并不能满足继承、多态及关联这些面向对象编程概念。当Java应用中的用户定义的数据类型被映射到关系数据库上时,由这一失配导致的另一个问题就出现了,因为后者并没有提供所需的类型支持。

对象关系映射< o<="" span=""><>

对象关系映射(object-relational mappingORM)已成为了有时被称作对象关系阻抗失配(impedance mismatch)的这一问题的一个解决方案。ORM是一种透明地把应用对象持久到关系数据库中的表的技术。ORM的行为就像是一个虚拟的数据库,对用户隐藏了底层的数据库架构。ORM提供功能来执行完整的CRUD操作并鼓励面向对象的查询。ORM还支持元数据映射以及在应用的事务管理方面提供帮助。

举个例子有助于说明ORM是如何工作的。考虑一个需要持久到数据库中的简单的Car对象,领域模型中的这一Car对象是数据模型中的CAR表的表现形式。Car对象的属性派生自CAR表的各列。在Car类和CAR表之间存在一个直接映射,如图1中的说明。

 

把对象映射到表上

1. 把对象映射到表上< o<="" span=""><>

 

存在许多开源的ORM工具,其中包括HibernateiBATIS SQL Maps以及Java Ultra-Lite Persistence等。这些工具大多数是提供Java应用和数据库之间的抽象层的持久性框架。持久性框架把应用领域中的对象映射成需要持久在数据库中的数据,这些映射可使用XML文件或是元数据注解(后者作为Java1.5的组成部分被引入到语言中)来定义。持久性框架的目的是分离数据库相关代码和应用代码(即业务逻辑),从而提高应用的灵活性。持久性框架通过提供一个持久性逻辑的包装器来简化开发过程。

完成了持久性的基本介绍之后,我们就做好了继续讨论两种最流行的开源持久框架iBATISHibernate的准备。我们还会介绍Java Persistence API,并讨论这三种解决方案在各种应用场景中的优势和弱点。

iBATIS:直接使用SQL< o<="" font=""><>

对象-关系映射(ORM)使用直接映射来生成内部的JDBC或是SQL代码。然而对于一些应用场景来说,你需要对SQL查询做更加直接的控制。在编写涉及了一系列更新查询的应用时,直接编写自己的SQL查询比依赖于ORM生成的SQL来得更有效一些。另外,在对象模型和数据模型之间存在失配时,ORM是不能够使用的。正如我们提到过的那样,JDBC代码一度是这类问题最常见的解决方案,但是它在应用代码内部引入了许多的数据库代码,使得应用更加难以维护。这里需要一个持久层来解耦应用和数据库。

iBATIS Data Mapper框架有助于解决这些问题。iBATIS是一个持久性框架,其提供了SQL的好处却又免去了JDBC的复杂性。与其他大多数的持久性框架不同,iBATIS鼓励直接使用SQL,并确保所有的SQL好处没有被框架本身覆盖掉。

简单是iBATIS最大的优势,因为它提供一个简单的映射和用于构建数据访问代码的API层。在这一框架中,数据模型和对象模型不需要做精确的彼此映射。这是因为iBATIS使用了一个数据映射器(data mapper,其经由一个XML描述符而不是元数据映射器把对象映射到存储过程、SQL语句或是ResultSet上,而元数据映射器起的是把领域中的对象映射到数据库中的表上的作用。因此,iBATIS能够使得数据模型和对象模型彼此独立,互不相干。

 

iBATIS简介< o<="" span=""><>

    iBATIS项目最初由Cinton Begin发起并在2001年发布。这一持久性框架最初是为Java设计的,然而后来已经被扩展以支持其他的平台,这其中包括.NetRuby

 

iBATIS的工作方式< o<="" span=""><>

iBATIS通过把数据库的输入输出映射到领域对象上来支持数据库和应用之间的松散耦合,并由此而引入了一个抽象层。映射是通过使用包含了SQL查询的XML文件来完成的。这一松散耦合允许映射在应用和数据库设计失配的系统上工作,它还有助于用来处理传统遗留数据库和随时间发生变化的数据库。

iBATIS框架主要使用下面的两个XML文件作为描述符:

Ÿ           SQLMapConfig.xml

Ÿ           SQLMap.xml

 

我们会仔细地研究每个文件。

SQLMapConfig.xml< o<="" font=""><>

SQLMapConfig.xml是一个主要的XML文件,其包含所有的配置细节,比如数据源这一类的数据细节等;它还可以选择在其中包含关于事务管理的信息。该文件标识了所有的SQLMap.xml文件——可能有不止一个这样的文件——并加载它们。

考虑这样一个映射到数据库中的EMPLOYEE表上的Employee类,类的属性——emp_id,emp_firstnameemp_lastname——对应于表中的相似名字列。Employee类的类图如图2所示。(该类会被用来说明本文中讨论的不同的持久性技术。)

 

Employee类的类图

2. Employee类的类图< o<="" span=""><>

 

Employee类的SQLMapConfig.xml文件可以写成清单1所示的内容。

 

1. Employee类的SQLMapConfig.xml文件< o<="" span=""><>

 

< o<="" font=""><>

  < o<="" font=""><>

    < o<="" font=""><>

                value="com.mysql.jdbc.Driver"/>< o<="" font=""><>

      < o<="" font=""><>

      < o<="" font=""><>

      < o<="" font=""><>

    < o<="" font=""><>

  < o<="" font=""><>

 SQL Map XML文件。可以从classpath中做加载,因为它们放在这里(com.mydomain.data... -->< o<="" font=""><>

  < o<="" font=""><>

 < o<="" font=""><>

 

SQLMapConfig.xml使用transactionManager标签来配置给这一特定的SQL映射使用的数据源。其指定数据源的类型以及一些细节,其中包括驱动程序、数据库URL以及用户名称和用户密码等信息。sqlMap标签则指定SQLMap.xml文件的位置,以便加载它。

SQLMap.xml

另一个XML文件是SQLMap.xml,该文件在其相关到某个表后再做实际命名。在单个应用中可能就会存在任意数量的这种文件,这一文件是把领域对象映射到SQL语句的地方。这一描述符使用参数映射来把输入映射到语句上,并使用结果映射来映射SQLResultSet。该文件还包含了查询,因此,要改变查询的话,你需要修改的是XML而非应用的Java代码。映射是通过使用实际的要和数据库交互的SQL语句来完成的。所以,使用SQL给开发者提供了更大的灵活性,并使得iBATIS对于有使用SQL编程经验的人来说变得更容易理解。

定义了在EMPLOYEE表上执行CRUD操作的SQL语句的SQLMap.xml文件如清单2所示。

2. 用于在EMPLOYEE上执行操作的SQLMap.xml< o<="" font=""><>

 

< o<="" font=""><>

  < o<="" font=""><>

  < o<="" font=""><>

    < o<="" font=""><>

    < o<="" font=""><>

    < o<="" font=""><>

   < o<="" font=""><>

  Employee类的结果映射来从表中选择所有的数据-->< o<="" font=""><>

  < o<="" font=""><>

id从表中选择数据 -->< o<="" font=""><>

      emp_lastname as lastName from EMPLOYEE where emp_id= #id#< o<="" font=""><>

< o<="" font=""><>

-->< o<="" font=""><>

< o<="" font=""><>

    insert into EMPLOYEE (< o<="" font=""><>

      emp_id,< o<="" font=""><>

       emp_firstname,< o<="" font=""><>

       emp_lastname)< o<="" font=""><>

    values (< o<="" font=""><>

      #id#, #firstName# , #lastName# )< o<="" font=""><>

  < o<="" font=""><>

id更新Employee的记录-->< o<="" font=""><>

  < o<="" font=""><>

    update EMPLOYEE set< o<="" font=""><>

      emp_firstname = #firstName#,< o<="" font=""><>

      emp_lastname = #lastName#< o<="" font=""><>

    where< o<="" font=""><>

      emp_id = #id#< o<="" font=""><>

  < o<="" font=""><>

id删除Employee的记录 -->< o<="" font=""><>

    < o<="" font=""><>

    delete from EMPLOYEE where emp_id = #id#< o<="" font=""><>

  < o<="" font=""><>

 

在清单2中,typeAlias标签被用来表示类型的别名,这样就可以避免在每次类名出现的时候都要输入完整的类名。其包含了resltMap标签,该标签描述了从查询中返回的列和Employee类所表示的类属性之间的映射。resultMap是可选的,如果表(或别名)中的列与类属性完全匹配的话就不需要resultMap。跟在这一resultMap标签后面的是一系列的查询,SQLMap.xml可以包含任意数目的查询。所有的这些selectinsertupdatedelete语句都写在各自的标签内部,每个语句都使用id属性来命名。

来自select查询的输出可以映射到一个resultMap或是一个JavaBean结果类上。查询中的别名应该与目标结果类(即该JavaBean)中的属性相匹配。parameterClass属性被用来指定其属性作为输入的JavaBean,这一哈希符号内的任何参数都是该JavaBean的属性。< o<="" span=""><>

Java应用中< o<="" span=""><>

在完成了整个的配置和在两个XML文件中都做了映射之后,SQLMapConfig.xml文件需要由Java应用来加载。第一步是加载早先创建的SQLMap.xml配置文件,要做到这一点的话,你需要用到com.ibatis.common.resources.Resources类,该类已包含在iBATIS框架中,如清单3所示。

 

3. 加载SQLMap.xml< o<="" font=""><>

 

private static SqlMapClient sqlMapper;< o<="" font=""><>

...< o<="" font=""><>

 try {< o<="" font=""><>

      Reader reader = Resources.getResourceAsReader("com/mydomain/data/SqlMapConfig.xml");< o<="" font=""><>

      sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);< o<="" font=""><>

      reader.close();< o<="" font=""><>

    } catch (IOException e) {< o<="" font=""><>

      // Fail fast.< o<="" font=""><>

      throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);< o<="" font=""><>

    }< o<="" font=""><>

  }< o<="" font=""><>

 

SqlMapClient类用来与SQLMap一起工作,其允许运行诸如selectinsertupdate一类的已映射语句。SqlMapClient对象是线程安全的,因此,一个对象就足够了,这也使得把它用作一个静态成员成为了一种不错的选择。该对象通过读入一个SQLMapConfig.xml文件来创建,iBATIS框架提供了Resources.getResourceAsReader()这一实用方法,你可以使用该方法来读入SQLMapConfig.xml文件。因此,通过使用SQLMap的这一实例,你可以访问来自数据库的对象——在这一例子中,是Employee对象。

为了调用针对EMPLOYEE表的操作,SQLMap提供了不同的方法,比如其中就包括queryForList()queryForObject()insert()update()queryForMap()等。清单4中展示的queryForList()方法返回Employee对象的一个列表。

 

4. queryForList()< o<="" font=""><>

 

sqlMapper.queryForList("selectAllEmps");< o<="" font=""><>

 

同样地,当只有一行内容作为查询结果返回时,应该使用queryForObject()方法。这两个方法都使用语句名作为参数。

相应的方法可用于执行insertupdatedelete操作,如清单5所示。这些方法既要用到SQLMap.xml文件中声明的语句名,也要用到Employee对象作为输入。< o<="" span=""><>

 

5. insertupdatedelete操作< o<="" span=""><>

 

sqlMapper.insert("insertEmp", emp);< o<="" font=""><>

sqlMapper.update("updateEmp", emp);< o<="" font=""><>

sqlMapper.delete("deleteEmp", id);< o<="" font=""><>

 

这样,Java对象就可以使用iBATISJava对象中的直接的SQL语句来持久化了。< o<="" span=""><>

何时使用iBATIS< o<="" font=""><>

iBATIS最好是用在你需要全面地控制SQL的时候,在需要对SQL查询做微调的时候也很有用。当你在应用和数据库设计两方面都有完全的控制权的时候,就不应该使用iBATIS,因为在这样的情况下,应用可能会做出修改以适应数据库,或是反过来。在这种情形中,你可以构建一个完全的对象-关系应用,其他的ORM工具更适于使用,因为iBATIS较为以SQL为中心,其通常被称作反转的——功能齐全的ORM工具生成SQL,而iBATIS直接使用SQLiBATIS也不适合于非关系型的数据库,因为这类数据库不支持事务和其他iBATIS用到的键特性。< o<="" span=""><>

Hibernate< o<="" font=""><>

Hibernate是一个开源的轻量级的对象-关系映射解决方案。Hibernate的主要特点是支持基于对象的建模,这使得它可以提供一个透明的持久性机制。其使用XML来把数据库映射到应用上,并且支持细粒度的对象。Hibernate的当前版本是3.x,该版本支持Java注解(annotation),因此是满足EJB规范的。< o<="" span=""><>

Hibernate包含了一种被称作Hibernate Query Language或是HQL的非常强大的查询语言。HQL非常类似SQL,不过还定义了一些额外的约定。HQL是完全面向对象的,能够充分利用继承、多态和关联等这些面向对象核心概念的长处。除了被用到的Java类和属性的名称之外,HQL查询是非大小写敏感的。HQL把查询结果作为对象返回,这些对象可以由编程者直接访问和操纵。HQL还支持分页和动态分析(profiling)等许多高级功能,SQL一直未提供对这些功能的支持。在用到多个表来工作时,HQL并不要求做任何显式的连接(join)。< o<="" span=""><>

 

Hibernate简介< o<="" span=""><>

    Hibernate是由Gavin King带领的一个团队开发出来。Hibernate的开发始于2001年,该团队后来被JBoss收购,Hibernate现由JBoss管理。Hibernate最初是为Java开发的;在2005年引入了命名为NHibernate.Net版本。

 

为什么我们需要Hibernate?< o<="" font=""><>

传统上用于对象-关系映射的实体beanentity bean)非常难以理解和维护,Hibernate使得对象-关系映射变得简单起来,它的方法是在一个XML文件中映射元数据,该文件定义了需要映射到某个特定类上的数据库中的表。在其他的持久性框架中,你需要修改应用类来实现对象-关系映射;而在Hibernate中则不需要这样做。< o<="" span=""><>

使用了Hibernate后,你就无需担心数据库的改变,因为手工修改SQL脚本文件的工作已被免除。如果你需要不时改变应用使用到的数据库的话,也可以通过修改配置文件中的dialet属性来很容易地解决这一问题。Hibernate提供了全部的SQL功能,其中的有些是早先的商业ORM框架一直没有提供的。Hibernate也支持许多的数据库,其中包括MySQLOracleSybaseDerbyPostgreSQL等,而且也能够与基于简单Java对象(plain old Java objectPOJO)的模型配合得很好。< o<="" span=""><>

Hibernate基于所选择的底层数据库来生产JDBC代码,因此省去了编写JDBC代码的麻烦,它还支持连接的池化。Hibernate使用的API很简单也很容易学习,只有很少SQL知识的开发者也能够使用Hibernate,因为它减轻了编写SQL查询的负担。< o<="" span=""><>

Hibernate架构< o<="" span=""><>

就内部来说,Hibernate用到了JDBCJDBC提供了数据库的一个抽象层,它同时也采用了Java Transaction APIJTA)和JNDI来集成其他应用。Hibernate需要用来与数据库交互的连接信息由JDBC连接池提供,这需要做配置。< o<="" span=""><>

Hibernate的架构主要由两个接口——SessionTransaction组成——以及一个Query接口,该接口位于应用的持久层中。定义于应用的业务层中的类通过Hibernate持久层的独立元数据来进行交互,持久层转而使用某些JDBC API来与数据库层对话。此外,Hibernate还用到了其他的配置接口,其中主要是有着适当命名的Configuration类。Hibernate还使用回调接口和一些用于扩展映射功能的可选接口。完整的Hibernate架构如图3所示。< o<="" span=""><>

 

Hibernate架构:全貌

3. Hibernate架构:全貌< o<="" span=""><>

 

下面是Hibernate组成部分的主要编程接口:< o<="" span=""><>

Ÿ           org.hibernate.SessionFactory基本上是用来获取一个session