1 Overview
Apache OpenJPA是JPA规范的一个实现,它既可以用于POJO的持久层,也可以被集成到EJB3.0兼容的容器中或者其它轻量级的框架中。在Apache Geronimo 2.0 版本中通过OpenEJB使用了OpenJPA。在WebLogic和WebShpere中也采用了OpenJPA。目前OpenJPA的最新版本是1.0.2。在OpenJPA中大量使用了generic和annotation,因此需要使用1.5以上版本的JDK。
以下是JPA中使用的主要组件:
以下是使用JPA的一个例子:
EntityManagerFactory factory = Persistence.createEntityManagerFactory(null); EntityManager em = factory.createEntityManager(PersistenceContextType.EXTENDED); EntityTransaction tx = em.getTransaction(); tx.begin(); Query query = em.createQuery("select e from Employee e where e.division.name = 'Research' AND e.avgHours > 40"); List results = query.getResultList (); for (Object res : results) { Employee emp = (Employee) res; emp.setSalary(emp.getSalary() * 1.1); } tx.commit(); em.close(); factory.close();
2 Entity
JPA 区分两种不同的persistent class: entity classes 和 embeddable classes. EntityManager可以通过persistent identity来查询entity 实例,或者在Query中指定条件来查询entity实例。在另一方面,embedded实例没有persistent identity,也不能直接从EntityManager或者Query的查询中得到。
2.1 Restrictions on Persistent Classes
在编写persistent class的时候,不需要继承任何父类或者实现任何接口。 但是persistent class需要一个无参构造函数。如果需要的话,OpenJPA的enhancer也可以创建一个protected 无参构造函数,所以当使用enhancer的时候,persistent class可以不提供无参构造函数。OpenJPA支持final classes 和 final methods。
所有的entity classes必须提供一个或者多个字段来组成一个persistent identity。你可以为persistent class提供一个version字段,用来检测并发修改,这个字段必须是整数型的(例如int,Long)或者是java.sql.Timestamp。version字段应该是不可变(immutable)的。除了version字段以外,OpenJPA也支持通过其它的方式检测并发修改。
JPA支持persistent class的继承,但是persistent class不能继承自一些包含native实现的系统类,例如:java.net.Socket 和 java.lang.Thread。如果一个persistent class继承自non-persistent class,那么non-persistent class中的字段不能被持久化。具有继承关系的所有类必须有相同的identity type。
2.2 Entity Identity
Java中有两种判断object identity的方式:使用==操作符判断同一个JVM中两个引用的值是否相等,或者说指向相同对象;使用equals 方法来判断两个对象是否满足定制的相等条件。JPA中引入了另外一种判断object identity的方式,称为entity identity或者 persistent identity。Entity identity被封装到持久对象的identity字段中,如果两个相同类型的entities拥有相同的identity字段,那么这两个entities代表datastore中相同的状态。Identity字段必须是以下类型:primitives、primitive wrappers、 Strings、Dates、Timestamps或者embeddable types。
当你处理single persistence context的时候,可以不必通过比较identity字段来判断这些entities是否代表datastore中相同的状态,而是可以通过==操作符。JPA要求每一个persistence context中只能保持一个object来代表每一个datastore record,因此entity identity相当于引用相等。
2.2.1 Identity Class
如果entity 中只有一个identity字段,那么可以将这个字段用作所有EntityManager APIs的identity object;否则必须提供一个identity class,它必须符合以下条件:
以下是个使用多个identity字段的entity class的例子:
import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; @Entity @IdClass(Magazine.MagazineId.class) public class Magazine { @Id private String isbn; @Id private String title; public String toString() { StringBuffer sb = new StringBuffer(); sb.append("isbn: " + isbn); sb.append(", title: " + title); return sb.toString(); } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public static class MagazineId { // each identity field in the Magazine class must have a // corresponding field in the identity class private String isbn; private String title; public MagazineId() { } public MagazineId(String isbn, String title) { this.isbn = isbn; this.title = title; } /** * Equality must be implemented in terms of identity field * equality, and must use instanceof rather than comparing * classes directly (some JPA implementations may subclass the * identity class). */ public boolean equals(Object obj) { if(this == obj) { return true; } if(!(obj instanceof MagazineId)) { return false; } MagazineId rhs = (MagazineId)obj; boolean b1 = (isbn == rhs.isbn || (isbn != null && isbn.equals(rhs.isbn))); boolean b2 = (title == rhs.title || (title != null && title.equals(rhs.title))); return b1 && b2; } /** * Hashcode must also depend on identity values. */ public int hashCode(){ int h1 = (isbn == null ? 0 : isbn.hashCode()); int h2 = (title == null ? 0 : title.hashCode()); return h1 ^ h2; } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("isbn: " + isbn); sb.append(", title: " + title); return sb.toString(); } } }
除了手工编写Id class之外,也可以通过ApplicationIdTool自动生成Id class,例如:
java org.apache.openjpa.enhance.ApplicationIdTool -s Id Magazine.java
关于其可选的命令行参数,请参考Apache OpenJPA的user guide。
如果觉得在命令行上执行命令比较麻烦,也可以在程序里直接调用其main方法,如下:
String suffix = "Id"; String java = "./src/com/example/entity/Product.java"; ApplicationIdTool.main(new String[]{"-d", "./src/", "-s", suffix, java});
关于如何在类的继承体系中编写identity class,请参考Apache OpenJPA的user guide。
2.3 Lifecycle Callbacks
经常需要在persistent object 生命周期的不同阶段实施不用的动作。JPA包含了多种不同的callback 方法来监控persistent object。这些callback方法可以在persistent classes 中定义,也可以在non-persistent listener classes 中定义。
2.3.1 Callback Methods
每一个persistence event都有相关的callback方法标记,如下:
除了属性存取方法外,任何没有参数的方法都可以用以上annotation标记。一个方法也可以使用多个annotation标记。以下是个persistent classes的例子
/** * Example persistent class declaring our entity listener. */ @Entity public class Magazine { @Transient private byte[][] data; @ManyToMany private List<Photo> photos; @PostLoad public void convertPhotos() { data = new byte[photos.size()][]; for (int i = 0; i < photos.size(); i++) data[i] = photos.get(i).toByteArray(); } @PreDelete public void logMagazineDeletion() { getLog().debug("deleting magazine containing" + photos.size() + " photos."); } }
也可以使用XML文件,如下:
<entity class="Magazine"> <pre-remove>logMagazineDeletion</pre-remove> <post-load>convertPhotos</post-load> </entity>
2.3.2 Entity Listeners
在persistent classes 中加入Lifecycle Callbacks并不总是理想,更优雅的方式是在 non-persistent listener class 中处理生命周期内的相关事件。Entity listener classes需要有一个无参的构造函数,callback 方法需要有个java.lang.Object 型的参数来指定激发事件的持久对象。Entities可以通过EntityListeners这个annotation来枚举listeners。以下是个Entity和Entity Listener的例子:
/** * Example persistent class declaring our entity listener. */ @Entity @EntityListeners({ MagazineLogger.class, ... }) public class Magazine { // ... // } /** * Example entity listener. */ public class MagazineLogger { @PostPersist public void logAddition(Object pc) { getLog ().debug ("Added new magazine:" + ((Magazine) pc).getTitle ()); } @PreRemove public void logDeletion(Object pc) { getLog().debug("Removing from circulation:" + ((Magazine) pc).getTitle()); } }
也可以在XML文件中定义Entity Listeners,如下:
<entity class="Magazine"> <entity-listeners> <entity-listener class="MagazineLogger"> <post-persist>logAddition</post-persist> <pre-remove>logDeletion</pre-remove> </entity-listener> </entity-listeners> </entity>
关于entity listeners的调用顺序,默认listener会最先被调用;接下来,父类的listeners会先调用,然后是子类的listeners;最后,如果entity上还有某个事件的多个callback方法,那么会按照这些callback方法在entity上声明顺序来调用。
可以使用以下两个class-level的annotation,以便在entity listeners的调用链上去掉默认listener和父类中定义的listener: