Spring 2 和 JPA 简介(4)

通过 Spring 2 的 JPA 支持获得数据访问

在这一节,我们将利用 Spring 2 对 Java 持久性 API(JPA)的支持把数据库访问添加到 Employee 和 Address。

通过 JPA ORM 的对象持久性

为提供前面定义的服务接口的实现,需要利用 Spring 2 的部分特性和支持库。当然,存在替代方法。可以用标准的 Java 数据库访问技术,例如 JDBC,一个方法一个方法地开始实现接口。但是,看到 Spring 2 如何用 JPA 实现这一任务之后,您就会认识到,把这项工作委托给 Spring 实际上要更容易。

在 Spring 2 中,集成了来自 EJB 3.0 和 Java EE 5 规范的 JPA 持久性栈(请参阅 参考资料),使之成为 Spring 支持数据库访问的最简单而且也是标准的方式。

Spring 框架一直通过其他对象到关系的映射(ORM)技术支持持久性,但是这类映射任务要求对第三方非标准的技术性库有相当精妙和深入的了解。随着 JPA 的到来,大批供应商开始支持 JPA 标准,因而对非标准的第三方持久性库的支持的重要性有所降低。

Spring 2 支持 JPA,这使得为关系数据库编写、读取、搜索、更新和删除对象(POJO)的烦琐工作变得透明。可以继续使用 Java 语言面向对象的语法处理 POJO,JPA ORM 层负责数据库表的创建、查询、更新代码和删除代码。

除了透明的数据库操作,Spring 2 的 JPA 支持还把各种五花八门的特定于数据库厂商的异常转换成一套定义良好的异常,使得异常处理代码大为简化。图 5 演示了 Spring 2 的 JPA 支持:

图 5. Spring 2 JPA 支持



与 Java SE 5 注释一道,JPA 还支持通过外部 XML 文件的映射线索规范。如果您使用的不是 Java SE 5,那么这项技术是必要的,但相关内容已超出了本教程的讨论范围。

在图 5 中,您将自己的对象反馈给 Spring 引擎,同时还有一些关于您希望如何把它们映射到关系数据库表的线索(元数据)。Spring JPA 支持负责处理剩下的工作。可以用 Java 5 注释的形式或外部 XML 定义文件(为了与 JDK 1.4 兼容)的形式向 JPA 引擎提供映射线索。

因为各种 ORM 产品和数据库都存在 JPA 实现,所以您的实现代码是可以在不同厂商的解决方案之间移植的(如果必要)。

对映射对象的操作通过 JPA 实体管理器来执行。例如,用叫作 em 的实体管理器把一个相关对象树写入关系数据库,代码应当是:
em.persists(myObjectTree);
JPA 实体管理器然后检查您所提供的映射线索,并通过 myObjectTree 把对象树的所有映射字段保存到关系数据库。

您很快就会看到(在 用 Spring DAO 实现域服务 一节中),Spring 走得更远,它还简化了使用 JPA 实体管理器的任务。


提供 JPA ORM 映射元数据

要提供如何把 Employee 对象保存到数据库的提示,可以向 Employee.java 源代码添加 Java SE 5 注释。这些线索通常叫作元数据,因为它们是描述数据的数据。

清单 6 显示了注释版本的 Employee 对象,其中的注释以粗体突出显示:

清单 6. 为 Employee POJO 添加 JPA 注释
package com.ibm.dw.spring2;

import java.util.Date;
import javax.persistence.CascadeType;
...
import javax.persistence.TemporalType;

@Entity
public class Employee {
   
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private long empid;

@Column(length = 6)
private String empno;

@Column(name = "FIRSTNME")
private String firstName;

@Column(name = "MIDINIT")
private String midInitial;

private String lastName;

@Column(name = "PHONENO")
private String phoneNumber;

@Column(length = 8)
private String job;

@Column(name = "EDLEVEL")
private int educationLevel;

@Column(length = 1)
private char sex;

@Column(precision=12, scale=2)
private double salary;

@Column(precision=12, scale=2)
private double bonus;

@Column(name = "COMM", precision=12, scale=2)
private double commission;

@OneToOne(cascade = CascadeType.ALL)
private Address addr;

@Temporal(TemporalType.DATE)
private Date hiredate;

@Temporal(TemporalType.DATE)
private Date birthdate;

...
这个示例中的所有注释都在字段级。这是 JPA 注释的最常见用法。也可以给与字段对应的 getter 方法加注释。如果想要保存到数据库中的值是计算得来的,而不是对象的字段时,这可能是必要的。


Employee POJO 的 JPA 注释

表 1 描述了 清单 6 中每个字段的注释,以及提供给 Spring 2 引擎的持久性线索:

表 1. Employee POJO 的 JPA 注释
字段/元素注释说明
Employee @Entity 指出这是要保存到数据库的类。默认情况下类名称被用作表名。
empid @Id 指出这是表的主关键字段。
empid @GeneratedValue(strategy = GenerationType.TABLE) 指定持久性引擎用来分配唯一主关键 ID 的策略。GenerationType.TABLE 指出应当使用可移植的基于表的 ID 序列。其他特定于数据库的选项也可使用,但可能无法跨多个数据库工作。
empno @Column(length = 6) 这个字段包含员工编号,由公司分配。请注意这不是主键。在这个应用程序中,主键是由引擎生成和管理的。@Column() 标记指定字段应当是六个字符长。指定合适的字段长度会有助于限制生成的表的大小。
firstName @Column(name = "FIRSTNME") 指定这个字段在数据库表中应当使用的字段名。
midInitial @Column(name = "MIDINIT") 指定这个字段在数据库表中应当使用的字段名。请注意,它与 Java 字段名不同。
lastName 没有注释,所以将使用字段名 "LASTNAME",与 Java 字段名匹配。
phoneNumber @Column(name = "PHONENO") 指定在数据库表中使用的字段名。
job @Column(length = 8) 指定数据库字段的长度。
educationLevel @Column(name = "EDLEVEL") 指定数据库字段名。
sex @Column(length = 1) 指定数据库字段的长度。
salary @Column(precision=12, scale=2) 指定浮点数据库字段的算术精度。
bonus @Column(precision=12, scale=2) 指定浮点数据库字段的算术精度。
commission @Column(precision=12, scale=2) 指定浮点数据库字段的算术精度。
addr @OneToOne(cascade = CascadeType.ALL) 指定这个表和另一个映射表中的 Address 对象之间的关系。cascade=ALL 指明添加、修改、删除和刷新应当全部级联到关联表。
hiredate @Temporal(TemporalType.DATE) 指定字段是日期字段(而不是时间或时间戳字段)。
birthdate @Temporal(TemporalType.DATE) 指定字段是日期字段(而不是时间或时间戳字段).



请注意 Employee 和 Address 实例是一对一的关联(由 @OneToOne(casecade=CascadeType.ALL) 注释指定)。这个注释指定 Employee 对象上的所有实体管理器操作应级联到关联的 Address 对象。这意味着新增任何 Employee 记录都会在 RDBMS 中创建一个对应的 Address 记录。它还意味着对 Employee 记录所做的任何更新或删除,都会级联到关联的 Address 记录。这是经常在 RDBMS 中发现的级联删除完整性约束的扩展。在实践中,会发现在执行级联选项时,Java 代码被极大地简化了:不再需要协调跨越多个表的操作。

有注释的 Employee 源代码是 JPA 实体管理器管理 Employee 或 Address 对象的持久性实例的蓝本。您应发现,与容易出错的编写创建和操作实际 RDBMS 表的实际代码相比,这样的注释要简单得多。。

Employee 对象的表可以选择性地基于上述注释生成,其模式类似于清单 7:

清单 7. 员工 POJO 的同等数据库模式
CREATE TABLE EMPLOYEE (
      EMPID INTEGER NOT NULL,
      EDLEVEL INTEGER,
      SEX CHAR(1),
      FIRSTNME VARCHAR(255),
      SALARY DOUBLE,
      LASTNAME VARCHAR(255),
      BONUS DOUBLE,
      JOB VARCHAR(8),
      COMM DOUBLE,
      MIDINIT VARCHAR(255),
      HIREDATE DATE,
      EMPNO VARCHAR(6),
      BIRTHDATE DATE,
      PHONENO VARCHAR(255),
      ADDR_ID INTEGER,
      PRIMARY KEY (EMPID),
      FOREIGN KEY (ADDR_ID)
   )
;
基于字符串的字段的长度

请注意,任何没有用 @Column(length=?) 注释的 String 字段默认是 VARCHAR(255)。这可以表示短字段浪费的存储空间(每行)。在生产场景中,可能想更严格地控制底层管理的表的空间分配。

请比较清单 7 与 清单 6 中注释的 Employee,查看注释在对 Spring 引擎创建的表上的效果。

如果想得到所有可用的 JPA 注释的详细解释和说明,请参考 JSR 220,它是企业 JavaBeans 3.0 规范的最终发行文档(请参阅 参考资料 )。


Address 对象的 JPA 注释

Address POJO 以相似的方式进行注释,如清单 8 所示:

清单 8. Address POJO 的 JPA 注释
package com.ibm.dw.spring2;

import javax.persistence.Column;
...

@Entity
public class Address {

   @Id
   @GeneratedValue(strategy = GenerationType.TABLE)
   private long id;

   @Column(name = "NUM")
   private int number;

   @Column(name = "STNAME", length=25)
   private String street;
...
迄今为止,所有注释对您来说都有着某些意义。毫不奇怪,清单 8 中的注释生成的表具有如清单 9 所示的模式:

清单 9. Address POJO 等价的数据库模式
CREATE TABLE ADDRESS (
      ID INTEGER NOT NULL,
      NUM INTEGER,
      STNAME VARCHAR(25),
      PRIMARY KEY (ID)
   )
;
Spring 2 和 Java EE 5 的关系

JPA 持久性是 EJB 3.0 的一部分,后者又是 Java EE 5 规范的一部分,这意味着所有兼容的 Java EE 5 服务器(商业的、开放源码的或其他)都有符合规范的实现。这实际上就保证了在不久的将来,将有健壮、高质量的 JPA 实现可以使用。

请注意,虽然 Spring 2 利用了 EJB 3.0 规范的 JPA 持久性,但未强迫 Spring 2 的用户利用 EJB 3.0 或 Java EE 5 规范的其他元素。

从最初起,JPA 就被设计成可以在传统 EJB 容器之外独立使用。作为一个具体的示例,本教程中的应用程序受益于 JPA ,但绝对不是 EJB 3.0 或 Java EE 5 应用程序。

fig05.gif
快乐渡过每一天,减肥坚持每一天