以下内容来自:http://article.yeeyan.org/view/213582/180283
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规范的一部分,但其作为一个完整的持久性解决方案却未能打动业界,主要是因为这几方面的原因:
Ÿ 实体bean是重量级组件且与Java EE服务器紧密耦合,这使得它们相比于轻量级的POJO来说更缺乏适应性,对于可重用性来说,POJO更加理想。
Ÿ 实体bean难以开发和部署。
Ÿ 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作为持久性提供程序。
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一起工作。 |
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
******/
实体类的特性如下:
Ÿ 实体类使用javax.persistence.Entity这一注解(@Entity)来进行注释。
Ÿ 其必须有一个公有的或是受保护的未带参数的构造函数,也还可以包含其他的构造函数。
Ÿ 其不能声明成final的。
Ÿ 实体类可继承于其他的实体类和非实体类,反过来也是可以的。
Ÿ 其不能有公有的实例变量,应该只能使用公有的getter和setter方法来暴露类成员,遵循JavaBean的风格。
Ÿ 实体类,作为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
oracle.toplink.essentials.PersistenceProvider
com.trial.Employee
该persistence.xml文件定义了一个名为EmployeePU的持久单元,相应数据库的配置也包含在这一持久单元中。一个应用可以配有多个关联到不同数据库的持久单元。
总而言之,JPA为Java SE应用和Java EE应用提供了一个标准的基于POJO的ORM解决方案,其使用实体类、实体管理器和持久单元来映射和持久领域对象和数据库中的表。
JPA应该用在需要标准的基于Java的持久性解决方案的时候。JPA支持继承和多态这两种面向对象编程特性。JPA的缺点是其需要一个实现了其自身的提供程序。这些供应商特有的工具还提供了某些并未定义成JPA规范组成部分的特性,其中一个这样的特性是缓存支持,该功能并未在JPA中做明确定义,但其中一个最流行的实现了JPA的框架Hibernate对这一功能提供了很好的支持。
此外,JPA被定义成只能在关系数据库上工作。如果你的持久化解决方案需要扩展到其他类型的数据存储上,比如XML数据库上的话,则JPA就不能够用来解决你的持久性问题了。
现在你已经分析了三种不同的持久化机制及其运作方式。这些框架中的每一种都有自己的优点和缺点。让我们来考虑几个参数,这些参数可帮助你确定其中满足你需求的最佳可行方案。
在许多应用的开发中,时间是主要的制约因素,特别是当团队成员需要经培训来使用某种特定框架的时候。在这类情形中,iBATIS是最好的选择,该框架是三种框架中最简单的,因为它仅需SQL方面的知识就够了。
像Hibernate和JPA一类的传统的ORM解决方案应该用来作为一种完全的对象-关系映射手段。Hibernate和JPA直接把Java对象映射到数据库表上,而iBATIS则是把Java对象映射到SQL查询的结果上。在某些应用中,领域模型中的对象是根据业务逻辑来设计的,可能不完全与数据模型匹配,在这种情况下,iBATIS是合适的选择。
总是会存在精通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正慢慢赶上。
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解决方案等各方面的考虑来做决定