第 1 部分: OpenJPA 与 EJB 3.0
几乎所有的企业应用都需要持久化数据,没有数据持久化需求的企业应用在现在的市场环境下几乎是不可能出现的。由于关系型数据的普及,通常我们提到数据持久化时,一般指的是将数据持久化到关系型数据库中。关系型数据是一种结构化的数据管理方式,开发者只能通过 SQL 来操作数据库。
Java 语言天生就是一门面向对象的编程语言,在 Java 的世界中,被处理的内容都被组织成一个一个的对象,对象和对象之间存在着继承、引用关系,这样的关系无法通过简单的方式直接反应到关系型数据库中。因此在关系型数据库与面向对象之间便存在着阻抗失谐(impedance mismatch)。
EJB 3.0 规范由三部分组成:EJB3.0 Simplified API、EJB 核心规范(EJB Core Contracts and Requirements)和 JPA(Java Persistence API)。
JPA 标准中引入了新的实体概念,每一个实体都是一个普通的 Java 类,不需要继承任何其他的接口或者扩展某个指定类,这个 Java 类必须使用 javax.persistence.Entity 进行注释。JPA 标准中还提供了包括 javax.persistence.Table、javax.persistence.Id 等在内的多个注释,用于完成实体和数据库之前的映射。JPA 中引入了新的查询语言 JPQL(Java Persistence Query Language),JPQL 允许开发者采用面向对象的查询语言来查找实体,这些实体持久化在关系型的数据库中,”select a from Animal a where a.name=’a’”是一个 JPQL 的例子。其中的 Animal 是一个 Java 类,而不是关系型数据库中的一个表或者视图。除了简单的查询功能之外,JPQL 中还能够支持 Group、Order 等通常只有 SQL 才能提供的高级功能。JPA 标准中还规定了在 Java EE 环境中和非 Java EE 环境中使用 JPA 时的差异,以及 Java EE 环境中容器的职责等。
JPA 中定义一套类和接口用于实现持久化管理和对象/关系的映射,下面这张图中显示了 JPA 的主要组件以及它们之间的相互关系。
清单 1 在非 Java EE 环境使用 JPA 接口的例子
1. /*
2. * Persistence 类获取 EntityManagerFactory 实例;
3. * 一般 EntityManagerFactory 实例被缓存起来重复使用,
4. * 避免重复创建 EntityManagerFactory 实例引起的性能影响
5. */
6. EntityManagerFactory factory =
7. Persistence.createEntityManagerFactory (“mysql”);
8.
9. // 从 EntityManagerFactory 实例 factory 中获取 EntityManager
10. EntityManager em = factory.
11. createEntityManager(PersistenceContextType.EXTENDED);
12.
13. // 实体的更新需要在事务中运行
14. EntityTransaction tx = em.getTransaction ();
15. tx.begin ();
16.
17. // 查找所有公司中的女性雇员
18. Query query = em.createQuery ("select e from Employee e "
19. + " where e.sex = 'femail'");
20. List results = query.getResultList ();
21.
22. // 给所有女性雇员增加半天假期
23. for (Object res : results){
24. Employee emp = (Employee) res;
25. emp.setHoliday (emp.getHoliday () +0.5);}
26.
27. // 提交事务(持久化所有更新)
28. tx.commit ();
29. em.close ();
30. factory.close ();
下面的代码显示了在 EJB 容器中开发 JPA 应用时的接口使用情况,由于容器中的 EntityManager 是注入的,事务也是声明式的,因此在容器中完成上面的业务逻辑要简单得多。
清单 2 在容器中运行的 JPA 例子
1. /*
2. * 在容器中运行 JPA 应用时,EntityManager 接口的实例”em”
3. * 是通过 @Resource 注释注入的。事务也通常是声明式的。
4. */
5. // 查找所有公司中的女性雇员
6. Query query = em.createQuery ("select e from Employee e "
7. + " where e.sex = 'femail'");
8. List results = query.getResultList ();
9.
10. // 给所有女性雇员增加半天假期
11. for (Object res : results){
12. Employee emp = (Employee) res;
13. emp.setHoliday (emp.getHoliday () +0.5);}
JPA 标准制定过程中充分吸收了目前已经出现的所有持久化技术的所有优点,摒弃了它们存在的局限,使 JPA 在简单易用、查询能力等方面表现突出。
表 1 持久化技术的优缺点
支持内容: 序列化 JDBC ORM ODB EJB 2.X JDO EJB 3 (JPA)
Java 对象 Yes No Yes Yes Yes Yes Yes
高级 OO 原理 Yes No Yes Yes No Yes Yes
事务完整性 No Yes Yes Yes Yes Yes Yes
并发 No Yes Yes Yes Yes Yes Yes
大数据集 No Yes Yes Yes Yes Yes Yes
现有 Schema No Yes Yes No Yes Yes Yes
关系型和非关系型数据存储 No No No No Yes Yes No
查询 No Yes Yes Yes Yes Yes Yes
严格的标准 / 可移植 Yes No No No Yes Yes Yes
简单易用 Yes Yes Yes Yes No Yes Yes
OpenJPA 是 Apache 组织提供的开源项目,它实现了 EJB 3.0 中的 JPA 标准,为开发者提供功能强大、使用简单的持久化数据管理框架。OpenJPA 封装了和关系型数据库交互的操作,让开发者把注意力集中在编写业务逻辑上。OpenJPA 可以作为独立的持久层框架发挥作用,也可以轻松的与其它 Java EE 应用框架或者符合 EJB 3.0 标准的容器集成。
除了对 JPA 标准的支持之外,OpenJPA 还提供了非常多的特性和工具支持让企业应用开发变得更加简单,减少开发者的工作量,包括允许数据远程传输/离线处理、数据库/对象视图统一工具、使用缓存(Cache)提升企业应用效率等。
第 2 部分: 开发第一个 Open JPA 应用
由于 OpenJPA 是基于注释机制的框架,这需要用到 JDK 5.0 或者以上版本,因此请确保在工作机器上已经下载和安装了 JDK 5.0(参见 参考资源)。
演示过程中,我们需要一个数据库作为对象持久化的目标数据库。出于简单和方便的考虑,我们选择采用 MySQL 数据库,因此您需要下载 MySQL 数据库安装包(请见 参考资源)。如果需要 MySQL 很好的支持中文字符(GB2312 编码),您可选择下载 MySQL 5.0 或者以上版本,安装的时候选择数据库字符集为 GB2312 即可。
2. 下载、安装 OpenJPA
OpenJPA 的最新稳定版本是 Open JPA 0.97(下载链接见 参考资源)。OpenJPA 的安装比较简单,只需要将下载的压缩文件解压即可。我们假设将下载的压缩包解压到 C:/OpenJPA 目录下(本文后面将使用 %OPENJPA_HOME% 来引用这个目录)。
本文的演示说明均基于 Windows XP 平台,JDK 版本为 1.5.0_11,数据库服务器为 MySQL 5.0,和演示代码位于同一台机器上。所有演示用例对应的 MySQL 数据库为“openjpa”,访问 MySQL 的用户名和密码也均为“openjpa”。
在 C: 盘根目下创建名为 OpenJPAExamples 的目录,我们所有的类文件和配置文件都将放在这个目录下。
2. 编写 JPA 配置文件
在 C:\OpenJPAExamples 新建 META-INF 目录,随后在该目录下创建 persistence.xml 文件。persistence.xml 是 OpenJPA 的配置文件,提供 OpenJPA 容器初始化、运行所需要的配置信息。比如 OpenJPA 的事务策略、数据库的连接信息等。清单 1 中是我们演示实例中所使用的 persistence.xml 文件的内容。
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <!-- persistence-unit的name属性提供了创建EntityManagerFacotry时的 关键字,transaction-type则指定了使用的事务管理类型,这里使 用‘RESOURCE_LOCAL’参数表示使用本地事务 --> <persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL"> <!-- JPA的提供类,OpenJPA的设置如下,如果使用其它的JPA实现,这里 的内容需要修改成相应的提供类 <provider> org.apache.openjpa.persistence.PersistenceProviderImpl </provider> --> <!-- OpenJPA容器中管理的实体类列表 --> <class>org.vivianj.openjpa.entity.Animal</class> <!-- OpenJPA容器访问数据库的参数 --> <properties> <property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost/openjpa" /> <property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver" /> <property name="openjpa.ConnectionUserName" value="openjpa" /> <property name="openjpa.ConnectionPassword" value="openjpa" /> </properties> </persistence-unit> </persistence>
3. 创建实体类
新创建实体类 Animal,为了说明的简单,该类只有两个属性:id 和 name,其中 id 字段代表的是编号(编号由 MySQL 数据库自动生成),name 属性表示名称。实体类的全部代码见清单 2,请注意其中的黑体部分,它们是 JPA 中定义的注释(Annotation),Animal 中仅仅用到了非常少的几个注释,了解其它更多注释的细节请参看 OpenJPA 的帮助文档。
package org.vivianj.openjpa.entity; import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /* * 使用Entity注释表示该类是实体类,它的name属性是该实体在查询中对应的 * 唯一名称,如果没有提供Entity的name属性,默认的name属性是类名。 */ @Entity public class Animal { // 编号 /* 使用Id注释表示该字段是标识字段 */ /* * 使用GeneratedValue注释定义该标识字段的产生方式,我们的演示系统中 * id由MySQL数据库字段自动生成,因此选择GenerationType.IDENTITY, * 另外的可选方式包括GeneratorType.AUTO,GenerationType.SEQUENCE, * GenerationType.TABLE。 */ @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; // 名称 /* * Basic注释表示该属性是持久化属性,没有使用Basic注释的属性将不会被持久化到数据库中 */ @Basic private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
4. 编译实体类
5. 增强(Enhance)实体类
Enhance 是使用 OpenJPA 必须的一个步骤,所谓 Enhance 是指使用 OpenJPA 提供的工具 PCEnhancer(org.apache.openjpa.enhance.PCEnhancer)对实体类进行处理的过程,被 Enhance 过的实体类能够支持性能优化、懒惰式装载等高级特性。
OpenJPA 支持在编译时、部署时、运行时增强实体类,我们以编译时为例了解如何增强实体类。我们使用下面的系列语句完成实体类的增强(Enhance)。
1. C:\OpenJPAExamples>set OPENJPA_HOME=C:\OpenJPA 2. C:\OpenJPAExamples>set classpath= %OPENJPA_HOME%\lib\commons-collections-3.2.jar; %OPENJPA_HOME%\lib\commons-lang-2.1.jar; %OPENJPA_HOME%\lib\commons-logging-1.0.4.jar; %OPENJPA_HOME%\lib\commons-pool-1.3.jar; %OPENJPA_HOME%\lib\geronimo-j2ee-connector_1.5_spec-1.0.1.jar; %OPENJPA_HOME%\lib\geronimo-jms_1.1_spec-1.0.1.jar; %OPENJPA_HOME%\lib\geronimo-jta_1.0.1B_spec-1.0.1.jar; %OPENJPA_HOME%\lib\persistence-api-1.0.jar; %OPENJPA_HOME%\lib\serp-1.11.0.jar; %OPENJPA_HOME%\openjpa-all-0.9.6-incubating.jar 3. C:\OpenJPAExamples>java org.apache.openjpa.enhance.PCEnhancer Animal.java
从中我们可以看到 Animal 被增加了新的 org.apache.openjpa.enhance.PersistenceCapable 接口,而该接口声明的方法都是和实体创建、状态保持等相关的。事实上,OpenJPA 通过 Enhance 过程修改我们的实体类,扩充了实体类的能力,从而实现性能优化、懒惰式装载等高级特性。
1. public class org.vivianj.openjpa.entity.Animal 2. extends java.lang.Object 3. implements org.apache.openjpa.enhance.PersistenceCapable{ 4. protected transient org.apache.openjpa.enhance.StateManager pcStateManager; 5. protected transient byte pcFlags; 6. static java.lang.Class class$Ljava$lang$String; 7. static java.lang.Class class$Lorg$vivianj$openjpa$entity$Animal; 8. public org.vivianj.openjpa.entity.Animal(); 9. public int getId(); 10. public void setId(int); 11. public java.lang.String getName(); 12. public void setName(java.lang.String); 13. static final {}; 14. static java.lang.Class class$(java.lang.String); 15. protected void pcClearFields(); 16. public org.apache.openjpa.enhance.PersistenceCapable pcNewInstance( 17. org.apache.openjpa.enhance.StateManager, java.lang.Object, boolean); 18. public org.apache.openjpa.enhance.PersistenceCapable pcNewInstance( 19. org.apache.openjpa.enhance.StateManager, boolean); 20. protected static int pcGetManagedFieldCount(); 21. public void pcReplaceField(int); 22. public void pcReplaceFields(int[]); 23. public void pcProvideField(int); 24. public void pcProvideFields(int[]); 25. protected void pcCopyField(org.vivianj.openjpa.entity.Animal, int); 26. public void pcCopyFields(java.lang.Object, int[]); 27. public java.lang.Object pcGetGenericContext(); 28. public java.lang.Object pcFetchObjectId(); 29. public boolean pcIsDeleted(); 30. public boolean pcIsDirty(); 31. public boolean pcIsNew(); 32. public boolean pcIsPersistent(); 33. public boolean pcIsTransactional(); 34. public boolean pcSerializing(); 35. public void pcDirty(java.lang.String); 36. public org.apache.openjpa.enhance.StateManager pcGetStateManager(); 37. public java.lang.Object pcGetVersion(); 38. public void pcReplaceFlags(); 39. public synchronized void pcReplaceStateManager( 40. org.apache.openjpa.enhance.StateManager) 41. throws java.lang.SecurityException; 42. public void pcCopyKeyFieldsToObjectId( 43. org.apache.openjpa.enhance.FieldSupplier, java.lang.Object); 44. public void pcCopyKeyFieldsToObjectId(java.lang.Object); 45. public void pcCopyKeyFieldsFromObjectId( 46. org.apache.openjpa.enhance.FieldConsumer, java.lang.Object); 47. public void pcCopyKeyFieldsFromObjectId(java.lang.Object); 48. public java.lang.Object pcNewObjectIdInstance(java.lang.Object); 49. public java.lang.Object pcNewObjectIdInstance(); 50. public java.lang.Boolean pcIsDetached(); 51. public java.lang.Object pcGetDetachedState(); 52. public void pcSetDetachedState(java.lang.Object); 53. }
6. 将新创建的实体类注册到 OpenJPA 容器中
1. <persistence …> 2. <persistence-unit …> 3. … 4. <class>org.vivianj.openjpa.entity.Animal</class> 5. … 6. </persistence-unit> 7. </persistence>
7. 准备数据库
8. 保持实体类和数据库表结构一致性
OpenJPA 中提供了专门的 MappingTool(org.apache.openjpa.jdbc.meta.MappingTool)工具协助开发者保持实体类和数据库表结构之间的一致性。MappingTool 工具能够自动的根据实体类中提供的注释(Annotation),识别出对象、对象之间的继承/包含等关系以及如何在关系型数据库中处理这些关系的设置,自动保证实体类和数据库之间的一致性,开发者也可以选择使用 OpenJPA 生成创建数据库所需要的 SQL 语句,然后手动的保持实体类和数据库之间的一致性。
可以使用下面的命令语句直接通过 MappingTool 在数据库中创建 OpenJPA 应用所需要的数据库表。
java org.apache.openjpa.jdbc.meta.MappingTool Animal.java |
命令执行完成后,我们通过 MySQL 的客户端访问 OpenJPA 数据库,可以发现里面已经创建了名为“animal”的数据表。如果开发者不想让 MappingTool 自动维护 OpenJPA 和数据库之间的映射,而只是想让 MappingTool 协助生成创建数据库所需要的数据库表定义文件(DDL),可以使用下面的命令语句,其中的参数 -sql 的值就是生成数据库表定义文件的位置。
比如要将生成的数据库表定义文件写入当前文件夹下 animal.sql 文件的命令如下:
java org.apache.openjpa.jdbc.meta.MappingTool –sql animal.sql Animal.java |
[注] 要成功执行 MapptingTool 工具,必须先将数据库的 JDBC 驱动放入到 classpath 中,否则会得到一个 org.apache.openjpa.util.StoreException 类型的异常信息。
访问 OpenJPA 容器中管理的持久化对象所需要的基本步骤如下:
获取 OpenJPA 容器中的 EntityManagerFactory
EntityManagerFactory 是 OpenJPA 中创建 EntityManager 的工厂,要想得到 EntityManager,就必须获取的相应的 EntityManagerFactory。
EntityManagerFactory 通过 Persistence 的静态方法 createEntityManagerFactory 获得,该方法是一个重载的方法,支持不同的输入参数。最常用的是使用一个字符串作为参数,该字符串的内容是 EntityManagerFactory 的标识名,该标识名需要和 persistence.xml 文件中的 persistence-unit 元素的 name 属性保持一致。可以使用 null 作为 createEntityManagerFactory 方法的参数,这时候将使用 persistence.xml 中没有提供 name 属性的 persistence-unit 元素提供的参数来配置 EntityManagerFactory。
下面的代码段可以从 OpenJPA 容器中获取名为“mysql”的 EntityManagerFactory。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("mysql");
而它对应的 persistence.xml 文件中,应该有相应的 name 属性为“mysql”的 persistence-unit 元素,下面的配置是一个示例。
1. <persistence xmlns="http://java.sun.com/xml/ns/persistence"
2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3. version="1.0">
4. <persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL">
5. …
6. </persistence-unit>
7. </persistence>
为了提升软件的执行效率,我们通常选择在某一个范围内缓存 EntityManagerFactory 对象。在 EntityManagerFactory 使用完后,我们需要调用它的 close 方法来释放相应的资源。
获取 EntityManager
要访问 OpenJPA 容器中的实体类,必须首先获得相应的 EntityManager。可以通过 EntityManagerFactory 对象的 createEntityManager() 方法来获取 EntityManager 对象。
EntityTransaction的启动(提交)
对于 OpenJPA 容器中的持久化对象的创建、修改、删除操作必须在代码中显式的处理事务,而对于查询操作则不需要在代码中显式的处理事务。JPA 应用中的事务由 EntityTransaction 接口处理,EntityTransaction 可以直接通过 EntityManager 对象的 getTransaction 方法获得。我们可以调用 EntityTransaction 的 begin(commit) 方法显式的启动(提交)事务。获取、使用 EntityTransaction 的实际例子请参考 清单 4 AnimalDAOImpl.java 中的源代码。
Query 接口和 JPQL 查询语言
要查询 EntityManager 中符合条件的对象列表,需要借助 Query 接口和 JPQL。Query 接口可以直接通过 EntityManager 的 createQuery 方法获得。Query 对象目前支持 JPQL 和原生态 SQL 两种方式。
JPQL 是 OpenJPA 中支持的对象查询语言,是 EJB SQL 的一种实现。通过 JPQL,我们可以用一种面向对象的方式编写持久化对象的查询条件。比如要查找编号为“1”的 Animal 对象,我们可以使用下面的 JPQL 语法:
select animal form Animal animal where animal.id=1)
关于 JPQL 的更多信息请参考 OpenJPA 的帮助文档。
import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; import org.vivianj.openjpa.entity.Animal; public class AnimalDAOImpl { /** * removeAnimal方法可以从数据库中删除指定编号的Animal对象 * * @param id * Animal对象的编号 */ public void removeAnimal(int id) { // 获取EntityManagerFactory EntityManagerFactory factory = Persistence .createEntityManagerFactory("mysql"); // 获取EntityManager EntityManager em = factory.createEntityManager(); // 开始事务处理 em.getTransaction().begin(); // 使用Query删除对象 em.createQuery("delete from Animal animal where animal.id=" + id) .executeUpdate(); // 我们还可以选择通过Query对象来完成 /* * // 从EntityManager中查询到符合条件的对象 Animal animal = * em.find(Animal.class,id); // 调用EntityManager的remove方法删除对象 * em.remove(animal); */ // 提交事务 em.getTransaction().commit(); // 关闭EntityManager em.close(); // 关闭EntityManagerFactory factory.close(); } /** * findAnimalsByName 通过输入的name内容模糊查找符合条件的Animal对象列表 * * @param name * Animal对象的name * @return 符合模糊查找条件的Animal对象列表 */ public List<Animal> findAnimalsByName(String name) { // 获取EntityManagerFactory EntityManagerFactory factory = Persistence .createEntityManagerFactory("mysql"); // 获取EntityManager EntityManager em = factory.createEntityManager(); /* * 通过EntityManager的createQuery方法获取Query对象 * createQuery方法的参数是JPQL查询语句,JPQL语句的语法请参考OpenJPA的帮助文档. * * 由于查询不需要事务的支持,因此Query操作的前后没有出现begin、commit方法的调用 * */ Query q = em .createQuery("select animal from Animal animal where animal.name like '%" + name + "%' ESCAPE ''"); List<Animal> lstRet = q.getResultList(); // 关闭EntityManager em.close(); // 关闭EntityManagerFactory factory.close(); return lstRet; } /** * getAnimalByPrimaryKey 方法可以查找符合条件的单个Animal对象, 如果不存在对应的Animal对象将返回null * * @param id * Animal对象的编号 * @return 唯一符合条件的Animal对象 * */ public Animal getAnimalByPrimaryKey(int id) { // 获取EntityManagerFactory EntityManagerFactory factory = Persistence .createEntityManagerFactory("mysql"); // 获取EntityManager EntityManager em = factory.createEntityManager(); // 查找符合条件的对象 Animal animal = em.find(Animal.class,id); // 关闭EntityManager em.close(); // 关闭EntityManagerFactory factory.close(); return animal; } /** * 将对象持久化到数据库中 * * @param animal * 需要被持久化的对象 */ public void persistAnimal(Animal animal) { // 获取EntityManagerFactory EntityManagerFactory factory = Persistence .createEntityManagerFactory("mysql"); // 获取EntityManager EntityManager em = factory.createEntityManager(); // 开始事务处理 em.getTransaction().begin(); // 持久化对象 em.persist(animal); // 提交事务 em.getTransaction().commit(); // 关闭EntityManager em.close(); // 关闭EntityManagerFactory factory.close(); } /** * 将Animal对象被更新的属性持久化到数据库中 * * @param animal * 被更新过的Animal对象 */ public void updateAnimal(Animal animal) { // 获取EntityManagerFactory EntityManagerFactory factory = Persistence .createEntityManagerFactory("mysql"); // 获取EntityManager EntityManager em = factory.createEntityManager(); // 开始事务处理 em.getTransaction().begin(); // 更新持久化对象状态 em.merge(animal); // 提交事务 em.getTransaction().commit(); // 关闭EntityManager em.close(); // 关闭EntityManagerFactory factory.close(); } }
第 3 部分: 实体继承
Animal、Fish、Dog 三个类的所有对象实例都被保存在 Animal 数据表中,该表将会有 5 个属性,其中 ID,NAME 字段对应 ANIMAL 类的两个属性,ID、NAME、SEX 对应 Dog 类的属性,ID、NAME、STERRITORY 对应 Fish 类的属性。DTYPE 是 OpenJPA 加入的字段,用于确定当前记录的实际类类型,在这里例子中,它的内容是“ANIMAL”、“FISH”或者是“DOG”。
3. 类和子类分别保存在不同的数据库表中,子类中不保存父类中已有的属性,仅通过主键进行关联
这种情况下,父类和子类对应不同的表,但是子类对应的表中不再保存父类对应表中已经存在的字段信息,两个表之间通过关键字段关联起来,也就是数据库技术中通常所说的外健。这种实现方式是最理想化的一种,既能够处理对象之间的继承,又满足了关系数据库中对于设计范式的要求。
这三种方式的处理对于开发者而言是透明的,无论选择哪一种,仅仅影响数据在关系数据库中的保存方式,对于开发者而言,只需要按照面向对象的方式操作对象既可,OpenJPA 框架在处理持久化操作的时候,会动态地判断当前对象的实际类类型(后期绑定),从而确定持久化到哪个表中。在一个企业应用的实现中,开发者可以根据需要选择这三种方式的一种或者几种来处理对象之间的继承关系。
OpenJPA 是一个基于注释的持久化框架,对持久化的大多数元信息都只需要为实体类提供相应的注释。开发者使用注释描述实体和数据库表之间的映射,也采用注释描述对象继承关系的持久化。javax.persistence.Inheritance 注释用来指定对象继承关系持久化的方式。它的 strategy 属性用于指定持久化对象继承关系在关系数据库中的表现形式,可选择项包括 SINGLE_TABLE、JOINED 和 TABLE_PER_CLASS。它们三个都是 javax.persistence.InheritanceType 中定义的常量。
strategy 设置为 SINGLE_TABLE 选项表示所有类及其子类保存在同一个数据库表中,对象的类型使用表中的特殊字段 DTYPE 进行识别。
strategy 设置为该选项表示每个类使用一个表。
strategy 设置为该选项表示父类和子类分别保存在不同的数据库表中,子类中不保存父类对应数据库表中已有的属性,仅通过主键进行关联。
javax.persistence.Inheritance 注释是类级别的注释。需要为每一个成为父类的实体类提供 javax.persistence.Inheritance 注释并且指定 strategy 属性。在同一个企业应用中,开发者可以根据实际情况选择这三种策略中的一种,或者是几种同时使用。
Animal.java, 继承关系的父类
1. package chapter04.entity; 2. import javax.persistence.Entity; 3. import javax.persistence.Id; 4. import javax.persistence.Inheritance; 5. import javax.persistence.InheritanceType; 6. 7. @Entity 8. @Inheritance(strategy = InheritanceType.JOINED) 9. public class Animal { 10. @Id 11. private int id; 12. 13. private String name; 14. 15. public Animal() { 16. } 17. 18. public Animal(int id, String name) { 19. this.id = id; 20. this.name = name; 21. } 22. 23. public int getId() { 24. return id; 25. } 26. 27. public void setId(int id) { 28. this.id = id; 29. } 30. 31. public String getName() { 32. return name; 33. } 34. 35. public void setName(String name) { 36. this.name = name; 37. } 38. 39. }
Fish.java, 继承关系中的子类
1. package chapter04.entity; 2. 3. import javax.persistence.Entity; 4. 5. @Entity 6. public class Fish extends Animal { 7. /* 鱼的活动范围,比如江、河、湖、海 */ 8. private String territory; 9. 10. public Fish() { 11. } 12. 13. public Fish(int id, String name, String territory) { 14. super(id, name); 15. this.territory = territory; 16. } 17. 18. public String getTerritory() { 19. return territory; 20. } 21. 22. public void setTerritory(String territory) { 23. this.territory = territory; 24. } 25. 26. }
Dog.java, 继承关系中的子类
1. package chapter04.entity; 2. 3. import javax.persistence.Entity; 4. 5. @Entity 6. public class Dog extends Animal { 7. /* 性别 */ 8. private String sex; 9. 10. public Dog() { 11. } 12. 13. public Dog(int id, String name, String sex) { 14. super(id, name); 15. this.sex = sex; 16. } 17. 18. public String getSex() { 19. return sex; 20. } 21. 22. public void setSex(String sex) { 23. this.sex = sex; 24. } 25. 26. }
持久化 Animal
1. // 通过 Persistence 创建 EntityManagerFactory 2. EntityManagerFactory factory = Persistence.createEntityManagerFactory( 3. "jpa-unit", System.getProperties()); 4. 5. // 从 EntityManagerFactory 中创建 EntityManager 6. EntityManager em = factory.createEntityManager(); 7. 8. // 开始持久化实体的事务 9. em.getTransaction().begin(); 10. 11. // 使用相同的方式持久化实体 12. em.persist(new Animal(1,"honey")); 13. 14. // 提交持久化实体的事务 15. em.getTransaction().commit(); 16. 17. // 关闭EntityManager 18. em.close();
当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 语句:
INSERT INTO Animal (id, name) VALUES (1, 'honey') |
持久化 Fish
1. // 通过 Persistence 创建 EntityManagerFactory 2. EntityManagerFactory factory = Persistence.createEntityManagerFactory( 3. "jpa-unit", System.getProperties()); 4. 5. // 从 EntityManagerFactory 中创建 EntityManager 6. EntityManager em = factory.createEntityManager(); 7. 8. // 开始持久化实体的事务 9. em.getTransaction().begin(); 10. 11. // 使用相同的方式持久化实体 12. em.persist(new Fish(2,"mermaid","SEA")); 13. 14. // 提交持久化实体的事务 15. em.getTransaction().commit(); 16. 17. // 关闭EntityManager 18. em.close();
由于 Fish 对象的属性保存在两个表中,因此当我们执行这段代码时,OpenJPA 会将它转化为对应的两条 SQL 语句:
INSERT INTO Animal (id, name) VALUES (2, 'mermaid') INSERT INTO Fish (id, territory) VALUES (2, 'SEA') |
持久化 Dog
持久化 Dog 对象和持久化 Fish 对象的过程几乎一样,区别是 persist 方法的参数变成了 Dog 对象。
em.persist(new Dog(3,"ba guai","MALE")); |
和持久化 Fish 对象时一样,Dog 对象的属性也保存在两个表中,因此当我们执行这段代码时,OpenJPA 会将它转化为对应的两条 SQL 语句:
INSERT INTO Animal (id, name) VALUES (3, 'ba guai') INSERT INTO Dog (id, sex) VALUES (3, 'MALE') |
获取所有 Animal 对象
1. // 通过 Persistence 创建 EntityManagerFactory 2. EntityManagerFactory factory = Persistence.createEntityManagerFactory( 3. "jpa-unit", System.getProperties()); 4. // 创建新的 EntityManager 5. EntityManager em2 = factory.createEntityManager(); 6. 7. // 查询所有 Animal 对象 8. Query q = em2.createQuery("select m from Animal m"); 9. 10. // 直接处理 Animal 对象,打印 Animal 对象的信息 11. for (Animal m : (List<Animal>) q.getResultList()) { 12. System.out.println("Animal Object:"); 13. System.out.println(" id:" + m.getId()); 14. System.out.println(" name:" + m.getName()); 15. } 16. 17. // 关闭 EntityManager 和 EntityManagerFactory 18. em2.close(); 19. factory.close();
当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 查询语句:
SELECT t0.id, t1.id, t2.id, t0.name, t1.sex, t2.territory FROM Animal t0 LEFT OUTER JOIN Dog t1 ON t0.id = t1.id LEFT OUTER JOIN Fish t2 ON t0.id = t2.id |
在查询结果返回后,OpenJPA 会将查询结果影射到相关的 Animal 对象上,整个过程是透明的,开发者只需要处理对象模型即可。
获取所有 Fish 对象
1. // 通过 Persistence 创建 EntityManagerFactory 2. EntityManagerFactory factory = Persistence.createEntityManagerFactory( 3. "jpa-unit", System.getProperties()); 4. // 创建新的 EntityManager 5. EntityManager em2 = factory.createEntityManager(); 6. 7. // 查询所有 Fish 对象 8. Query q1 = em2.createQuery("select fish from Fish fish"); 9. 10. // 打印 Fish 对象的信息 11. for (Fish fish : (List<Fish>) q1.getResultList()) { 12. System.out.println("Fish Object:"); 13. System.out.println(" id:" + fish.getId()); 14. System.out.println(" name:" + fish.getName()); 15. System.out.println(" territory:" + fish.getTerritory()); 16. } 17. 18. // 关闭 EntityManager 和 EntityManagerFactory 19. em2.close(); 20. factory.close();
当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 查询语句:
SELECT t1.id, t0.id, t1.name, t0.territory FROM Fish t0 INNER JOIN Animal t1 ON t0.id = t1.id |
获取所有 Dog 对象
获取 Dog 对象的过程和获取 Fish 对象的过程一致,开发者只需要将 Query 接口使用的 JPQL 语句改为“select dog from Dog dog
”。
Query q1 = em2.createQuery("select dog from Dog dog "); |
当我们执行这段代码时,OpenJPA 会将它转化为关系数据库对应的 SQL 查询语句:
SELECT t1.id, t0.id, t1.name, t0.sex FROM Dog t0 INNER JOIN Animal t1 ON t0.id = t1.id |
在查询结果返回后,OpenJPA 会将查询结果影射到相关的Fish对象上,整个过程是透明的,开发者只需要处理对象模型即可。
对象继承关系在关系数据库中的表现是对象持久化中难于实现的部分,OpenJPA 为开发者提供了一种透明的实现。在 OpenJPA 中提供了 SINGLE_TABLE、JOINED 和 TABLE_PER_CLASS 三种实现方式处理实体继承关系,开发者需要做的仅仅是为实体类提供 javax.persistence.Inheritance 注释,同时设置它的 strategy 属性,确定使用哪种对象继承关系即可,和关系数据库交互的部分由 OpenJPA 框架完成。