JAVA 开发平台的技术和框架(四)三、ORM :JPA

Java Persistence API

以下内容来自:http://article.yeeyan.org/view/213582/180283 

Java Persistence APIJava EE 5平台上的标准的对象-关系映射和持久管理接口。作为EJB 3规范成果的一部分,它得到了所有主要的Java供应商的支持。Java Persistence API借鉴了诸如HibernateOracle TopLinkJava Data ObjectsJDO)以及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的查询语言:JPQLJava Persistence Query Language),该种语言不同于EJB-QLEJB Query Language),EJB-QL是由实体bean使用的一种语言。

使用JPA,你就可以插入任何实现了JPA规范的持久性提供程序,而不是随Java EE容器一起提供的缺省的持久性提供程序是什么就用什么。例如,GlassFish服务器使用Oracle提供的TopLink Essentials作为它的缺省持久性提供程序,但是你可以通过在应用中包含所有必须的JAR文件来选择使用Hibernate作为持久性提供程序。

HibernateJPA

刚刚才了解了Hibernate如何用作一个独立的持久化方案,你可能会因发现它也能与JPA一起工作而感到奇怪。严格来讲,如果你打算直接使用Hibernate的话,那么你要使用的就是Hibernate Core这一模块,该模块使用无需处理JDBC对象的HQL生成SQL;应用依然独立于数据库。Hibernate Core可和任何的应用服务器一起使用,以及可用在任何普通的需要实现对象-关系映射的Java应用中,这一映射通过使用原生的Hibernate APIHibernate Query LanguageXML映射来实现。

Hibernate团队积极投身到EJB 3规范的制定中,在引入了EJB 3之后,EJB 3持久性的一个独立实现被作为Hibernate的一部分提供——Hibernate AnnotationsHibernate EntityManager,这两部分都构建在Hibernate Core之上。对于使用Java EE 5做开发的应用来说,在Java EE 5中需要使用EJB 3,因此Hibernate EntityManager可考虑作为持久性提供程序的一个选择,使用Java EE 5做开发的应用会利用Hibernate来和JPA一起工作。

  使用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的。

Ÿ           实体类可继承于其他的实体类和非实体类,反过来也是可以的。

Ÿ           其不能有公有的实例变量,应该只能使用公有的gettersetter方法来暴露类成员,遵循JavaBean的风格。

Ÿ           实体类,作为POJO,一般来说不需要实现任何特定的接口,然而,如果其被作为参数在网络间传递的话,它们就必须要实现Serializable接口。

javax.persistence.Table这一注解指定了该实体实例所映射的表的名称。类成员可以是Java原始类型的、Java原始类的封装器、枚举类型的或是其他可嵌入的类。到表的每列的映射使用javax.persistence.Column这一注解来指定。这一映射可用在持久域上,在这种情况下,实体使用持久域,映射也可用在getter/setter方法上,在这种情况下实体使用持久属性。不过对于某个特定的实体类来说,其必须要遵循同一种约定。此外,使用javax.persistence.Transient进行注释的域或是标记为transient的域不会被持久到数据库中。

每个实体都有一个唯一的对象标识符,该标识符用来区分应用领域中的不同实体实例;其对应于定义在相应表中的主键。主键可以是简单的或是复合的。简单主键使用javax.persistence.Id这一注解来进行注释,复合主键可是单个的持久属性/域或是一组这样的域/属性;它们必须定义在一个主键类中。复合主键使用javax.persistence.EmbeddedIdjavax.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这一注解来在应用组件之间做传播。在清单10create()方法中,一个新的雇员记录被插入到了EMPLOYEE表中,一旦与persist()关联的EntityTransaction执行完成,实体实例表示的数据就被持久到数据库中。JPA也定义静态的和动态的查询来从数据库中检索数据,静态查询使用javax.persistence.NamedQuery这一注解来编写,如在Employee这一实体类中展示的那样。动态的查询则使用EntityManagercreateQuery()方法直接在应用中定义。

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

JPA应该用在需要标准的基于Java的持久性解决方案的时候。JPA支持继承和多态这两种面向对象编程特性。JPA的缺点是其需要一个实现了其自身的提供程序。这些供应商特有的工具还提供了某些并未定义成JPA规范组成部分的特性,其中一个这样的特性是缓存支持,该功能并未在JPA中做明确定义,但其中一个最流行的实现了JPA的框架Hibernate对这一功能提供了很好的支持。

此外,JPA被定义成只能在关系数据库上工作。如果你的持久化解决方案需要扩展到其他类型的数据存储上,比如XML数据库上的话,则JPA就不能够用来解决你的持久性问题了。

比较持久化技术

现在你已经分析了三种不同的持久化机制及其运作方式。这些框架中的每一种都有自己的优点和缺点。让我们来考虑几个参数,这些参数可帮助你确定其中满足你需求的最佳可行方案。

简易性

在许多应用的开发中,时间是主要的制约因素,特别是当团队成员需要经培训来使用某种特定框架的时候。在这类情形中,iBATIS是最好的选择,该框架是三种框架中最简单的,因为它仅需SQL方面的知识就够了。

完整的ORM解决方案

HibernateJPA一类的传统的ORM解决方案应该用来作为一种完全的对象-关系映射手段。HibernateJPA直接把Java对象映射到数据库表上,而iBATIS则是把Java对象映射到SQL查询的结果上。在某些应用中,领域模型中的对象是根据业务逻辑来设计的,可能不完全与数据模型匹配,在这种情况下,iBATIS是合适的选择。

对SQL的依赖

总是会存在精通Java的人和更信任SQL的人这样的一种划分,对于一个熟练的Java程序员来说,他想使用一个无需与SQL有太多交互的持久性框架,那么Hibernate是最好的选择,因为它会在运行时生成高效率的SQL查询。但是,如果你想要使用存储过程来对数据库查询做各方面的控制的话,则iBATIS是推荐的解决方案。JPA还可通过EntityManagercreateNativeQuery()方法来支持SQL

支持的查询语言

iBATIS大力支持SQL,而HibernateJPA则是使用它们自己的查询语言(分别是HQLJPQL),这些语言与SQL类似。

性能

一个应用要成功的话需要具备良好的性能。Hibernate通过提供缓存设施来提高性能,这些缓存设施有助于更快地从数据库中检索数据。iBATIS使用SQL查询,这些查询可通过微调来获得更佳性能。JPA的性能则取决于供应商的实现,根据每个应用的特有情况做选择。

跨不同数据库的移植性

有时候,你需要改变应用使用的关系数据库,如果你使用Hibernate来作为持久化解决方案的话,那么这一问题很容易解决,因为Hibernate在配置文件中使用了一个数据库方言属性。从一个数据库移植到另一个数据库上仅是把dialect属性修改成适当值的事。Hibernate使用这一属性来作为生成特定于某种给定数据库的SQL代码的指南。

如前所述,iBATIS要求你编写自己的SQL代码,因此,iBATIS应用的可移植性取决于这些SQL。如果查询是使用可移植的SQL编写的话,那么iBATIS也是可在不同的关系数据库之间做移植的。另一方面,JPA的移植性则取决于其正在使用的供应商实现。JPA是可在不同的实现之间做移植的,比如HibernateTopLink Essentials之间。因此,如果应用没有用到某些提供商特有的功能特性的话,那么移植性就不是什么大问题。

社区支持和文档

在这方面,Hibernate明显是个赢家。存在许多以Hibernate为焦点的论坛,在这些论坛中社区成员都会积极地回答各种问题。关于这一点,iBATISJPA正慢慢赶上。

跨非Java平台的移植性

iBATIS支持.NetRuby on RailsHibernateNHibernate的形式为.Net提供了一个持久性解决方案。JPA,作为特定于JavaAPI,显然并不支持任何的非Java平台。

1给出了这一比较的一个总结。

1. 持久性解决方案比较

特性

iBATIS

Hibernate

JPA

简易性

完整的ORM解决方案

一般

对数据模型改变的适应性

一般

一般

复杂性

一般

一般

SQL的依赖

一般

一般

性能

不适用*

跨不同关系数据库的移植性

一般

不适用*

Java平台的移植性

不支持

社区支持和文档

一般

 

*JPA对这些特性的支持取决于持久性提供程序,最终的结果可能会视情况各异。

结论

iBATISHibernateJPA是用于把数据持久到关系数据库中的三种不同的机制,每种都有着自己的优势和局限性。iBATIS不提供完整的ORM解决方案,也不提供任何的对象和关系模型的直接映射。不过,iBATIS给你提供了对查询的全面控制权。Hibernate提供了一个完整的ORM解决方案,但不提供对查询的控制权。Hibernate非常的受欢迎,有一个庞大而活跃的社区为新用户提供支持。JPA也提供一个完整的ORM解决方案,并提供对诸如继承和多态一类的面向对象编程特性的支持,不过它的性能则取决于持久性提供程序。

某个特定持久性机制的选择事关所有功能特性的权衡,这些特性在本文的比较章节中都做了讨论。对于大部分的开发者来说,需要根据是否要求对应用的SQL做全面控制、是否需要自动生成SQL,或仅是想要一个易于编程的完整的ORM解决方案等各方面的考虑来做决定

你可能感兴趣的:(java)