iBATIS、Hibernate和JPA
在本文中我们介绍并比较两种最流行的开源持久框架:iBATIS和Hibernate,我们还会讨论到Java Persistence API(JPA)。我们介绍每种解决方案并讨论其所规定的品质,以及在广泛的应用场景中其各自的长处和缺点。然后我们会基于诸如性能、移植性、复杂性以及对数据模型改变的适应性等因素来比较iBATIS、Hibernate和JPA。
如果你是一个刚起步的Java程序员,新接触持久性概念的话,那么就把阅读此文当作是接受一次这一主题以及大部分流行的开源持久性解决方案的启蒙。如果你对这三种解决方案都很熟悉,并且只想要一个简单的比较的话,那么你会在“比较持久化技术”一节中找到相应的内容。
理解持久性
持久性(persistence)是数据的一个属性,其确保即使是在应用的生命周期之外数据也是可用的。对于像Java这样的面向对象语言来说,持久性确保了即使是在创建对象的应用停止执行之后对象的状态仍是可访问的。
存在多种实现持久性的方法。传统的解决这一问题的方法是使用文件系统来把需要的信息存储在平面文件(flat file)中。这一方法很难用来管理大量的数据,因为数据分布在不同的文件中。使用平面文件系统的话维护数据的一致性也是一个问题,因为相同的信息可能会被重复放在各个文件中。在平面文件中查找数据很耗时,特别是如果这些文件还未排序的话。还有,文件系统对并发访问的支持有限,因为它们不能确保数据的完整性。基于上述种种原因,在寻求持久性时,文件系统并不被视为一个良好的数据存储解决方案。
当前最常见的方法是使用数据库,其充当巨大量数据的存储库。存在许多种类型的数据库:关系型的、层次结构型的、网络型的、面向对象型的等等。这些数据库,以及它们的数据库管理系统(DBMS),不仅提供持久性能力,而且管理其所持久的信息。关系数据库是最被广泛使用的类型,关系数据库中的数据被建模成一组相互关联的表。
企业级应用的出现普及了n层架构,其目的是通过把表现、业务和数据库相关代码分离到应用的不同层级(或是层面)中来提升可维护性。分离了业务逻辑和数据库代码的层面即是持久层,其保持了应用相对于底层的数据库技术的独立性。适当的位置上有了这一强健的层面,开发者就不再需要操心数据的持久性。持久层封装了存储和检索关系型数据库中的数据的方式。
Java应用传统上使用JDBC(Java Database Connectivity)API来把数据持久到关系数据库中。JDBC API使用SQL语句来完成创建(create)、读取(read)、更新(update)和删除(delete)(CRUD)操作。JDBC代码内嵌在Java类中——换句话说,这类代码与业务逻辑紧密耦合在一起。这类代码还在很大程度上依赖于SQL,而SQL并非是跨数据库的标准;这使得从一种数据库移植到另一种数据库变得困难起来。
关系数据库技术强调的是数据及其之间的关系,而用于Java中的面向对象范式却并非关注数据本身,而是关注执行于数据之上的操作。因此,当这两种技术需要携手合作时,就会存在利益冲突。而且,关系数据库并不能满足继承、多态及关联这些面向对象编程概念。当Java应用中的用户定义的数据类型被映射到关系数据库上时,由这一失配导致的另一个问题就出现了,因为后者并没有提供所需的类型支持。
对象关系映射
对象关系映射(object-relational mapping,ORM)已成为了有时被称作对象关系阻抗失配(impedance mismatch)的这一问题的一个解决方案。ORM是一种透明地把应用对象持久到关系数据库中的表的技术。ORM的行为就像是一个虚拟的数据库,对用户隐藏了底层的数据库架构。ORM提供功能来执行完整的CRUD操作并鼓励面向对象的查询。ORM还支持元数据映射以及在应用的事务管理方面提供帮助。
举个例子有助于说明ORM是如何工作的。考虑一个需要持久到数据库中的简单的Car对象,领域模型中的这一Car对象是数据模型中的CAR表的表现形式。Car对象的属性派生自CAR表的各列。在Car类和CAR表之间存在一个直接映射,如图1中的说明。
图1. 把对象映射到表上
存在许多开源的ORM工具,其中包括Hibernate、iBATIS SQL Maps以及Java Ultra-Lite Persistence等。这些工具大多数是提供Java应用和数据库之间的抽象层的持久性框架。持久性框架把应用领域中的对象映射成需要持久在数据库中的数据,这些映射可使用XML文件或是元数据注解(后者作为Java1.5的组成部分被引入到语言中)来定义。持久性框架的目的是分离数据库相关代码和应用代码(即业务逻辑),从而提高应用的灵活性。持久性框架通过提供一个持久性逻辑的包装器来简化开发过程。
完成了持久性的基本介绍之后,我们就做好了继续讨论两种最流行的开源持久框架iBATIS和Hibernate的准备。我们还会介绍Java Persistence API,并讨论这三种解决方案在各种应用场景中的优势和弱点。
iBATIS:直接使用SQL
对象-关系映射(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简介 |
iBATIS项目最初由Cinton Begin发起并在2001年发布。这一持久性框架最初是为Java设计的,然而后来已经被扩展以支持其他的平台,这其中包括.Net和Ruby。 |
iBATIS的工作方式
iBATIS通过把数据库的输入输出映射到领域对象上来支持数据库和应用之间的松散耦合,并由此而引入了一个抽象层。映射是通过使用包含了SQL查询的XML文件来完成的。这一松散耦合允许映射在应用和数据库设计失配的系统上工作,它还有助于用来处理传统遗留数据库和随时间发生变化的数据库。
iBATIS框架主要使用下面的两个XML文件作为描述符:
l SQLMapConfig.xml
l SQLMap.xml
我们会仔细地研究每个文件。
SQLMapConfig.xml
SQLMapConfig.xml是一个主要的XML文件,其包含所有的配置细节,比如数据源这一类的数据细节等;它还可以选择在其中包含关于事务管理的信息。该文件标识了所有的SQLMap.xml文件——可能有不止一个这样的文件——并加载它们。
考虑这样一个映射到数据库中的EMPLOYEE表上的Employee类,类的属性——emp_id,、emp_firstname和emp_lastname——对应于表中的相似名字列。Employee类的类图如图2所示。(该类会被用来说明本文中讨论的不同的持久性技术。)
图2. Employee类的类图
Employee类的SQLMapConfig.xml文件可以写成清单1所示的内容。
清单1. Employee类的SQLMapConfig.xml文件
<sqlMapConfig>
<transactionManager type="JDBC" commitRequired="false">
<dataSource type="EMPLOYEE">
<property name="JDBC.Driver" value="com.mysql.jdbc.Driver"/>
<property name="JDBC.ConnectionURL" value="jdbc:mysql://localhost:3306/ibatis"/>
<property name="JDBC.Username" value="root"/>
<property name="JDBC.Password" value=""/>
</dataSource>
</transactionManager>
<!-- List the SQL Map XML files. They can be loaded from the classpath, as they are here (com.mydomain.data...) -->
<sqlMap resource="com/mydomain/data/Employee.xml"/>
</sqlMapConfig>
SQLMapConfig.xml使用transactionManager标签来配置给这一特定的SQL映射使用的数据源。其指定数据源的类型以及一些细节,其中包括驱动程序、数据库URL以及用户名称和用户密码等信息。sqlMap标签则指定SQLMap.xml文件的位置,以便加载它。
SQLMap.xml
另一个XML文件是SQLMap.xml,该文件在其相关到某个表后再做实际命名。在单个应用中可能就会存在任意数量的这种文件,这一文件是把领域对象映射到SQL语句的地方。这一描述符使用参数映射来把输入映射到语句上,并使用结果映射来映射SQL的ResultSet。该文件还包含了查询,因此,要改变查询的话,你需要修改的是XML而非应用的Java代码。映射是通过使用实际的要和数据库交互的SQL语句来完成的。所以,使用SQL给开发者提供了更大的灵活性,并使得iBATIS对于有使用SQL编程经验的人来说变得更容易理解。
定义了在EMPLOYEE表上执行CRUD操作的SQL语句的SQLMap.xml文件如清单2所示。
清单2. 用于在EMPLOYEE上执行操作的SQLMap.xml
<sqlMap namespace="Employee">
<typeAlias alias="Employee" type="com.sample.Employee"/>
<resultMap id="EmpResult" class="Employee">
<result property="id" column="emp_id"/>
<result property="firstName" column="emp_firstname"/>
<result property="lastName" column="emp_lastname"/>
</resultMap>
<!-- Select all data from the table using the result map for Employee class.-->
<select id="selectAllEmps" resultMap="EmpResult">
select * from EMPLOYEE
</select>
<!-- Select the data from the table based on the id. -->
<select id="selectEmpById" parameterClass="int" resultClass="Employee">
<select emp_id as id,emp_firstname as firstName, emp_lastname as lastName from EMPLOYEE where emp_id= #id#
</select>
<!-- insert the data into the table -->
<insert id="insertEmp" parameterClass="Employee">
insert into EMPLOYEE (
emp_id,
emp_firstname,
emp_lastname)
values (
#id#, #firstName# , #lastName# )
</insert>
<!-- update the Employee record based on the id -->
<update id="updateEmp" parameterClass="Employee">
update EMPLOYEE set
emp_firstname = #firstName#,
emp_lastname = #lastName#
where
emp_id = #id#
</update>
<!-- delete the Employee record based on the given id -->
<delete id="deleteEmp" parameterClass="int">
delete from EMPLOYEE where emp_id = #id#
</delete>
</sqlMap>
在清单2中,typeAlias标签被用来表示类型的别名,这样就可以避免在每次类名出现的时候都要输入完整的类名。其包含了resltMap标签,该标签描述了从查询中返回的列和Employee类所表示的类属性之间的映射。resultMap是可选的,如果表(或别名)中的列与类属性完全匹配的话就不需要resultMap。跟在这一resultMap标签后面的是一系列的查询,SQLMap.xml可以包含任意数目的查询。所有的这些select、insert、update和delete语句都写在各自的标签内部,每个语句都使用id属性来命名。
来自select查询的输出可以映射到一个resultMap或是一个JavaBean结果类上。查询中的别名应该与目标结果类(即该JavaBean)中的属性相匹配。parameterClass属性被用来指定其属性作为输入的JavaBean,这一哈希符号内的任何参数都是该JavaBean的属性。
加载描述符文件到Java应用中
在完成了整个的配置和在两个XML文件中都做了映射之后,SQLMapConfig.xml文件需要由Java应用来加载。第一步是加载早先创建的SQLMap.xml配置文件,要做到这一点的话,你需要用到com.ibatis.common.resources.Resources类,该类已包含在iBATIS框架中,如清单3所示。
清单3. 加载SQLMap.xml
private static SqlMapClient sqlMapper;
...
try {
Reader reader = Resources.getResourceAsReader("com/mydomain/data/SqlMapConfig.xml");
sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close();
} catch (IOException e) {
// Fail fast.
throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
}
}
SqlMapClient类用来与SQLMap一起工作,其允许运行诸如select、insert、update一类的已映射语句。SqlMapClient对象是线程安全的,因此,一个对象就足够了,这也使得把它用作一个静态成员成为了一种不错的选择。该对象通过读入一个SQLMapConfig.xml文件来创建,iBATIS框架提供了Resources.getResourceAsReader()这一实用方法,你可以使用该方法来读入SQLMapConfig.xml文件。因此,通过使用SQLMap的这一实例,你可以访问来自数据库的对象——在这一例子中,是Employee对象。
为了调用针对EMPLOYEE表的操作,SQLMap提供了不同的方法,比如其中就包括queryForList()、queryForObject()、insert()、update()和queryForMap()等。清单4中展示的queryForList()方法返回Employee对象的一个列表。
清单4. queryForList()
sqlMapper.queryForList("selectAllEmps");
同样地,当只有一行内容作为查询结果返回时,应该使用queryForObject()方法。这两个方法都使用语句名作为参数。
相应的方法可用于执行insert、update和delete操作,如清单5所示。这些方法既要用到SQLMap.xml文件中声明的语句名,也要用到Employee对象作为输入。
清单5. insert、update和delete操作
sqlMapper.insert("insertEmp", emp);
sqlMapper.update("updateEmp", emp);
sqlMapper.delete("deleteEmp", id);
这样,Java对象就可以使用iBATISJava对象中的直接的SQL语句来持久化了。
何时使用iBATIS
iBATIS最好是用在你需要全面地控制SQL的时候,在需要对SQL查询做微调的时候也很有用。当你在应用和数据库设计两方面都有完全的控制权的时候,就不应该使用iBATIS,因为在这样的情况下,应用可能会做出修改以适应数据库,或是反过来。在这种情形中,你可以构建一个完全的对象-关系应用,其他的ORM工具更适于使用,因为iBATIS较为以SQL为中心,其通常被称作反转的——功能齐全的ORM工具生成SQL,而iBATIS直接使用SQL。iBATIS也不适合于非关系型的数据库,因为这类数据库不支持事务和其他iBATIS用到的键特性。
Hibernate
Hibernate是一个开源的轻量级的对象-关系映射解决方案。Hibernate的主要特点是支持基于对象的建模,这使得它可以提供一个透明的持久性机制。其使用XML来把数据库映射到应用上,并且支持细粒度的对象。Hibernate的当前版本是3.x,该版本支持Java注解(annotation),因此是满足EJB规范的。
Hibernate包含了一种被称作Hibernate Query Language或是HQL的非常强大的查询语言。HQL非常类似SQL,不过还定义了一些额外的约定。HQL是完全面向对象的,能够充分利用继承、多态和关联等这些面向对象核心概念的长处。除了被用到的Java类和属性的名称之外,HQL查询是非大小写敏感的。HQL把查询结果作为对象返回,这些对象可以由编程者直接访问和操纵。HQL还支持分页和动态分析(profiling)等许多高级功能,SQL一直未提供对这些功能的支持。在用到多个表来工作时,HQL并不要求做任何显式的连接(join)。
Hibernate简介 |
Hibernate是由Gavin King带领的一个团队开发出来。Hibernate的开发始于2001年,该团队后来被JBoss收购,Hibernate现由JBoss管理。Hibernate最初是为Java开发的;在2005年引入了命名为NHibernate的.Net版本。 |
为什么我们需要Hibernate?
传统上用于对象-关系映射的实体bean(entity bean)非常难以理解和维护,Hibernate使得对象-关系映射变得简单起来,它的方法是在一个XML文件中映射元数据,该文件定义了需要映射到某个特定类上的数据库中的表。在其他的持久性框架中,你需要修改应用类来实现对象-关系映射;而在Hibernate中则不需要这样做。
使用了Hibernate后,你就无需担心数据库的改变,因为手工修改SQL脚本文件的工作已被免除。如果你需要不时改变应用使用到的数据库的话,也可以通过修改配置文件中的dialet属性来很容易地解决这一问题。Hibernate提供了全部的SQL功能,其中的有些是早先的商业ORM框架一直没有提供的。Hibernate也支持许多的数据库,其中包括MySQL、Oracle、Sybase、Derby和PostgreSQL等,而且也能够与基于简单Java对象(plain old Java object,POJO)的模型配合得很好。
Hibernate基于所选择的底层数据库来生产JDBC代码,因此省去了编写JDBC代码的麻烦,它还支持连接的池化。Hibernate使用的API很简单也很容易学习,只有很少SQL知识的开发者也能够使用Hibernate,因为它减轻了编写SQL查询的负担。
Hibernate架构
就内部来说,Hibernate用到了JDBC,JDBC提供了数据库的一个抽象层,它同时也采用了Java Transaction API(JTA)和JNDI来集成其他应用。Hibernate需要用来与数据库交互的连接信息由JDBC连接池提供,这需要做配置。
Hibernate的架构主要由两个接口——Session和Transaction组成——以及一个Query接口,该接口位于应用的持久层中。定义于应用的业务层中的类通过Hibernate持久层的独立元数据来进行交互,持久层转而使用某些JDBC API来与数据库层对话。此外,Hibernate还用到了其他的配置接口,其中主要是有着适当命名的Configuration类。Hibernate还使用回调接口和一些用于扩展映射功能的可选接口。完整的Hibernate架构如图3所示。
图3. Hibernate架构:全貌
下面是Hibernate组成部分的主要编程接口:
l org.hibernate.SessionFactory基本上是用来获取一个session实例,并且可看作是连接池化机制的一个模拟。这是线程安全的,因为所有的应用线程都使用单一的SessionFactory(只要Hibernate只使用一个数据库)。该接口通过配置文件来配置,配置文件决定了要加载的映射文件。
l org.hibernate.Session提供了一个单独的线程,该线程确定应用和数据库之间的对话。这是对一个特定(单个)连接的模拟。该接口是非常轻量级的,而且是非线程安全的。
l org.hibernate.Transaction提供了一个单线程对象,其横跨整个应用并确定原子工作单元。其基本上抽象了JDBC、JTA和CORBA事务。
l org.hibernate.Query被用来执行查询,或以HQL的形式或是以底层数据库的SQL方言形式。Query实例是轻量级的,需要提到的很重要的一点是,它不能用在创建它的session的外部。
配置Hibernate
Hibernate通过一个名为hibernate.cfg.xml的XML文件来做配置。该配置文件提供建立到特定关系数据库的连接方面的帮助,配置文件应该要知道其需要引用哪些映射文件。在运行时,Hibernate读入映射文件,然后使用映射文件来构建一个动态的与数据库表相对应的Java类。一个示例的配置文件如清单6所示。
清单6. hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<!-- local connection properties -->
<property name="hibernate.connection.url">
jdbc:mysql://localhost/hibernateDemo
</property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="hibernate.connection.username">
root
</property>
<property name="hibernate.connection.password">
infosys
</property>
<!-- dialect for MySQL -->
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.show_sql">false</property>
<property name="hibernate.transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
<mapping resource="Employee.hbm.xml" />
</session-factory>
</hibernate-configuration>
使用Hibernate工作
当在应用中创建了一个SessionFactory实例时,Hibernate读入配置文件并标识出各自的映射文件。创建自SessionFactory的session对象获取到数据库的一个特定连接,这个session对象就是持久化类实例的持久化上下文。实例可以是以下三种状态之一:瞬态的(transient)、持久的(persistent)或是游离的(detached)。处于瞬态时,对象尚未与表关联起来;处于持久态中时,对象是与表关联的;而处于游离态中时,则不能保证对象与表是同步的。用来持久一个Employee对象的Hibernate代码如清单7所示。
清单7. 使用Hibernate来持久一个对象
Session session = null;
Transaction tx = null;
// At this point the Configuration file is read
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
// A specific session object is obtained
session = sessionFactory.openSession();
// A new database transaction is started
tx = session.beginTransaction();
// Employee Object is created & populated
Employee emp = new Employee();
emp.setId(1);
emp.setEmpFirstname("K L");
emp.setEmpLastname("Nitin");
// Using the session, emp object is persisted in the database
session.save(emp);
配置文件标识的映射文件把某个特定的持久类映射到数据库表上。它把特定的列映射到特定的字段上,并且具备关联、集合、主键映射以及ID键生成机制。映射文件一般根据其映射到表来命名,在我们的示例应用中,你应该使用Employee.hbm.xml作为对应于EMPLOYEE表的文件的名称。如你在清单8中见到的那样,映射文件指定Employee类需要映射到数据库中EMPLOYEE表上,该表有名为id、emp_firstname和emp_lastname的三列,id是主键,必须要赋予某个值。
清单8. Employee.hbm.xml
<hibernate-mapping package="demo">
<class name="Employee" table="employee" >
<meta attribute="sync-DAO">false</meta>
<id name="Id" type="integer" column="emp_id">
<generator class="assigned"/>
</id>
<property name="EmpFirstname" column="emp_firstname"
type="string" not-null="true" length="30" />
<property name="EmpLastname" column="emp_lastname" type="string" not-null="true" length="30" />
</class>
</hibernate-mapping>
何时使用Hibernate
Hibernate最适合用来作为端到端的OR映射的手段。其提供了一个完整的ORM解决方案,但但是不会让你控制查询。对于那些对应用和数据库两者都有完全的控制权的情况来说,Hibernate是一种理想的解决方案。在这类情况中,你可以修改应用来适用数据库,反之亦然,在这些情况下你可以使用Hibernate来构建一个全对象-关系应用。对于不太熟悉SQL的面向对象编程者来说,Hibernate是最佳选择。
Java Persistence API
Hibernate 和JPA |
刚刚才了解了Hibernate如何用作一个独立的持久化方案,你可能会因发现它也能与JPA一起工作而感到奇怪。严格来讲,如果你打算直接使用Hibernate的话,那么你要使用的就是Hibernate Core这一模块,该模块使用无需处理JDBC对象的HQL生成SQL;应用依然独立于数据库。Hibernate Core可和任何的应用服务器一起使用,以及可用在任何普通的需要实现对象-关系映射的Java应用中,这一映射通过使用原生的Hibernate API、Hibernate Query Language和XML映射来实现。
Hibernate团队积极投身到EJB 3规范的制定中,在引入了EJB 3之后,EJB 3持久性的一个独立实现被作为Hibernate的一部分提供——Hibernate Annotations和Hibernate EntityManager,这两部分都构建在Hibernate Core之上。对于使用Java EE 5做开发的应用来说,在Java EE 5中需要使用EJB 3,因此Hibernate EntityManager可考虑作为持久性提供程序的一个选择,使用Java EE 5做开发的应用会利用Hibernate来和JPA一起工作。 |
Java Persistence API是Java EE 5平台上的标准的对象-关系映射和持久管理接口。作为EJB 3规范成果的一部分,它得到了所有主要的Java供应商的支持。Java Persistence API借鉴了诸如Hibernate、Oracle TopLink、Java Data Objects(JDO)以及EJB容器托管持久化等领先的持久性框架的想法。JPA提供了一个平台,持久性提供程序(persistence provider)可在该平台上获得使用。Java Persistence API的一个主要特性是任何的持久性提供程序都可以在上面做插拔。
JPA是一个基于POJO的标准的ORM持久化模型。它是EJB3规范的组成部分,代替了实体bean。实体bean被定义成EJB 2.1规范的一部分,但其作为一个完整的持久性解决方案却未能打动业界,主要是因为这几方面的原因:
l 实体bean是重量级组件且与Java EE服务器紧密耦合,这使得它们相比于轻量级的POJO来说更缺乏适应性,对于可重用性来说,POJO更加理想。
l 实体bean难以开发和部署。
l BMP实体bean强制使用JDBC,而CMP实体bean则高度依赖Java EE服务器的配置和ORM声明,这些限制将会影响到应用的性能。
为了解决这些问题,EJB 3软件专家组制定了JPA,其作为JSR220的组成部分。JPA从其他的持久化技术那里借用了最好的想法,其为所有的Java应用定义了一个标准的持久化模型。JPA既可用作Java SE应用也可用作Java EE应用的持久化解决方案。
JPA使用元数据注解和/或XML描述符文件来配置应用领域中的Java对象和关系数据库中的表之间的映射。JPA是一个完整的ORM解决方案,并且支持继承和多态。它还定义了一种类SQL的查询语言:JPQL(Java Persistence Query Language),该种语言不同于EJB-QL(EJB Query Language),EJB-QL是由实体bean使用的一种语言。
使用JPA,你就可以插入任何实现了JPA规范的持久性提供程序,而不是随Java EE容器一起提供的缺省的持久性提供程序是什么就用什么。例如,GlassFish服务器使用Oracle提供的TopLink Essentials作为它的缺省持久性提供程序,但是你可以通过在应用中包含所有必须的JAR文件来选择使用Hibernate作为持久性提供程序。
使用JPA工作
JPA用到了Java EE 5版本提供的javax.persistence包中定义的许多接口和注解类型。JPA使用与数据库中的表做映射的实体类(entity class)。这些实体类使用JPA注解来做定义。清单9给出了名为Employee的实体类,其对应于例子应用的数据库中的EMPLOYEE表。
清单9. Employee实体类
@Entity
@Table(name = "employee")
@NamedQueries({@NamedQuery(name = "Employee.findByEmpId", query = "SELECT e FROM Employee e WHERE e.empId = :empId"), @NamedQuery(name = "Employee.findByEmpFirstname", query = "SELECT e FROM Employee e WHERE e.empFirstname = :empFirstname"), @NamedQuery(name = "Employee.findByEmpLastname", query = "SELECT e FROM Employee e WHERE e.empLastname = :empLastname")})
public class Employee implements Serializable {
@Id
@Column(name = "emp_id", nullable = false)
private Integer empId;
@Column(name = "emp_firstname", nullable = false)
private String empFirstname;
@Column(name = "emp_lastname", nullable = false)
private String empLastname;
public Employee() { }
public Employee(Integer empId) {
this.empId = empId;
}
public Employee(Integer empId, String empFirstname, String empLastname) {
this.empId = empId;
this.empFirstname = empFirstname;
this.empLastname = empLastname;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpFirstname() {
return empFirstname;
}
public void setEmpFirstname(String empFirstname) {
this.empFirstname = empFirstname;
}
public String getEmpLastname() {
return empLastname;
}
public void setEmpLastname(String empLastname) {
this.empLastname = empLastname;
}
/****
*override equals, hashcode and toString methods
*using @Override annotation
******/
实体类的特性如下:
l 实体类使用javax.persistence.Entity这一注解(@Entity)来进行注释。
l 其必须有一个公有的或是受保护的未带参数的构造函数,也还可以包含其他的构造函数。
l 其不能声明成final的。
l 实体类可继承于其他的实体类和非实体类,反过来也是可以的。
l 其不能有公有的实例变量,应该只能使用公有的getter和setter方法来暴露类成员,遵循JavaBean的风格。
l 实体类,作为POJO,一般来说不需要实现任何特定的接口,然而,如果其被作为参数在网络间传递的话,它们就必须要实现Serializable接口。
javax.persistence.Table这一注解指定了该实体实例所映射的表的名称。类成员可以是Java原始类型的、Java原始类的封装器、枚举类型的或是其他可嵌入的类。到表的每列的映射使用javax.persistence.Column这一注解来指定。这一映射可用在持久域上,在这种情况下,实体使用持久域,映射也可用在getter/setter方法上,在这种情况下实体使用持久属性。不过对于某个特定的实体类来说,其必须要遵循同一种约定。此外,使用javax.persistence.Transient进行注释的域或是标记为transient的域不会被持久到数据库中。
每个实体都有一个唯一的对象标识符,该标识符用来区分应用领域中的不同实体实例;其对应于定义在相应表中的主键。主键可以是简单的或是复合的。简单主键使用javax.persistence.Id这一注解来进行注释,复合主键可是单个的持久属性/域或是一组这样的域/属性;它们必须定义在一个主键类中。复合主键使用javax.persistence.EmbeddedId和javax.persistence.IdClass这两个注解来进行注释,任何主键类都应该要实现hashcode()和equals()方法。
JPA实体的生命周期由实体管理器进行管理,实体管理器是javax.persistence.EntityManager的一个实例。每个这样的实体管理器都和一个持久化上下文相关联。该上下文可被传播至所有的应用组件中,或是由应用来做管理。EntityManager可在应用中使用EntityManagerFactory来创建,如清单10所示。
清单10. 创建EntityManager
public class Main {
private EntityManagerFactory emf;
private EntityManager em;
private String PERSISTENCE_UNIT_NAME = "EmployeePU";
public static void main(String[] args) {
try {
Main main = new Main();
main.initEntityManager();
main.create();
System.out.println("Employee successfully added");
main.closeEntityManager();
}
catch(Exception ex) {
System.out.println("Error in adding employee");
ex.printStackTrace();
}
}
private void initEntityManager() {
emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
em = emf.createEntityManager();
}
private void closeEntityManager() {
em.close(); emf.close(); }
private void create() {
em.getTransaction().begin();
Employee employee=new Employee(100);
employee.setEmpFirstname("bob");
employee.setEmpLastname("smith");
em.persist(employee);
em.getTransaction().commit();
}
}
PERSISTENCE_UNIT_NAME表示用来创建EntityManagerFactory的持久单元的名称。EntityManagerFactory还可使用javax.persistence.PersistenceUnit这一注解来在应用组件之间做传播。
在清单10的create()方法中,一个新的雇员记录被插入到了EMPLOYEE表中,一旦与persist()关联的EntityTransaction执行完成,实体实例表示的数据就被持久到数据库中。JPA也定义静态的和动态的查询来从数据库中检索数据,静态查询使用javax.persistence.NamedQuery这一注解来编写,如在Employee这一实体类中展示的那样。动态的查询则使用EntityManager的createQuery()方法直接在应用中定义。
JPA结合使用基于注解的和基于XML的配置。用于此目的的XML文件是persistence.xml,该文件位于应用的META-INF目录下。该文件定义了应用用到的所有持久单元,每个持久单元都定义了被映射到某单个数据库上的所有实体类。Employee应用的persistence.xml文件如清单11所示。
清单11. persistence.xml
<persistence>
<persistence-unit name="EmployeePU" transaction-type="RESOURCE_LOCAL"> <provider>oracle.toplink.essentials.PersistenceProvider</provider>
<class>com.trial.Employee</class>
<properties>
<property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/projects"/>
<property name="toplink.jdbc.user" value="root"/>
<property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="toplink.jdbc.password" value="infosys"/>
</properties>
</persistence-unit>
</persistence>
该persistence.xml文件定义了一个名为EmployeePU的持久单元,相应数据库的配置也包含在这一持久单元中。一个应用可以配有多个关联到不同数据库的持久单元。
总而言之,JPA为Java SE应用和Java EE应用提供了一个标准的基于POJO的ORM解决方案,其使用实体类、实体管理器和持久单元来映射和持久领域对象和数据库中的表。
何时使用JPA
JPA应该用在需要标准的基于Java的持久性解决方案的时候。JPA支持继承和多态这两种面向对象编程特性。JPA的缺点是其需要一个实现了其自身的提供程序。这些供应商特有的工具还提供了某些并未定义成JPA规范组成部分的特性,其中一个这样的特性是缓存支持,该功能并未在JPA中做明确定义,但其中一个最流行的实现了JPA的框架Hibernate对这一功能提供了很好的支持。
此外,JPA被定义成只能在关系数据库上工作。如果你的持久化解决方案需要扩展到其他类型的数据存储上,比如XML数据库上的话,则JPA就不能够用来解决你的持久性问题了。
比较持久化技术
现在你已经分析了三种不同的持久化机制及其运作方式。这些框架中的每一种都有自己的优点和缺点。让我们来考虑几个参数,这些参数可帮助你确定其中满足你需求的最佳可行方案。
简易性
在许多应用的开发中,时间是主要的制约因素,特别是当团队成员需要经培训来使用某种特定框架的时候。在这类情形中,iBATIS是最好的选择,该框架是三种框架中最简单的,因为它仅需SQL方面的知识就够了。
完整的ORM解决方案
像Hibernate和JPA一类的传统的ORM解决方案应该用来作为一种完全的对象-关系映射手段。Hibernate和JPA直接把Java对象映射到数据库表上,而iBATIS则是把Java对象映射到SQL查询的结果上。在某些应用中,领域模型中的对象是根据业务逻辑来设计的,可能不完全与数据模型匹配,在这种情况下,iBATIS是合适的选择。
对SQL的依赖
总是会存在精通Java的人和更信任SQL的人这样的一种划分,对于一个熟练的Java程序员来说,他想使用一个无需与SQL有太多交互的持久性框架,那么Hibernate是最好的选择,因为它会在运行时生成高效率的SQL查询。但是,如果你想要使用存储过程来对数据库查询做各方面的控制的话,则iBATIS是推荐的解决方案。JPA还可通过EntityManager的createNativeQuery()方法来支持SQL。
支持的查询语言
iBATIS大力支持SQL,而Hibernate和JPA则是使用它们自己的查询语言(分别是HQL和JPQL),这些语言与SQL类似。
性能
一个应用要成功的话需要具备良好的性能。Hibernate通过提供缓存设施来提高性能,这些缓存设施有助于更快地从数据库中检索数据。iBATIS使用SQL查询,这些查询可通过微调来获得更佳性能。JPA的性能则取决于供应商的实现,根据每个应用的特有情况做选择。
跨不同数据库的移植性
有时候,你需要改变应用使用的关系数据库,如果你使用Hibernate来作为持久化解决方案的话,那么这一问题很容易解决,因为Hibernate在配置文件中使用了一个数据库方言属性。从一个数据库移植到另一个数据库上仅是把dialect属性修改成适当值的事。Hibernate使用这一属性来作为生成特定于某种给定数据库的SQL代码的指南。
如前所述,iBATIS要求你编写自己的SQL代码,因此,iBATIS应用的可移植性取决于这些SQL。如果查询是使用可移植的SQL编写的话,那么iBATIS也是可在不同的关系数据库之间做移植的。另一方面,JPA的移植性则取决于其正在使用的供应商实现。JPA是可在不同的实现之间做移植的,比如Hibernate和TopLink Essentials之间。因此,如果应用没有用到某些提供商特有的功能特性的话,那么移植性就不是什么大问题。
社区支持和文档
在这方面,Hibernate明显是个赢家。存在许多以Hibernate为焦点的论坛,在这些论坛中社区成员都会积极地回答各种问题。关于这一点,iBATIS和JPA正慢慢赶上。
跨非Java平台的移植性
iBATIS支持.Net和Ruby on Rails。Hibernate以NHibernate的形式为.Net提供了一个持久性解决方案。JPA,作为特定于Java的API,显然并不支持任何的非Java平台。
表1给出了这一比较的一个总结。
表1. 持久性解决方案比较
特性 |
iBATIS |
Hibernate |
JPA |
简易性 | 优 | 良 | 良 |
完整的ORM解决方案 | 一般 | 优 | 优 |
对数据模型改变的适应性 | 良 | 一般 | 一般 |
复杂性 | 优 | 一般 | 一般 |
对SQL的依赖 | 良 | 一般 | 一般 |
性能 | 优 | 优 | 不适应* |
跨不同关系数据库的移植性 | 一般 | 优 | 不适应* |
非Java平台的移植性 | 优 | 良 | 不支持 |
社区支持和文档 | 一般 | 良 | 良 |
*JPA对这些特性的支持取决于持久性提供程序,最终的结果可能会视情况各异。
结论
iBATIS、Hibernate和JPA是用于把数据持久到关系数据库中的三种不同的机制,每种都有着自己的优势和局限性。iBATIS不提供完整的ORM解决方案,也不提供任何的对象和关系模型的直接映射。不过,iBATIS给你提供了对查询的全面控制权。Hibernate提供了一个完整的ORM解决方案,但不提供对查询的控制权。Hibernate非常的受欢迎,有一个庞大而活跃的社区为新用户提供支持。JPA也提供一个完整的ORM解决方案,并提供对诸如继承和多态一类的面向对象编程特性的支持,不过它的性能则取决于持久性提供程序。
某个特定持久性机制的选择事关所有功能特性的权衡,这些特性在本文的比较章节中都做了讨论。对于大部分的开发者来说,需要根据是否要求对应用的SQL做全面控制、是否需要自动生成SQL,或仅是想要一个易于编程的完整的ORM解决方案等各方面的考虑来做决定。
上一篇: PHPexecl导出一个复杂的表头
推荐阅读
-
Hibernate中使用HQLQuery查询全部数据和部分数据的方法实例
-
Hibernate一级缓存和二级缓存详解
-
Hibernate中Session.get()方法和load()方法的详细比较
-
Hibernate核心类和接口的详细介绍
-
基于spring boot 1.5.4 集成 jpa+hibernate+jdbcTemplate(详解)
-
Spring Web MVC和Hibernate的集成配置详解
-
Hibernate迫切连接和普通连接的区别实例详解
-
基于spring boot 1.5.4 集成 jpa+hibernate+jdbcTemplate(详解)
-
Spring-Data-JPA整合MySQL和配置的方法
-
解决Hibernate JPA中insert插入数据后自动执行select last_insert_id()