Terry.Li-彬

虚其心,可解天下之问;专其心,可治天下之学;静其心,可悟天下之理;恒其心,可成天下之业。

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  143 随笔 :: 344 文章 :: 130 评论 :: 0 Trackbacks

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中使用的主要组件:

  • Persistence: javax.persistence.Persistence类包含静态方法用于获得EntityManagerFactory对象。
  • EntityManagerFactory: javax.persistence.EntityManagerFactory类是创建EntityManager的工厂类。
  • EntityManager: javax.persistence.EntityManager是应用中主要使用的接口,它主要用于管理持久对象,也用于创建Query 接口。
  • Entity。Entity用于封装持久对象。
  • EntityTransaction: EntityTransaction 用于封装事务,javax.persistence.EntityTransaction和EntityManager之间是一对一的关系。
  • Query: javax.persistence.Query接口用于持久对象的查询。它支持Java Persistence Query Language (JPQL) 和 Structured Query Language (SQL)。
  • PersistenceException: JPA异常体系的根是PersistenceException,它继承于RuntimeException。OpenJPA中抛出的异常都实现了 org.apache.openjpa.util.ExceptionInfo接口,用于提供额外的信息。

   以下是使用JPA的一个例子:

Java代码
  1. EntityManagerFactory factory = Persistence.createEntityManagerFactory( null );  
  2. EntityManager em = factory.createEntityManager(PersistenceContextType.EXTENDED);  
  3. EntityTransaction tx = em.getTransaction();  
  4. tx.begin();  
  5. Query query = em.createQuery("select e from Employee e where e.division.name = 'Research' AND e.avgHours > 40");  
  6. List results = query.getResultList ();  
  7. for  (Object res : results) {  
  8.     Employee emp = (Employee) res;  
  9.     emp.setSalary(emp.getSalary() * 1.1);  
  10. }  
  11. tx.commit();  
  12. em.close();  
  13. 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,它必须符合以下条件:

  • The class must be public.
  • The class must be serializable.
  • The class must have a public no-args constructor.
  • The names of the non-static fields or properties of the class must be the same as the names of the identity fields or properties of the corresponding entity class, and the types must be identical.
  • The equals and hashCode methods of the class must use the values of all fields or properties corresponding to identity fields or properties in the entity class.
  • If the class is an inner class, it must be static.
  • All entity classes related by inheritance must use the same identity class, or else each entity class must have its own identity class whose inheritance hierarchy mirrors the inheritance hierarchy of the owning entity classes.

   以下是个使用多个identity字段的entity class的例子:

Java代码
  1. import  javax.persistence.Entity;  
  2. import  javax.persistence.Id;  
  3. import  javax.persistence.IdClass;  
  4.   
  5. @Entity   
  6. @IdClass (Magazine.MagazineId. class )  
  7. public   class  Magazine {  
  8.     @Id  
  9.     private String isbn;  
  10.       
  11.     @Id  
  12.     private String title;  
  13.   
  14.     public String toString() {  
  15.         StringBuffer sb = new StringBuffer();  
  16.         sb.append("isbn: " + isbn);  
  17.         sb.append(", title: " + title);  
  18.         return sb.toString();  
  19.     }  
  20.       
  21.     public String getIsbn() {  
  22.         return isbn;  
  23.     }  
  24.   
  25.     public void setIsbn(String isbn) {  
  26.         this.isbn = isbn;  
  27.     }  
  28.   
  29.     public String getTitle() {  
  30.         return title;  
  31.     }  
  32.   
  33.     public void setTitle(String title) {  
  34.         this.title = title;  
  35.     }  
  36.       
  37.     public static class MagazineId {  
  38.         // each identity field in the Magazine class must have a  
  39.         // corresponding field in the identity class  
  40.         private String isbn;  
  41.         private String title;  
  42.           
  43.         public MagazineId() {  
  44.         }  
  45.           
  46.         public MagazineId(String isbn, String title) {  
  47.             this.isbn = isbn;  
  48.             this.title = title;  
  49.         }  
  50.           
  51.         /** 
  52.          * Equality must be implemented in terms of identity field 
  53.          * equality, and must use instanceof rather than comparing  
  54.          * classes directly (some JPA implementations may subclass the 
  55.          * identity class). 
  56.          */   
  57.         public boolean equals(Object obj) {  
  58.             if(this == obj) {  
  59.                 return true;  
  60.             }  
  61.             if(!(obj instanceof MagazineId)) {  
  62.                 return false;  
  63.             }  
  64.               
  65.             MagazineId rhs = (MagazineId)obj;  
  66.             boolean b1 = (isbn == rhs.isbn || (isbn != null && isbn.equals(rhs.isbn)));  
  67.             boolean b2 = (title == rhs.title || (title != null && title.equals(rhs.title)));  
  68.             return b1 && b2;  
  69.         }  
  70.           
  71.         /** 
  72.          * Hashcode must also depend on identity values. 
  73.          */   
  74.         public int hashCode(){  
  75.             int h1 = (isbn == null ? 0 : isbn.hashCode());  
  76.             int h2 = (title == null ? 0 : title.hashCode());  
  77.             return h1 ^ h2;  
  78.         }  
  79.           
  80.         public String toString() {  
  81.             StringBuffer sb = new StringBuffer();  
  82.             sb.append("isbn: " + isbn);  
  83.             sb.append(", title: " + title);  
  84.             return sb.toString();  
  85.         }  
  86.     }  
  87. }  

   除了手工编写Id class之外,也可以通过ApplicationIdTool自动生成Id class,例如:

   java org.apache.openjpa.enhance.ApplicationIdTool -s Id Magazine.java
    关于其可选的命令行参数,请参考Apache OpenJPA的user guide。
    如果觉得在命令行上执行命令比较麻烦,也可以在程序里直接调用其main方法,如下:

Java代码
  1. String suffix =  "Id" ;  
  2. String java = "./src/com/example/entity/Product.java";  
  3. 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方法标记,如下:

  • PrePersist: 用这个annotation 标记的方法会在对象被持久化之前调用。这个方法可以用来为持久化对象设置主键。
  • PostPersist: 用这个annotation 标记的方法会在对象被持久化之后调用。这个方法可以用来在model被保存后更新view。
  • PostLoad: 用这个annotation 标记的方法会在对象中所有eagerly fetched 字段被加载后调用。这个方法通常用来根据加载后的持久化字段的值更初始化非持久字段的值。
  • PreUpdate: 用这个annotation 标记的方法会在对象中持久字段的值被flush到datastore前被调用。 这个方法通常用来根据非持久字段的值来设置持久字段。
  • PostUpdate: 用这个annotation 标记的方法会在对象中持久字段值的改变被保存到datastore后调用。这个方法可以用来清空应用层的过期数据。
  • PreRemove: 用这个annotation 标记的方法会在对象经事务标记成已删除状态前调用。在这个方法中访问持久字段是合法的。在这个方法中,可以基于复杂的条件级联删除其它对象,或者做其它的清理工作。
  • PostRemove: 用这个annotation 标记的方法会在对象被标记成已删除状态后调用。

   除了属性存取方法外,任何没有参数的方法都可以用以上annotation标记。一个方法也可以使用多个annotation标记。以下是个persistent classes的例子

Java代码
  1. /** 
  2.  * Example persistent class declaring our entity listener. 
  3.  */   
  4. @Entity   
  5. public   class  Magazine {  
  6.   
  7.     @Transient   
  8.     private byte[][] data;  
  9.   
  10.     @ManyToMany  
  11.     private List<Photo> photos;  
  12.   
  13.     @PostLoad  
  14.     public void convertPhotos() {  
  15.         data = new byte[photos.size()][];  
  16.         for (int i = 0; i < photos.size(); i++)  
  17.             data[i] = photos.get(i).toByteArray();  
  18.     }  
  19.   
  20.     @PreDelete  
  21.     public void logMagazineDeletion() {  
  22.         getLog().debug("deleting magazine containing" + photos.size()   
  23.             + " photos.");  
  24.     }  
  25. }  

   也可以使用XML文件,如下:  

Xml代码
  1. < entity   class = "Magazine" >   
  2.     <pre-remove>logMagazineDeletion</pre-remove>  
  3.     <post-load>convertPhotos</post-load>  
  4. </ 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的例子:

Java代码
  1. /** 
  2.  * Example persistent class declaring our entity listener. 
  3.  */   
  4. @Entity   
  5. @EntityListeners ({ MagazineLogger. class , ... })  
  6. public   class  Magazine {  
  7.   
  8.     // ... //  
  9. }  
  10.   
  11.   
  12. /** 
  13.  * Example entity listener. 
  14.  */   
  15. public   class  MagazineLogger {  
  16.   
  17.     @PostPersist  
  18.     public void logAddition(Object pc) {  
  19.         getLog ().debug ("Added new magazine:" + ((Magazine) pc).getTitle ());  
  20.     }  
  21.   
  22.   
  23.     @PreRemove  
  24.     public void logDeletion(Object pc) {  
  25.         getLog().debug("Removing from circulation:" +   
  26.             ((Magazine) pc).getTitle());  
  27.     }  
  28. }  

   也可以在XML文件中定义Entity Listeners,如下: 

Xml代码
  1. < entity   class = "Magazine" >   
  2.     <entity-listeners>  
  3.         <entity-listener class="MagazineLogger">  
  4.             <post-persist>logAddition</post-persist>  
  5.             <pre-remove>logDeletion</pre-remove>  
  6.         </entity-listener>  
  7.     </entity-listeners>  
  8. </ entity >   

   关于entity listeners的调用顺序,默认listener会最先被调用;接下来,父类的listeners会先调用,然后是子类的listeners;最后, 如果entity上还有某个事件的多个callback方法,那么会按照这些callback方法在entity上声明顺序来调用。

   可以使用以下两个class-level的annotation,以便在entity listeners的调用链上去掉默认listener和父类中定义的listener:

  • ExcludeDefaultListeners: 这个annotation 表明这个类及其子类上不再调用默认listeners。
  • ExcludeSuperclassListeners: 这个annotation 指示OpenJPA不调用父类上声明的任何entity listeners。

3 Metadata
    通过javax.persistence 包中定义的Annotation或者XML mapping files来指定Persistence metadata。当混合使用Annotation 和XML mapping file 的时候,如果发生冲突,那么以XML mapping file为准。
 

3.1 Class Metadata
3.1.1 Entity
    Entity annotation用来指定entity class。它有一个属性name用来在Query中引用entity。如果不指定name,那么缺省是entity class的名字(unqualified)。

   以下是个使用Entity annotation的例子:

Java代码
  1. @Entity   
  2. public   class  Company {  
  3.     @Id  
  4.     private int id;  
  5.       
  6.     @Basic   
  7.     private String name;  
  8. }  
  9.   
  10. @Entity   
  11. public   class  Address {  
  12.     @Basic  
  13.     private String province;  
  14.       
  15.     @Basic  
  16.     private String city;  
  17.       
  18.     @Basic  
  19.     private String street;  
  20.       
  21.     @Basic  
  22.     private String zip;  
  23. }  
    如果使用MySQL数据库,那么用MappingTool生成的SQL如下:  
Sql代码
  1. CREATE TABLE Company (id INTEGER NOT NULLname VARCHAR(255), PRIMARY KEY (id)) TYPE = innodb;  
  2. CREATE TABLE Address (id BIGINT NOT NULL, city VARCHAR(255), province VARCHAR(255), street VARCHAR(255), zip VARCHAR(255), PRIMARY KEY (id)) TYPE = innodb;  
  3. CREATE TABLE OPENJPA_SEQUENCE_TABLE (ID TINYINT NOT NULL, SEQUENCE_VALUE BIGINTPRIMARY KEY (ID)) TYPE = innodb;  
    JPA要求每个persistent class都必须至少声明一个identity字段,但是OpenJPA允许不声明identity字段。如果persistent class没有声明identity字段,那么OpenJPA会缺省增加id列作为数据库表的主键,同时采用Table Generator的方式为id赋值,Table Generator缺省使用OPENJPA_SEQUENCE_TABLE。 

 

3.1.2 IdClass
    当某个entity class中包含多个identity 字段的时候,必须使用identity class(例如之前提到的MagazineId类)。IdClass annotation用来指定identity class,它的value属性是java.lang.Class 型。

 

3.1.3 MappedSuperclass
    Mapped superclass不是entity class,但是它可以为entity class定义persistent state和mapping information。Mapped superclass通常是抽象类。Mapped superclass不能用于Query,或者被运用到任何EntityManager或者Query中。Mapped superclass通过 MappedSuperclass annotation指定。

   以下是个使用MappedSuperclass annotation的例子:

Java代码
  1. @MappedSuperclass  
  2. public abstract class Document {  
  3.     @Id  
  4.     protected int id;  
  5.       
  6.     @Version  
  7.     protected int version;  
  8. }  
  9.   
  10. @Entity  
  11. public class Contract extends Document {  
  12.     @Basic  
  13.     private String name;  
  14. }  
    如果使用MySQL数据库,那么用MappingTool生成的SQL如下: 
Sql代码
  1. CREATE TABLE Contract (id INTEGER NOT NULLname VARCHAR(255), version INTEGERPRIMARY KEY (id)) TYPE = innodb;  

 

3.1.4 Embeddable
    Embeddable annotation指定embeddable persistent class。Embeddable instances被映射到datastore record的一部分。JPA要求Persistent class只能是entity class或embeddable class之一。OpenJPA允许Persistent class既是entity class,也是embeddable class。只要同时使用@Entity和@ Embeddable即可。

   以下是个使用Embeddable annotation的例子:

Java代码
  1. @Entity  
  2. public class Company {  
  3.     @Id  
  4.     private int id;  
  5.       
  6.     @Basic   
  7.     private String name;  
  8.       
  9.     @Embedded  
  10.     private Address address;  
  11. }  
  12.   
  13. @Embeddable  
  14. public class Address {  
  15.     @Basic  
  16.     private String province;  
  17.       
  18.     @Basic  
  19.     private String city;  
  20.       
  21.     @Basic  
  22.     private String street;  
  23.       
  24.     @Basic  
  25.     private String zip;  
  26. }  
    如果使用MySQL数据库,那么用MappingTool生成的SQL如下: 
Sql代码
  1. CREATE TABLE Company (id INTEGER NOT NULLname VARCHAR(255), city VARCHAR(255), province VARCHAR(255), street VARCHAR(255), zip VARCHAR(255), PRIMARY KEY (id)) TYPE = innodb;  

   从以上SQL可以看出,OpenJPA并没有为Address embeddable class生成一个表,而是在Company表中包含了Address信息。

 

3.1.5 EntityListeners
    EntityListeners annotation用来指定entity listeners,它的value属性是java.lang.Class数组型。

 

3.1.6 Table

   Table annotation 用来指定entity class对应的数据库表名。它有以下几个属性:

  • String name: 表名,缺省使用unqualified class name。
  • String schema: schema名。缺省使用数据库连接的缺省schema。
  • String catalog: catalog名. 缺省使用数据库连接的缺省catalog。
  • UniqueConstraint[] uniqueConstraints: Unique constraints 用来确保一个或多个列的值在表中唯一。它只有一个属性columnNames。

   以下是个简单的例子:

Java代码
  1. @Entity  
  2. @Table(name="ART", uniqueConstraints=@Unique(columnNames="TITLE"))  
  3. public class Article {  
  4.     ...  
  5. }  

 

3.1.7 SecondaryTable
    有时候一个逻辑上的记录被分散在多个数据库表中。通过SecondaryTable annotation可以指定entity class的secondary tables。通过column annotation的table属性来引用这些secondary tables。例如:

Java代码
  1. @Entity  
  2. @Table(name="ART")  
  3. @SecondaryTable(name="ART_DATA", pkJoinColumns=@PrimaryKeyJoinColumn(name="ART_ID", referencedColumnName="ID"))  
  4. public class Article {  
  5.   
  6.     @Id  
  7.     private long id;  
  8.   
  9.     @Column(table="ART_DATA")  
  10.     private byte[] content;  
  11.   
  12.     ...  
  13. }  
 

3.2 Field and Property Metadata
    JPA提供了两种访问persistent state的方法:field access和property access。每个entity只能使用field access或property access之一来访问同一个persistence state,子类和父类必须使用相同的访问方式来访问同一个persistence state。
    如果使用field access,那么JPA会直接把persistent state注入到filed中,并直接从filed上得到persistent state的改变。Annotation直接标记在field上,例如:

Java代码
  1. @ManyToOne  
  2. private Company publisher;  

   如果使用property access,那么JPA会通过Java Bean的"getter" 和 "setter" 方法访问persistent state。Annotation标记在"getter"方法上,例如:

Java代码
  1. @ManyToOne  
  2. private Company getPublisher() { ... }  
  3. private void setPublisher(Company publisher) { ... }  

   当使用property access 的时候,你应该只通过"getter"和"setter"方法访问persistence state。这个类中其它的方法(例如业务逻辑方法),也应该只通过"getter"和"setter"方法来访问persistence state。当你需要在"getter"和"setter"方法内添加其它业务逻辑的时候,你应该认识到,"getter"和"setter"方法也会被 JPA调用。

 

3.2.1 Transient
    Transient annotation指定一个字段为non-persistent。

 

3.2.2 Id
    Id annotation用来指定identity字段。

 

3.2.3 Generated Value
    GeneratedValue 用来为identity字段生成唯一值,它有两个属性,分别是strategy和generator。
Strategy有以下可选值:

  • GeneratorType.AUTO: 缺省值,生成策略取决于JPA的实现。
  • GenerationType.IDENTITY: 在插入的时候由database设置。
  • GenerationType.SEQUENCE: 根据数据库中序列生成。
  • GenerationType.TABLE: 根据数据库中某个表的某个字段的值生成。

   JPA支持以下两种generator,分别是Sequence Generator和Table Generator。Sequence Generator有以下属性:

  • String name: 指定generator的名字。
  • String sequenceName: 指定数据库中序列名。
  • int initialValue: 指定序列的初始值。
  • int allocationSize: 有些数据库支持预分配一定数量的序列值,来减少访问次数以提高性能。allocationSize的缺省值是50。

   Table Generator有以下属性:

  • String name: 指定generator的名字。
  • String table: 指定数据库中表名。
  • String pkColumnName: 指定数据库表的主键名。
  • String valueColumnName: 指定数据库表的保存序列值的列名。
  • String pkColumnValue: 指定数据库表的某个主键值。如果用同一个表来生成多个序列值,那么需要指定pkColumnValue。
  • int allocationSize: 每一次访问数据库的时候,预分配序列值的数目,缺省值是50。

   除了identity字段之外,OpenJPA 支持在任何字段上使用 GeneratedValue annotation。 OpenJPA额外提供了两种generator,如下:  

  • uuid-string: OpenJPA会生成128位的UUID,用16-character string表示。
  • uuid-hex: 跟uuid-string一样,OpenJPA会生成128位的UUID,但是用32-character hexadecimal string 表示。

   以下是使用GeneratedValue的一个例子:

Java代码
  1. import javax.persistence.Entity;  
  2. import javax.persistence.GeneratedValue;  
  3. import javax.persistence.GenerationType;  
  4. import javax.persistence.Id;  
  5. import javax.persistence.TableGenerator;  
  6.   
  7. @Entity  
  8. public class GenerationStrategy {  
  9.     @Id  
  10.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  11.     private int identity;  
  12.       
  13.     @GeneratedValue(strategy = GenerationType.AUTO, generator="uuid-string")  
  14.     private String uuidString;  
  15.       
  16.     @GeneratedValue(strategy = GenerationType.AUTO, generator="uuid-hex")  
  17.     private String uuidHex;  
  18.       
  19.     @GeneratedValue(strategy = GenerationType.TABLE, generator="tg1")  
  20.     @TableGenerator(name="tg1", table="GenerationStrategyTable", pkColumnName="id", valueColumnName="sequence", allocationSize=1)  
  21.     private long tableGeneratorValue;  
  22.       
  23. }  

   以上例子中,声明了一个named generator:"tg1"。在运行时可以通过OpenJPAEntityManager. getNamedGenerator("tg1")方法来访问它。笔者通过MappingTool生成以上例子相关SQL的代码如下(也可以通过命令行生 成),如果generateSql是false,那么MappingTool会直接更改数据库。

Java代码
  1. boolean generateSql = true;  
  2. String java = "./src/com/example/jpa/GenerationStrategy.java";  
  3. if(!generateSql) {  
  4.     MappingTool.main(new String[]{java});  
  5.     System.out.println("the table successfully generated");  
  6. else {  
  7.     String sql = java.replace("java""sql");  
  8.     MappingTool.main(new String[]{"-sql", sql, java});  
  9.     System.out.println("the sql file was successfully generated: " + sql);  
  10. }  
    如果使用MySql数据库,那么用MappingTool生成的SQL如下:
Sql代码
  1. CREATE TABLE GenerationStrategy (identity INTEGER NOT NULL AUTO_INCREMENT, tableGeneratorValue BIGINT, uuidHex VARCHAR(255), uuidString VARCHAR(255), PRIMARY KEY (identity)) TYPE = innodb;  
  2. CREATE TABLE GenerationStrategyTable (ID VARCHAR(255) NOT NULLSEQUENCE BIGINTPRIMARY KEY (ID)) TYPE = innodb;  

 

3.2.4 Embedded Id
    如果一个entity class有多个identity字段,那么你可以声明多个@Id 字段,或者声明一个@EmbeddedId字段。@EmbeddedId字段必须是一个embeddable entity class。

 

3.2.5 Version
    Version annotation用来指定version字段。


3.2.6 Basic
    跟Transient annotation不同,basic annotation指定一个字段为persistent。可以在以下类型的字段上使用basic annotation:primitives, primitive wrappers, java.lang.String, byte[], Byte[], char[], Character[], java.math.BigDecimal, java.math.BigInteger, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Timestamp, Enums, 和 Serializable types.
    Basic annotation有以下两个属性:

  • FetchType fetch: 指定加载方式,可选值FetchType.EAGER(缺省值)和 FetchType.LAZY。
  • boolean optional: datastore 中相关字段是否允许为null,缺省值是true。

   OpenJPA 可以在任何字段上进行延迟加载(lazy-load),也允许在运行时动态修改某个字段的加载方式。

 

3.2.7 Embedded
    @Embedded字段必须是字段必须是一个embeddable entity class,它用来映射datastore record的一部分。

 

3.2.8 Many To One
    如果多个entity A引用entity B,而且其它entity A也可能引用相同的entity B,那么称A和B之间的关系是Many To One。JPA使用ManyToOne annotation来指定这种关系。它有以下属性:

  • Class targetEntity: 关联entity的Class。
  • CascadeType[] cascade: 指定级连行为,缺省是空数组。
  • FetchType fetch: 指定加载方式,可选值FetchType.EAGER(缺省值)和 FetchType.LAZY。
  • boolean optional: 关联的entity是否必须存在。如果是false,那么这个字段不能是null,缺省值是true。

   关于Cascade Type,有以下可选值:CascadeType.PERSIST、CascadeType.REMOVE、CascadeType.REFRESH和 CascadeType.MERGE。CascadeType.ALL用来指定以上所有CascadeType,以下两种写法是等价的:

Java代码
  1. @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE, CascadeType.REFRESH,CascadeType.MERGE})  
  2. private Company publisher;  
  3.   
  4. @ManyToOne(cascade=CascadeType.ALL)  
  5. private Company publisher;  

 

   以下是个使用ManyToOne annotation的例子:

Java代码
  1. @Entity  
  2. public class Publisher {  
  3.     @Id  
  4.     private int publisherId;  
  5. }  
  6.   
  7. @Entity  
  8. public class Magazine {  
  9.     @Id  
  10.     private int magazineId;  
  11.       
  12.     @ManyToOne  
  13.     private Publisher publisher;  
  14. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下: 
Sql代码
  1. CREATE TABLE Magazine (magazineId INTEGER NOT NULL, publisher_publisherId INTEGERPRIMARY KEY (magazineId)) TYPE = innodb;  
  2. CREATE TABLE Publisher (publisherId INTEGER NOT NULLPRIMARY KEY (publisherId)) TYPE = innodb;  
  3. CREATE INDEX I_MGAZINE_PUBLISHER ON Magazine (publisher_publisherId);  
    如果不希望使用OpenJPA缺省的关联字段名(例如publisher_publisherId),那么可以使用JoinColumn annotation,例如: 
Java代码
  1. @Entity  
  2. public class Publisher {  
  3.     @Id  
  4.     private int publisherId;  
  5. }  
  6.   
  7. @Entity  
  8. public class Magazine {  
  9.     @Id  
  10.     private int magazineId;  
  11.       
  12.     @ManyToOne  
  13.     @JoinColumn(name = "publisherId", referencedColumnName = "publisherId")  
  14.     private Publisher publisher;  
  15. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下:
Sql代码
  1. CREATE TABLE Magazine (magazineId INTEGER NOT NULL, publisherId INTEGERPRIMARY KEY (magazineId)) TYPE = innodb;  
  2. CREATE TABLE Publisher (publisherId INTEGER NOT NULLPRIMARY KEY (publisherId)) TYPE = innodb;  
  3. CREATE INDEX I_MGAZINE_PUBLISHER ON Magazine (publisherId);  
 

3.2.9 One To Many
    如果entity A引用多个entity B,而且没有两个entity A引用相同的entity B,那么称A和B之间的关系是One To Many。JPA使用OneToMany annotation来指定这种关系。它有以下属性:

  • Class targetEntity: 关联entity的Class。通常使用在parameterized 集合类上,如果集合类不是parameterized 类型,那么必须指定这个属性。
  • String mappedBy: 用来之指定双向关系另一端的entity name。如果没有指定这个属性,那么认为是标准的单向关系。
  • CascadeType[] cascade: A指定级连行为,缺省是空数组。
  • FetchType fetch: 指定load方式,可选值FetchType.EAGER和 FetchType.LAZY(缺省值)。

   如果使用OneToMany关系,那么缺省情况下JPA将会生成中间表来进行关联,例如:

Java代码
  1. @Entity  
  2. public class Publisher {  
  3.     @Id  
  4.     private int publisherId;  
  5.       
  6.     @OneToMany  
  7.     private List<Magazine> magazines;  
  8. }  
  9.   
  10. @Entity  
  11. public class Magazine {  
  12.     @Id  
  13.     private int magazineId;  
  14. }  

   用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下:

Sql代码
  1. CREATE TABLE Magazine (magazineId INTEGER NOT NULLPRIMARY KEY (magazineId)) TYPE = innodb;  
  2. CREATE TABLE Publisher (publisherId INTEGER NOT NULLPRIMARY KEY (publisherId)) TYPE = innodb;  
  3. CREATE TABLE Publisher_Magazine (Publisher_publisherId INTEGER, magazines_magazineId INTEGER) TYPE = innodb;  
  4. CREATE INDEX I_PBLSGZN_ELEMENT ON Publisher_Magazine (magazines_magazineId);  
  5. CREATE INDEX I_PBLSGZN_PUBLISHER_PUBLISHERID ON Publisher_Magazine (Publisher_publisherId);  
    如果不希望使用OpenJPA缺省的关联表名和字段名(例如Publisher_Magazine,Publisher_publisherId),那么可以使用JoinTable annotation,例如: 
Java代码
  1. @Entity  
  2. public class Publisher {  
  3.     @Id  
  4.     private int publisherId;  
  5.       
  6.     @OneToMany  
  7.         @JoinTable(name="PublisherMagazine",  
  8.             joinColumns=@JoinColumn(name="publisherId", referencedColumnName="publisherId"),  
  9.             inverseJoinColumns=@JoinColumn(name="magazineId", referencedColumnName="magazineId"))  
  10.     private List<Magazine> magazines;  
  11. }  
  12.   
  13. @Entity  
  14. public class Magazine {  
  15.     @Id  
  16.     private int magazineId;  
  17. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下: 
Sql代码
  1. CREATE TABLE Magazine (magazineId INTEGER NOT NULLPRIMARY KEY (magazineId)) TYPE = innodb;  
  2. CREATE TABLE Publisher (publisherId INTEGER NOT NULLPRIMARY KEY (publisherId)) TYPE = innodb;  
  3. CREATE TABLE PublisherMagazine (publisherId INTEGER, magazineId INTEGER) TYPE = innodb;  
  4. CREATE INDEX I_PBLSGZN_ELEMENT ON PublisherMagazine (magazineId);  
  5. CREATE INDEX I_PBLSGZN_PUBLISHERID ON PublisherMagazine (publisherId);  

 

   考虑下面这种情况:

Java代码
  1. @Entity  
  2. public class Publisher {  
  3.     @Id  
  4.     private int publisherId;  
  5.       
  6.     @OneToMany()  
  7.     private List<Magazine> magazines;  
  8. }  
  9.   
  10. @Entity  
  11. public class Magazine {  
  12.     @Id  
  13.     private int magazineId;  
  14.       
  15.     @ManyToOne()  
  16.     private Publisher publisher;  
  17. }  
    在这种情况下,JPA会根据Publisher上的OneToMany关系生成关联表,同时根据Magazine上的ManyToOne关系在 Magazine表上生成关联字段。如果希望避免这种冗余,那么可以通过OneToMany上的mappedBy属性来告知JPA:谁拥有这个双向关系。 例如: 
Java代码
  1. @Entity  
  2. public class Publisher {  
  3.     @Id  
  4.     private int publisherId;  
  5.       
  6.     @OneToMany(mappedBy="publisher")  
  7.     private List<Magazine> magazines;  
  8. }  
  9.   
  10. @Entity  
  11. public class Magazine {  
  12.     @Id  
  13.     private int magazineId;  
  14.       
  15.     @ManyToOne()  
  16.     private Publisher publisher;  
  17. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下:
Sql代码
  1. CREATE TABLE Magazine (magazineId INTEGER NOT NULL, publisher_publisherId INTEGERPRIMARY KEY (magazineId)) TYPE = innodb;  
  2. CREATE TABLE Publisher (publisherId INTEGER NOT NULLPRIMARY KEY (publisherId)) TYPE = innodb;  
  3. CREATE INDEX I_MGAZINE_PUBLISHER ON Magazine (publisher_publisherId);  
 

3.2.10 One To One
    如果entity A引用唯一的entity B,而且没有其它entity A引用相同的entity B,那么称A和B之间的关系是One To One。JPA使用OneToOne annotation来指定这种关系。它有以下属性:

  • Class targetEntity: 关联entity的Class。通常也可以从字段类型上得到这个信息。
  • String mappedBy: 用来之指定双向关系另一端的entity name。如果没有指定这个属性,那么认为是标准的单向关系。
  • CascadeType[] cascade: A指定级连行为,缺省是空数组。
  • FetchType fetch: 指定load方式,可选值FetchType.EAGER(缺省值)和 FetchType.LAZY。
  • boolean optional: 关联的entity是否必须存在。如果是false,那么这个字段不能是null,缺省值是true。

   以下是几个使用OneToOne关系的例子:

Java代码
  1. @Entity  
  2. public class Company {  
  3.     @Id  
  4.     private int id;  
  5.       
  6.     @OneToOne  
  7.     private Address address;  
  8. }  
  9.   
  10. @Entity  
  11. public class Address {  
  12.     @Id  
  13.     private int id;  
  14.       
  15.     @OneToOne  
  16.     private Company company;  
  17. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下(两个表都生成了跟对方表主键类型相同的字段及其索引,但是并没有显式的外键关联): 
Sql代码
  1. CREATE TABLE Address (id INTEGER NOT NULL, company_id INTEGERPRIMARY KEY (id)) TYPE = innodb;  
  2. CREATE TABLE Company (id INTEGER NOT NULL, address_id INTEGERPRIMARY KEY (id)) TYPE = innodb;  
  3. CREATE INDEX I_ADDRESS_COMPANY ON Address (company_id);  
  4. CREATE INDEX I_COMPANY_ADDRESS ON Company (address_id);  

  

   如果将Address类中company属性的annotation改成@OneToOne(mappedBy = "address"),那么用MappingTool生成的SQL(MySQL数据库)如下(只有Company表生成了跟Address表主键类型相同 的字段及其索引,但是并没有显式的外键关联):

Sql代码
  1. CREATE TABLE Address (id INTEGER NOT NULLPRIMARY KEY (id)) TYPE = innodb;  
  2. CREATE TABLE Company (id INTEGER NOT NULL, address_id INTEGERPRIMARY KEY (id)) TYPE = innodb;  
  3. CREATE INDEX I_COMPANY_ADDRESS ON Company (address_id);  

 

   如果不希望使用OpenJPA缺省的关联字段名(例如address_id),那么可以使用JoinColumn annotation,例如:

Java代码
  1. @Entity  
  2. public class Company {  
  3.     @Id  
  4.     private int id;  
  5.       
  6.     @OneToOne  
  7.     @JoinColumn(name = "AddressId", referencedColumnName = "id")  
  8.     private Address address;  
  9. }  
  10.   
  11. @Entity  
  12. public class Address {  
  13.     @Id  
  14.     private int id;  
  15.       
  16.     @OneToOne(mappedBy = "address")  
  17.     private Company company;  
  18. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下: 
Sql代码
  1. CREATE TABLE Address (id INTEGER NOT NULLPRIMARY KEY (id)) TYPE = innodb;  
  2. CREATE TABLE Company (id INTEGER NOT NULL, AddressId INTEGERPRIMARY KEY (id)) TYPE = innodb;  
  3. CREATE INDEX I_COMPANY_ADDRESS ON Company (AddressId);  

 

3.2.11 Many To Many
    如果entity A引用多个entity B,而且其它的entity A也可能引用其中某些entity B,那么称A和B之间的关系是Many To Many。JPA使用ManyToMany annotation来指定这种关系。它有以下属性:

  • Class targetEntity: 关联entity的Class。通常使用在parameterized 集合类上,如果集合类不是parameterized 类型,那么必须指定这个属性。
  • String mappedBy: 用来之指定双向关系另一端的entity name。如果没有指定这个属性,那么认为是标准的单向关系。
  • CascadeType[] cascade: A指定级连行为,缺省是空数组。
  • FetchType fetch: 指定load方式,可选值FetchType.EAGER和 FetchType.LAZY(缺省值)。

   如果使用ManyToMany关系,那么JPA将会生成中间表来进行关联,例如:

Java代码
  1. @Entity  
  2. public class Author {  
  3.     @Id  
  4.     private int authorId;  
  5.       
  6.     @ManyToMany(cascade=CascadeType.ALL)  
  7.     private List<Article> articles;  
  8. }  
  9.   
  10. @Entity  
  11. public class Article {  
  12.     @Id  
  13.     private int articleId;  
  14.       
  15.     @ManyToMany(cascade=CascadeType.ALL)  
  16.     private List<Author> authors;  
  17. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下: 
Sql代码
  1. CREATE TABLE Article (articleId INTEGER NOT NULLPRIMARY KEY (articleId)) TYPE = innodb;  
  2. CREATE TABLE Article_Author (Article_articleId INTEGER, authors_authorId INTEGER) TYPE = innodb;  
  3. CREATE TABLE Author (authorId INTEGER NOT NULLPRIMARY KEY (authorId)) TYPE = innodb;  
  4. CREATE TABLE Author_Article (Author_authorId INTEGER, articles_articleId INTEGER) TYPE = innodb;  
  5. CREATE INDEX I_RTCLTHR_ARTICLE_ARTICLEID ON Article_Author (Article_articleId);  
  6. CREATE INDEX I_RTCLTHR_ELEMENT ON Article_Author (authors_authorId);  
  7. CREATE INDEX I_THR_TCL_AUTHOR_AUTHORID ON Author_Article (Author_authorId);  
  8. CREATE INDEX I_THR_TCL_ELEMENT ON Author_Article (articles_articleId);  

   

   如果将Article类中authors属性的annotation改成@ManyToMany(cascade=CascadeType.ALL, mappedBy="articles"),那么用MappingTool生成的SQL(MySQL数据库)如下:

Sql代码
  1. CREATE TABLE Article (articleId INTEGER NOT NULLPRIMARY KEY (articleId)) TYPE = innodb;  
  2. CREATE TABLE Author (authorId INTEGER NOT NULLPRIMARY KEY (authorId)) TYPE = innodb;  
  3. CREATE TABLE Author_Article (authors_authorId INTEGER, articles_articleId INTEGER) TYPE = innodb;  
  4. CREATE INDEX I_THR_TCL_AUTHORS_AUTHORID ON Author_Article (authors_authorId);  
  5. CREATE INDEX I_THR_TCL_ELEMENT ON Author_Article (articles_articleId);  

 

   如果不希望使用OpenJPA缺省的关联表名和字段名(例如Author_Article,authors_authorId),那么可以使用JoinTable annotation,例如:

Java代码
  1. @Entity  
  2. public class Author {  
  3.     @Id  
  4.     private int authorId;  
  5.       
  6.     @ManyToMany(cascade=CascadeType.ALL)  
  7.     @JoinTable(name="AuthorArticle",  
  8.             joinColumns=@JoinColumn(name="AuthorId", referencedColumnName="AuthorId"),  
  9.             inverseJoinColumns=@JoinColumn(name="ArticleId", referencedColumnName="ArticleId"))  
  10.     private List<Article> articles;  
  11. }  
  12.   
  13. @Entity  
  14. public class Article {  
  15.     @Id  
  16.     private int articleId;  
  17.       
  18.     @ManyToMany(cascade=CascadeType.ALL, mappedBy="articles")  
  19.     private List<Author> authors;  
  20. }  
    用MappingTool生成以上两个entity class的SQL(MySQL数据库)如下: 
Sql代码
  1. CREATE TABLE Article (articleId INTEGER NOT NULLPRIMARY KEY (articleId)) TYPE = innodb;  
  2. CREATE TABLE Author (authorId INTEGER NOT NULLPRIMARY KEY (authorId)) TYPE = innodb;  
  3. CREATE TABLE AuthorArticle (AuthorId INTEGER, ArticleId INTEGER) TYPE = innodb;  
  4. CREATE INDEX I_THRRTCL_AUTHORID ON AuthorArticle (AuthorId);  
  5. CREATE INDEX I_THRRTCL_ELEMENT ON AuthorArticle (ArticleId);  

 

3.2.12 Order By
    由于关系型数据库并不保存records的顺序,因此为了保证集合类型的字段有一致的顺序,必须使用OrderBy annotation。缺省是采用identity values的升序。以下是个简单的例子:

Java代码
  1. @OrderBy("lastName, firstName")  
  2. private Collection<Author> authors;  

 

3.2.13 Map Key
    JPA在OneToMany或者ManyToMany关系中支持Map类型的字段,关联的entities组成了Map的value。JPA在每个 entity中挑选一个字段作为Map的key。MapKey annotation有一个name属性,用来指定被挑选作为key的字段名。如果没有指定name,那么会使用关联entity的identity字 段。

 

3.2.14 Persistent Field Defaults
    当某个字段没有使用以上介绍的annotations时,JPA对该字段采取以下的缺省行为:

  • 被声明成static, transient, final的字段缺省是non-persistent。
  • 以下类型的字段:primitive type, primitive wrapper type, java.lang.String, byte[], Byte[], char[], Character[], java.math.BigDecimal, java.math.BigInteger, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Timestamp, Serializable type 缺省时persistent,就像使用了Basic annotation一样。
  • Embeddable type缺省是persistent,就像使用了Embedded annotation一样。
  • 以上以外的字段缺省是non-persistent。
3.2.15 Column
    Column annotation 用来指定列的属性,它有以下可选属性:
  • String name: 列名,缺省是属性名。
  • String columnDefinition: 数据库中列的数据类型。只有当JPA vendor支持通过metadata创建表的时候,这个属性才被使用。
  • int length: 列的长度,这个属性通常也是在创建表的时候使用。也有的JPA vendor在flush之前通过这个属性验证数据。
  • int precision: 列的精度。
  • int scale: 列的小数范围。
  • boolean nullable: 列是否允许为null。缺省是true。
  • boolean insertable: 如果false,那么SQL 的insert语句中会忽略这个列。缺省是true。
  • boolean updatable: 如果false,那么SQL 的update语句中会忽略这个列。缺省是true。
  • String table: 指定secondary table的表名。

4 EntityManagerFactory
4.1 Overview
    EntityManagerFactory可以被注入到应用中,也可以通过以下方式创建:
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("openjpa");
    Persistence.createEntityManagerFactory方法通过在类路径上查找META-INF目录中的 persistence.xml文件来获得EntityManagerFactory的配置,persistence.xml文件中可以定义多个 persistence-unit。其name属性值可以作为Persistence.createEntityManagerFactory方法的参 数,transaction-type用来指定是使用JTA,还是使用局部事务。以下是个persistence.xml文件的例子:

Xml代码
  1. <?xml version="1.0"?>  
  2. <persistence>  
  3.   <persistence-unit name="openjpa" transaction-type="RESOURCE_LOCAL" >  
  4.     <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>  
  5.     <class>tutorial.Animal</class>  
  6.     <class>tutorial.Dog</class>  
  7.     <class>tutorial.Rabbit</class>  
  8.     <class>tutorial.Snake</class>  
  9.     <properties>  
  10.       <property name="openjpa.ConnectionURL" value="jdbc:hsqldb:tutorial_database"/>  
  11.       <property name="openjpa.ConnectionDriverName" value="org.hsqldb.jdbcDriver"/>  
  12.       <property name="openjpa.ConnectionUserName" value="sa"/>  
  13.       <property name="openjpa.ConnectionPassword" value=""/>  
  14.       <property name="openjpa.Log" value="DefaultLevel=WARN, Tool=INFO"/>  
  15.     </properties>  
  16.   </persistence-unit>  
  17. </persistence>  

    EntityManagerFactory 实例是重量级对象。每个EntityManagerFactory 实例可能要维护metadata cache、object state cache、EntityManager pool、connection pool等等资源。如果应用程序不再使用EntityManagerFactory,那么应该及时关闭它以便释放资源。如果在一个或多个 EntityManager处于活跃状态时试图关闭EntityManagerFactory,那么会导致一个 IllegalStateException。 

 

4.2 Persistence Context
Persistence context包含一组entities,这些entities都有唯一的persistent identity。在persistence context中,EntityManager管理entities的生命周期,entities可以访问datastore来获取persistent state。当persistence context结束的时候,被EntityManager管理的所有entities都变成detached状态。Detached entities不再被EntityManager管理,也不能访问datastore。有两种类型的persistent context:transaction persistence context和extended persistence context。

4.2.1 Transaction Persistence Context
    在transaction persistence context模型中,EntityManager为每一个事务开始一个新的persistence context。当事务被提交或者回滚后,persistence context也就自动结束,被EntityManager管理的所有entity都变成detached状态。此时如果访问entity上尚未被加载的 字段(例如lazy fetch字段)会导致没有定义的结果。
    如果不在事务中通过EntityManager访问datastore,那么EntityManager会对每一次方法调用都创建一个新的 persistence context。当方法调用结束时,persistence context也自动结束。例如当在事务之外调用EntityManager.find方法,EntityManager会创建一个临时的 persistence context,并在这个临时的persistence context中访问datastore。当EntityManager.find方法结束时,临时的persistence context自动结束,并且EntityManager.find方法会返回一个detached entity。如果用相同的identity object再次调用EntityManager.find方法,那么会得到一个新的detached entity。以下是个描述transaction persistence context模型的行为的例子:

Java代码
  1. EntityManager em; // injected  
  2. ...  
  3.   
  4. // outside a transaction:  
  5.   
  6. // each operation occurs in a separate persistence context, and returns   
  7. // a new detached instance  
  8. Magazine mag1 = em.find(Magazine.class, magId);  
  9. Magazine mag2 = em.find(Magazine.class, magId);  
  10. assertTrue(mag2 != mag1);  
  11. ...  
  12.   
  13. // transaction begins:  
  14.   
  15. // within a transaction, a subsequent lookup doesn't return any of the  
  16. // detached objects.  however, two lookups within the same transaction  
  17. // return the same instance, because the persistence context spans the  
  18. // transaction  
  19. Magazine mag3 = em.find(Magazine.class, magId);  
  20. assertTrue(mag3 != mag1 && mag3 != mag2);  
  21. Magazine mag4 = em.find(Magazine.class (magId);  
  22. assertTrue(mag4 == mag3);  
  23. ...  
  24.   
  25. // transaction commits:  
  26.   
  27. // once again, each operation returns a new instance  
  28. Magazine mag5 = em.find(Magazine.class, magId);  
  29. assertTrue(mag5 != mag3);  

 

4.2.2 Extended Persistence Context
    在extended persistence context模型中,不论是否在事务内,EntityManager在其整个生命周期内维护相同一个persistence context。所有通过EntityManager得到的entities都被EntityManager管理,只有在EntityManager被关 闭,或者entity被序列化的时候,entity才变成detached状态。
以下是个描述extended persistence context模型的行为的例子:

Java代码
  1. EntityManagerFactory emf = ...  
  2. EntityManager em = emf.createEntityManager();  
  3.   
  4. // persistence context active for entire life of EM, so only one entity  
  5. // for a given persistent identity  
  6. Magazine mag1 = em.find(Magazine.class, magId);  
  7. Magazine mag2 = em.find(Magazine.class, magId);  
  8. assertTrue(mag2 == mag1);  
  9.   
  10. em.getTransaction().begin();  
  11.   
  12. // same persistence context active within the transaction  
  13. Magazine mag3 = em.find(Magazine.class, magId);  
  14. assertTrue(mag3 == mag1);  
  15. Magazine mag4 = em.find(Magazine.class (magId);  
  16. assertTrue(mag4 == mag1);  
  17.   
  18. em.getTransaction.commit ();  
  19.   
  20. // when the transaction commits, instance still managed  
  21. Magazine mag5 = em.find(Magazine.class, magId);  
  22. assertTrue(mag5 == mag1);  
  23.   
  24. // instance finally becomes detached when EM closes  
  25. em.close();  

 

5 EntityManager
    EntityManager接口的方法可以大致分为以下几类。
5.1 Transaction Association

Java代码
  1. public EntityTransaction getTransaction ();  
    EntityManager实例和EntityTransaction实例之间是一对一的关系。 通过getTransaction方法可以得到与EntityManager关联的EntityTransaction实例。 

 

5.2 Entity Identity Management
    EntityManager负责管理entities,以下的这些方法的行为会因persistence context(transaction persistence context或者extended persistence context)的不同而有差异。

Java代码
  1. public <T> T find(Class<T> cls, Object oid);  
    find方法返回persistent identity指定的entity。 如果这个entity存在于当前的persistence context中,那么返回值是被缓存的对象;否则会创建一个新的entity,并从datastore中加载相关的persistent state。如果datastore不存在持有指定persistent identity的记录,那么这个方法返回null。 
Java代码
  1. public <T> T getReference(Class<T> cls, Object oid);  
    getReference方法与find相似。不同的是:如果缓存中没有指定的entity,EntityManager会创建一个新的entity,不 是立即访问datastore(不加载persistent state),而是在第一次访问某个持久字段的时候才加载相应的persistent state。此外,getReference 方法不返回null,如果在datastore中找不到相应的entity,这个方法会抛出EntityNotFoundException。 
Java代码
  1. public boolean contains(Object entity);  

   如果当前的persistence context 包含指定的entity,那么返回true;否则返回false。

 

5.3 Cache Management

Java代码
  1. public void flush();  

   Flush方法把当前事务中所有的修改写入到datastore中。如果EntityManager没有连接到datastore,那么 EntityManager首先会获取一个连接并一直持有到事务结束。Flush方法抛出的任何异常都会导致事务回滚。如果调用flush方法时没有一个 活跃的事务,那么flush方法会抛出TransactionRequiredException。

Java代码
  1. public FlushModeType getFlushMode();  
  2. public void setFlushMode(FlushModeType flushMode);  
    EntityManager的FlushMode 属性用来指定是否在执行query之前进行flush。这可以确保当前事务中的任何修改会体现在query的结果中。此外也可以在query实例上设置FlushMode。它有以下两个可选值: 
  • COMMIT: 只是在事务提交的时候flush。当前事务中的修改可能不会体现在query的结果中。
  • AUTO: 在需要的时候进行flush以确保当前事务中的任何修改都会体现在query的结果中。

   OpenJPA 只在当前事务的任何修改可能会影响到将要执行的query的结果时才进行flush。

Java代码
  1. public void clear();  
    Clear方法会结束当前的persistence context,被EntityManager管理的所有entities变成detached状态。 

 

5.4 Query Factory

Java代码
  1. public Query createQuery(String query);  
    createQuery方法根据提供的Java Persistence Query Language (JPQL) string来创建一个query。
Java代码
  1. public Query createNamedQuery(String name);  
    createNamedQuery方法用来得到通过metadata定义的命名query。 
Java代码
  1. public Query createNativeQuery(String sql);  
  2. public Query createNativeQuery(String sql, Class resultCls);  
  3. public Query createNativeQuery(String sql, String resultMapping);  

    以上方法用来创建datastore特有的native queries。例如在关系型数据库中使用的Structured Query Language (SQL)。

 

5.5 Closing

Java代码
  1. public boolean isOpen();  
  2. public void close();  

   当不再使用EntityManager 的时候,需要及时关闭它以便释放资源。如果EntityManager 已经关闭,那么除了调用isOpen 方法外,调用EntityManager上的其它方法会导致IllegalStateException。不能在一个事务正在进行中的时候关闭 EntityManager。

 

5.6 Entity Lifecycle Management
5.6.1 public void persist(Object entity);
    persist方法用于将新创建的entity纳入EntityManager的管理,在下一次的flush或者commit时,这个entity会被插入到datastore中。这个方法只能在一个活跃的事务环境中调用。它的行为如下:

  • 如果是新entity,那么它会被EntityManager管理。
  • 如果这个entity已经被EntityManager管理,那么会被忽略。
  • 如果这个entity之前被remove,那么它会重新被管理。
  • 如果这个entity的状态是detached,那么会导致IllegalArgumentException。

   以下是个简单的例子:

Java代码
  1. import java.util.Iterator;  
  2. import java.util.List;  
  3.   
  4. import javax.persistence.Basic;  
  5. import javax.persistence.CascadeType;  
  6. import javax.persistence.Entity;  
  7. import javax.persistence.FetchType;  
  8. import javax.persistence.Id;  
  9. import javax.persistence.OneToMany;  
  10.   
  11. import org.apache.openjpa.persistence.FetchAttribute;  
  12. import org.apache.openjpa.persistence.FetchGroup;  
  13. import org.apache.openjpa.persistence.FetchGroups;  
  14.   
  15. @Entity  
  16. @FetchGroups({  
  17.     @FetchGroup(name="detail", attributes={  
  18.         @FetchAttribute(name="grade"),  
  19.         @FetchAttribute(name="magazines")  
  20.     })  
  21. })  
  22. public class Publisher {  
  23.     private int id;  
  24.       
  25.     private String name;  
  26.       
  27.     private String grade;  
  28.       
  29.     private List<Magazine> magazines;  
  30.       
  31.     public String toString() {  
  32.         StringBuffer sb = new StringBuffer();  
  33.         sb.append("id: " + getId());  
  34.         sb.append(", name: " + getName());  
  35.         sb.append(", grade: " + getGrade());  
  36.           
  37.         sb.append(", magazines[");  
  38.         if(getMagazines() != null) {  
  39.             for(Iterator<Magazine> iter = getMagazines().iterator(); iter.hasNext(); )  {  
  40.                 sb.append(iter.next().toString());  
  41.                 if(iter.hasNext()) {  
  42.                     sb.append("; ");  
  43.                 }  
  44.             }  
  45.         }  
  46.         sb.append("]");  
  47.         return sb.toString();  
  48.     }  
  49.       
  50.     @Id  
  51.     public int getId() {  
  52.         return id;  
  53.     }  
  54.   
  55.     public void setId(int id) {  
  56.         this.id = id;  
  57.     }  
  58.       
  59.     @Basic  
  60.     public String getName() {  
  61.         return name;  
  62.     }  
  63.   
  64.     public void setName(String name) {  
  65.         this.name = name;  
  66.     }  
  67.       
  68.     @Basic(fetch=FetchType.LAZY)  
  69.     public String getGrade() {  
  70.         return grade;  
  71.     }  
  72.   
  73.     public void setGrade(String grade) {  
  74.         this.grade = grade;  
  75.     }  
  76.       
  77.     @OneToMany(mappedBy="publisher", cascade=CascadeType.ALL, fetch=FetchType.LAZY)  
  78.     public List<Magazine> getMagazines() {  
  79.         return magazines;  
  80.     }  
  81.     public void setMagazines(List<Magazine> magazines) {  
  82.         this.magazines = magazines;  
  83.     }  
  84. }  
Java代码
  1. import javax.persistence.CascadeType;  
  2. import javax.persistence.Entity;  
  3. import javax.persistence.Id;  
  4. import javax.persistence.IdClass;  
  5. import javax.persistence.JoinColumn;  
  6. import javax.persistence.ManyToOne;  
  7.   
  8. @Entity  
  9. @IdClass(Magazine.MagazineId.class)  
  10. public class Magazine {  
  11.     @Id  
  12.     private String isbn;  
  13.       
  14.     @Id  
  15.     private String title;  
  16.       
  17.     @ManyToOne(cascade=CascadeType.ALL)  
  18.     @JoinColumn(name = "publisherId", referencedColumnName = "id")    
  19.     private Publisher publisher;  
  20.       
  21.     public String toString() {  
  22.         StringBuffer sb = new StringBuffer();  
  23.         sb.append("isbn: " + isbn);  
  24.         sb.append(", title: " + title);  
  25.         return sb.toString();  
  26.     }  
  27.       
  28.     public String getIsbn() {  
  29.         return isbn;  
  30.     }  
  31.   
  32.     public void setIsbn(String isbn) {  
  33.         this.isbn = isbn;  
  34.     }  
  35.   
  36.     public String getTitle() {  
  37.         return title;  
  38.     }  
  39.   
  40.     public void setTitle(String title) {  
  41.         this.title = title;  
  42.     }  
  43.       
  44.     public Publisher getPublisher() {  
  45.         return publisher;  
  46.     }  
  47.   
  48.     public void setPublisher(Publisher publisher) {  
  49.         this.publisher = publisher;  
  50.     }  
  51.       
  52.     public static class MagazineId {  
  53.         private String isbn;  
  54.         private String title;  
  55.           
  56.         public MagazineId() {  
  57.         }  
  58.           
  59.         public MagazineId(String isbn, String title) {  
  60.             this.isbn = isbn;  
  61.             this.title = title;  
  62.         }  
  63.           
  64.         public boolean equals(Object obj) {  
  65.             if(this == obj) {  
  66.                 return true;  
  67.             }  
  68.             if(!(obj instanceof MagazineId)) {  
  69.                 return false;  
  70.             }  
  71.               
  72.             MagazineId rhs = (MagazineId)obj;  
  73.             boolean b1 = (isbn == rhs.isbn || (isbn != null && isbn.equals(rhs.isbn)));  
  74.             boolean b2 = (title == rhs.title || (title != null && title.equals(rhs.title)));  
  75.             return b1 && b2;  
  76.         }  
  77.           
  78.         public int hashCode(){  
  79.             int h1 = (isbn == null ? 0 : isbn.hashCode());  
  80.             int h2 = (title == null ? 0 : title.hashCode());  
  81.             return h1 ^ h2;  
  82.         }  
  83.           
  84.         public String toString() {  
  85.             StringBuffer sb = new StringBuffer();  
  86.             sb.append("isbn: " + isbn);  
  87.             sb.append(", title: " + title);  
  88.             return sb.toString();  
  89.         }  
  90.     }  
  91. }  
Java代码
  1. Publisher p1 = new Publisher();  
  2. p1.setId(id);  
  3. p1.setName("publisher1");  
  4. p1.setGrade("excellent");  
  5. p1.setMagazines(new ArrayList<Magazine>());  
  6. Magazine m1 = new Magazine();  
  7. m1.setIsbn("isbn1");  
  8. m1.setTitle("title1");  
  9. m1.setPublisher(p1);  
  10. p1.getMagazines().add(m1);  
  11. Magazine m2 = new Magazine();  
  12. m2.setIsbn("isbn2");  
  13. m2.setTitle("title2");  
  14. m2.setPublisher(p1);  
  15. p1.getMagazines().add(m2);  
  16. EntityManagerem = entityManagerFactory.createEntityManager();  
  17. em.getTransaction().begin();  
  18. em.persist(p1);  
  19. em.getTransaction().commit();  
  

5.6.2 public void remove(Object entity);
    remove方法用于删除被管理的entity。在下一次的flush或者commit时,这个entity会从datastore中删除。这个方法只能在一个活跃的事务环境中调用。它的行为如下:

  • 如果是新entity,那么会被忽略。
  • 如果是已经被管理的entity,那么它会被删除。
  • 如果这个entity之前被remove,那么会被忽略。
  • 如果这个entity的状态是detached,那么会导致IllegalArgumentException。

5.6.3 public void refresh(Object entity);
    refresh方法用于确保entity的persistent state和datastore中的persistent state同步。它的行为如下:

  • 如果是新entity,那么会被忽略。
  • 如果是已经被管理的entity,那么刷新它的persistent state。
  • 如果这个entity之前被remove,那么会被忽略。
  • 如果这个entity的状态是detached,那么会导致IllegalArgumentException。

5.6.4 public Object merge(Object entity);
    有些情况下,你需要编辑一个处于detached状态的entity,然后重新将这个entity纳入到EntityManager的管理中,并将对其 persistent state的修改更新到datastore中。Merge方法返回该entity受管理的一份拷贝。这个方法只能在一个活跃的事务环境中调用。它的行为如 下:

  • 如果是新entity,那么会创建该entity的一份拷贝,并将这份拷贝纳入EntityManager的管理。
  • 如果是已经被管理的entity,那么会被忽略。
  • 如果这个entity之前被remove,那么会导致IllegalArgumentException。
  • 如 果这个entity的状态是detached,如果EntityManager中已经管理了具有相同的identity的entity B,那么会将原始entity的persistent state拷贝到entity B中;否则会创建该entity的一份拷贝,并将这份拷贝纳入EntityManager的管理。

   以下是个简单的例子:

Java代码
  1. p1.setName("publisher2");  
  2. EntityManager em = entityManagerFactory.createEntityManager();  
  3. em.getTransaction().begin();  
  4. em.merge(obj);  
  5. em.getTransaction().commit();  

 

5.6.5 public void lock (Object entity, LockModeType mode);
    lock方法通过指定的LockModeType 来对entity加锁。LockModeType 有以下两个可选值:

  • READ: 在其它事务中可以读取,但是不能更新。
  • WRITE: 在其它事务中不可以读取或更新。在当前事务提交后,无论被WRITE lock锁定的entities是否改变,其version将会自动增加。

5.6.6 Detach and Attach
    除了JPA定义的detach和attach API之外,OpenJPA还支持更多的特性。例如OpenJPAEntityManager提供以下方法:

Java代码
  1. public Object detach(Object pc);  
  2. public Object[] detachAll(Object... pcs);  
  3. public Collection detachAll(Collection pcs);  
    以上的detach方法返回给定entity的detached状态的拷贝。在detach在当前事务中被修改过的entity之前,flush方法会被 调用,以便将这些修改保存到datastore中。由于detached entity不能访问datastore,因此有时候需要在detach之前加载一些persistent state(例如某些lazy fetch字段)。尽管可以通过手工编码完成,OpenJPA也提供了一些特性来自动完成类似的工作。例如DetachState,它有以下可选值: 
  • loaded: 保持已经加载的字段,对于没有加载的字段则保持不变。这是缺省值。
  • fetch-groups: 根据fetch group来决定需要加载的字段。
  • all: 加载所有的字段。

   继续5.6.1中的例子:

Java代码
  1. EntityManager em = entityManagerFactory.createEntityManager();  
  2. Publisher p2 = (Publisher)em.find(Publisher.class, id);  
  3. em.close();  
  4. System.out.println(p2.toString());  

    以上例子中p2在em.close()之后变成detached状态。由于采用了缺省的DetachState,因此没有加载lazy fetch字段grade和magazines。输出如下:
id: 1, name: publisher1, grade: null, magazines[]

 

Java代码
  1. em = entityManagerFactory.createEntityManager();  
  2. Publisher p4 = (Publisher)em.find(Publisher.class, id);  
  3. p4.getGrade();  
  4. p4.getMagazines();  
  5. em.close();  
  6. System.out.println(p4.toString());  

    以上例子中p4在em.close()之后变成detached状态。虽然采用了缺省的DetachState,但是由于程序中显式访问了lazy fetch字段,所以grade和magazines被加载,并且在detach之后仍然可以访问。输出如下:
id: 1, name: publisher1, grade: excellent, magazines[isbn: isbn1, title: title1; isbn: isbn2, title: title2]

 

Java代码
  1. em = entityManagerFactory.createEntityManager();  
  2. ((OpenJPAEntityManager)em).setDetachState(DetachStateType.ALL);  
  3. Publisher p5 = (Publisher)em.find(Publisher.class, id);  
  4. em.close();  
  5. System.out.println(p5.toString());  

    以上例子中p5在em.close()之后变成detached状态。由于采用了 DetachStateType.ALL,因此所有的lazy fetch字段在detach之前都自动被加载,并且在detach之后仍然可以访问。输出如下:
id: 1, name: publisher1, grade: excellent, magazines[isbn: isbn1, title: title1; isbn: isbn2, title: title2]

 

Java代码
  1. em = entityManagerFactory.createEntityManager();  
  2. ((OpenJPAEntityManager)em).setDetachState(DetachStateType.FETCH_GROUPS);  
  3. ((OpenJPAEntityManager)em).getFetchPlan().addFetchGroup("detail");  
  4. Publisher p6 = (Publisher)em.find(Publisher.class, id);  
  5. em.close();  
  6. System.out.println(p6.toString());  
    以上例子中p6在em.close()之后变成detached状态。由于采用了 DetachStateType. FETCH_GROUPS,而Publisher中名为"detail"的fetch group定义了要额外加载grade和magazines,因此grade和magazines被加载,并且在detach之后仍然可以访问。输出如 下:
id: 1, name: publisher1, grade: excellent, magazines[isbn: isbn1, title: title1; isbn: isbn2, title: title2]

6 Query
6.1 JPQL Queries
6.1.1Query Basics

Java代码
  1. public Query createQuery(String jpql);  

    EntityManager上的这个方法用来根据给定的JPQL创建Query。

Java代码
  1. public List getResultList();  

   Query上的这个方法用来得到query的执行结果,以下是个简单的例子:

Java代码
  1. EntityManager em = entityManagerFactory.createEntityManager();   
  2. Query q = em.createQuery("SELECT x FROM Magazine x");  
  3. List<Magazine> results = (List<Magazine>) q.getResultList();  
  4. em.close();  
    JPQL query的from子句中定义了query内部命名空间。可以将任意的标识符赋值给entities,之后就可以在query的任意位置上通过标识符引用entities。 在from子句中,关键字as是可选的。例如以下两个JPQL等效:
Sql代码
  1. SELECT x FROM Magazine x   
  2. SELECT x FROM Magazine AS x  

    当查询entities的时候,关键字object也是可选的,例如select x 和 SELECT OBJECT(x) 是等效的。JPQL中的关键字不是大小写敏感的,而entity、identifier和member names是大小写敏感的。以下是一些JPQL的例子: 

Sql代码
  1. SELECT x FROM Magazine x WHERE x.TITLE = 'JDJ'  
  2. SELECT x FROM Magazine x WHERE x.title = 'JDJ' OR x.title = 'JavaPro'  
  3. SELECT x FROM Magazine x WHERE x.price > 3.00 AND x.price <= 5.00  
  4. SELECT x FROM Magazine x WHERE x.price <> 3.00  
  5. SELECT x FROM Magazine x WHERE (x.price > 3.00 AND x.price <= 5.00) OR x.price = 7.00  
  6. SELECT x FROM Magazine x WHERE x.price > 3.00 AND (x.price <= 5.00 OR x.price = 7.00)  
  7. SELECT x FROM Magazine x WHERE x.price >= 3.00 AND x.price <= 5.00  
  8. SELECT x FROM Magazine x WHERE x.price BETWEEN 3.00 AND 5.00  
  9. SELECT x FROM Magazine x WHERE x.title LIKE 'J%'  
  10. SELECT x FROM Magazine x WHERE x.title LIKE 'J__'  
  11. SELECT x FROM Magazine x WHERE x.title IN ('JDJ''JavaPro''IT Insider')  
  12. SELECT x FROM Magazine x WHERE x.articles is empty  
  13. SELECT x FROM Magazine x WHERE x.publisher is null  
  14. SELECT x FROM Magazine x WHERE NOT(x.price = 10.0)  

 

6.1.2 Relation Traversal
    可以通过类似Java的语法来遍历对象间的关系。例如Magazine中有个Publisher类型的属性publisher,那么可以通过以下方式编写JPQL query:

Sql代码
  1. SELECT x FROM Magazine x WHERE x.publisher.name = 'Random House'  
    以上的遍历中假设关系不是null。在SQL术语中类似于inner join。如果你希望包含为null 的关系,那么可以如下指定: 
Sql代码
  1. SELECT x FROM Magazine x WHERE x.publisher.name = 'Random House' or x.publisher is null  
    也可以在query中遍历集合字段,但是必须首先在from子句中定义遍历相关的identification variable,例如: 
Sql代码
  1. SELECT x FROM Magazine x, IN(x.articles) y WHERE y.authorName = 'John Doe'  
    IN() 类型的语法可以使用inner join关键词,例如以下两个JPQL等效: 
Sql代码
  1. SELECT x FROM Magazine x, IN(x.articles) y WHERE y.authorName = 'John Doe'   
  2. SELECT x FROM Magazine x inner join x.articles y WHERE y.authorName = 'John Doe'   

 

6.1.3 Fetch Joins
    JPQL query中可以指定一个或多个join fetch来指定哪些字段被pre-fetched,以5.6.1中的Publisher和Magazine为例:

Java代码
  1. em = entityManagerFactory.createEntityManager();  
  2. Query q2 = em.createQuery("SELECT x FROM Publisher x WHERE x.id = 1");  
  3. List<Publisher> r2 = (List<Publisher>)q2.getResultList();  
  4. em.close();  
  5. for(Iterator<Publisher> iter = r2.iterator(); iter.hasNext(); ) {  
  6.     System.out.println(iter.next().toString());  
  7. }  
    以上代码执行后的输出是:
    id: 1, name: publisher1, grade: null, magazines[]
Java代码
  1. em = entityManagerFactory.createEntityManager();  
  2. Query q3 = em.createQuery("SELECT x FROM Publisher x join fetch x.grade join fetch x.magazines WHERE x.id = 1");  
  3. List<Publisher> r3 = (List<Publisher>)q3.getResultList();  
  4. em.close();  
  5. for(Iterator<Publisher> iter = r3.iterator(); iter.hasNext(); ) {  
  6.     System.out.println(iter.next().toString());  
  7. }  
    以上代码执行后的输出是:
    id: 1, name: publisher1, grade: excellent, magazines[isbn: isbn1, title: title1; isbn: isbn1, title: title1; isbn: isbn2, title: title2; isbn: isbn2, title: title2]

 

6.1.4 JPQL Functions
    JPQL 支持一系列预定义的标量函数,例如:

Sql代码
  1. SELECT x FROM Magazine x WHERE CONCAT(x.title, 's') = 'JDJs'  
  2. SELECT x FROM Magazine x WHERE SUBSTRING(x.title, 1, 1) = 'J'  
  3. SELECT x FROM Magazine x WHERE TRIM(BOTH 'J' FROM x.title) = 'D'  
  4. SELECT x FROM Magazine x WHERE LOWER(x.title) = 'jdj'  
  5. SELECT x FROM Magazine x WHERE UPPER(x.title) = 'JAVAPRO'  
  6. SELECT x FROM Magazine x WHERE LENGTH(x.title) = 3  
  7. SELECT x FROM Magazine x WHERE LOCATE('D', x.title) = 2  
  8. SELECT x FROM Magazine x WHERE ABS(x.price) >= 5.00  
  9. SELECT x FROM Magazine x WHERE SQRT(x.price) >= 1.00  
  10. SELECT x FROM Magazine x WHERE MOD(x.price, 10) = 0  

 

6.1.5 Polymorphic Queries
    JPQL from子句中的entity class不仅包含这个类本身,而且还包含这个类及其子类。以下是个关于polymorphic query的简单例子:

Java代码
  1. @Entity  
  2. @Inheritance(strategy=InheritanceType.SINGLE_TABLE)  
  3. @DiscriminatorColumn(name="Class", discriminatorType=DiscriminatorType.STRING)  
  4. public abstract class BankingAccount {  
  5.     @Id  
  6.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  7.     protected int id;  
  8. }  
  9.   
  10. @Entity  
  11. @DiscriminatorValue("CC")  
  12. public class CreditCard extends BankingAccount {  
  13.     @Basic  
  14.     private BigInteger limitAmount;  
  15.   
  16.     public String toString() {  
  17.         StringBuffer sb = new StringBuffer();  
  18.         sb.append("credit card, limit amount: ").append(limitAmount);  
  19.         return sb.toString();  
  20.     }  
  21. }  
  22.   
  23. @Entity  
  24. @DiscriminatorValue("DC")  
  25. public class DebitCard extends BankingAccount {  
  26.     @Basic  
  27.     private BigInteger balanceAmount;  
  28.   
  29.     public String toString() {  
  30.         StringBuffer sb = new StringBuffer();  
  31.         sb.append("debit card, balance amount: ").append(balanceAmount);  
  32.         return sb.toString();  
  33.     }  
  34. }  
    假设目前数据库中的数据如下:
   
Sql代码
  1. select * from bankingaccount;  
  2. +----+-------+-------------+---------------+  
  3. | id | Class | limitAmount | balanceAmount |  
  4. +----+-------+-------------+---------------+  
  5. |  5 | CC    |        1000 |          NULL |  
  6. |  6 | DC    |        NULL |          2000 |  
  7. +----+-------+-------------+---------------+  
    
Java代码
  1. EntityManager em = entityManagerFactory.createEntityManager();  
  2. Query q1 = em.createQuery("SELECT x FROM BankingAccount x");  
  3. List<BankingAccount> r1 = (List<BankingAccount>)q1.getResultList();  
  4. for(Iterator<BankingAccount> iter = r1.iterator(); iter.hasNext(); ) {  
  5.     System.out.println(iter.next().toString());  
  6. }  
  7. em.close();  
    以上代码的执行结果如下:
    credit card, limit amount: 1000
    debit card, balance amount: 2000 

 

6.1.6 Query Parameters
    JPQL支持两种类型的query参数:named parameters 或 positional parameters。在一个JPQL query中不能同时使用两种类型的参数。以下是两个简单的例子:

Java代码
  1. Query q = em.createQuery("SELECT x FROM Magazine x WHERE x.title = ?1 and x.price > ?2");  
  2. q.setParameter(1"JDJ").setParameter(25.0);  
  3. List<Magazine> results = (List<Magazine>) q.getResultList();  
  4.   
  5. Query q = em.createQuery("SELECT x FROM Magazine x WHERE x.title = :titleParam and x.price > :priceParam");  
  6. q.setParameter("titleParam""JDJ").setParameter("priceParam"5.0);  
  7. List<Magazine> results = (List<Magazine>) q.getResultList();  

 

6.1.7 Query Hints
    JPQL支持通过name/value对来指定query hints。例如:

Java代码
  1. Query q = em.createQuery("select m from Magazine m where ... ");  
  2. q.setHint("openjpa.hint.OptimizeResultCount"new Integer(2));  
  3. q.setHint("openjpa.FetchPlan.ReadLockMode","WRITE");  
  4. List r = q.getResultList();  

 

6.1.8 Ordering
    JPQL支持根据一个或者多个字段对查询结果进行排序,例如:

Sql代码
  1. SELECT x FROM Magazine x order by x.title asc, x.price desc  

 

6.1.9 Aggregates
    JPQL支持min、max、avg和count等聚集函数(也被称为列函数)。以下是一些例子:

Java代码
  1. Query q = em.createQuery("SELECT AVG(x.price) FROM Magazine x");  
  2. Query q = em.createQuery("SELECT SUM(DISTINCT x.price) FROM Magazine x");  
  3. Query q = em.createQuery("SELECT MAX(x.price) FROM Magazine x WHERE x.title = 'JDJ'");  

 

6.1.10 Named Queries
    JPQL支持通过NamedQuery 和NamedQueries 这两个annotation来声明命名query。以下是一个例子:

Java代码
  1. @Entity  
  2. @NamedQueries({  
  3.     @NamedQuery(name="magsOverPrice",  
  4.         query="SELECT x FROM Magazine x WHERE x.price > ?1"),  
  5.     @NamedQuery(name="magsByTitle",  
  6.         query="SELECT x FROM Magazine x WHERE x.title = :titleParam")  
  7. })  
  8. public class Magazine {  
  9.     ...  
  10. }  
  11.   
  12. Query q = em.createNamedQuery("magsOverPrice");  
  13. q.setParameter(15.0f);  
  14. List<Magazine> results = (List<Magazine>) q.getResultList();  
  15.   
  16. Query q = em.createNamedQuery("magsByTitle");  
  17. q.setParameter("titleParam""JDJ");  
  18. List<Magazine> results = (List<Magazine>) q.getResultList();  

 

6.1.11 Delete By Query
    Query可以用来高效地删除对象(不必将每个对象查询之后再进行删除,而是直接进行批量删除)。以下是个简单的例子:

Java代码
  1. Query q = em.createQuery("DELETE FROM Subscription s WHERE s.subscriptionDate < :today");  
  2. q.setParameter("today"new Date());  
  3. int deleted = q.executeUpdate();  
    executeUpdate方法的返回值是删除对象的个数。 

 

6.1.12 Update By Query
    跟delete类似,Query也可以用来高效地更新对象。以下是个简单的例子:

Java代码
  1. Query q = em.createQuery("UPDATE Subscription s SET s.paid = :paid WHERE s.subscriptionDate < :today");  
  2. q.setParameter("today"new Date());  
  3. q.setParameter("paid"true);  
  4. int updated = q.executeUpdate();   

 

6.1.13 Subquery
    目前JPA规范支持在WHERE子句和HAVING子句中使用子查询,未来可能会支持FROM子句中使用子查询。以下是一些例子:

Sql代码
  1. SELECT DISTINCT auth FROM Author auth WHERE EXISTS (SELECT spouseAuthor FROM Author spouseAuthor WHERE spouseAuthor = auth.spouse)  
  2. SELECT auth FROM Author auth WHERE auth.salary >= ALL(SELECT a.salary FROM Author a WHERE a.magazine = auth.magazine)  
  3. SELECT goodPublisher FROM Publisher goodPublisher WHERE goodPublisher.revenue < (SELECT AVG(p.revenue) FROM Publisher p)  
  4. SELECT mag FROM Magazine mag WHERE (SELECT COUNT(art) FROM mag.articles art) > 10  

 

6.1.14 JPQL Constructor Expressions
    在SELECT子句中可以通过使用constructor来返回一个或多个实例。指定的类不必是entity class,例如:

Sql代码
  1. SELECT NEW com.company.PublisherInfo(pub.id, pub.revenue, mag.price) FROM Publisher pub JOIN pub.magazines mag WHERE mag.price > 5.00  

 

6.2 SQL Queries
    JPA支持通过Query接口的以下两个方法使用SQL查询:

Java代码
  1. public Query createNativeQuery(String sqlString, Class resultClass);  
  2. public Query createNativeQuery(String sqlString, String resultSetMapping);  

   OpenJPA也支持使用存储过程。OpenJPA假定任何不以SELECT开头的SQL为存储过程的调用。

 

   在指定resultClass的情况下,sqlString必须查询指定resultClass的 primary key 列、 discriminator 列 (如果存在) 和version column (如果存在)。JPA使用以上三个列的信息来判断object identity、区分查询子类和检查并发修改。 以下是个简单的例子:

Java代码
  1. Query query = em.createNativeQuery("SELECT isbn, title, price, vers FROM Magazine WHERE price > 5 AND price < 10", Magazine.class);  
  2. List<Magazine> results = (List<Magazine>) query.getResultList();  
  3. for (Magazine mag : results)  
  4.     processMagazine(mag);  
 

   在指定resultSetMapping的情况下,resultSetMapping引用一个定义好的SqlResultSetMapping,例如:

Java代码
  1. @Entity  
  2. @SqlResultSetMapping(name="srsm1",  
  3.     entities={@EntityResult(entityClass=Publisher.class),  
  4.               @EntityResult(entityClass=Magazine.class)}  
  5. )  
  6. public class Publisher {  
  7.     …  
  8. }  
  9.   
  10. EntityManager em = entityManagerFactory.createEntityManager();  
  11. Query q13 = em.createNativeQuery("SELECT p.id, p.name, m.isbn, m.title FROM Publisher AS p, Magazine as m WHERE p.id = 1 AND p.id = m.publisherId""srsm1");  
  12. List r13 = q13.getResultList();  
  13. em.close();  
  14. for(Iterator iter = r13.iterator(); iter.hasNext(); ) {  
  15.     Object objects[] = (Object[])iter.next();  
  16.     System.out.println("publisher: " + objects[0] + ", magazine: " + objects[1]);  
  17. }  
 

   当查询结果不仅包含entity,而且包含value type的时候,可以在SqlResultSetMapping中指定@ColumnResult,例如:

Java代码
  1. @Entity  
  2. @SqlResultSetMapping(name="srsm2",  
  3.     entities={@EntityResult(entityClass=Publisher.class)},  
  4.         columns={@ColumnResult(name="count")}  
  5. )  
  6. public class Publisher {  
  7.     …  
  8. }  
  9.   
  10. EntityManager em = entityManagerFactory.createEntityManager();  
  11. Query q14 = em.createNativeQuery("SELECT p.id, count(*) AS count FROM Publisher AS p LEFT JOIN Magazine as m ON p.id = m.publisherId GROUP BY p.id""srsm2");  
  12. List r14 = q14.getResultList();  
  13. em.close();  
  14. for(Iterator iter = r14.iterator(); iter.hasNext(); ) {  
  15.     Object objects[] = (Object[])iter.next();  
  16.     System.out.println("publisher: " + objects[0] + ", count: " + objects[1]);  


7 Inheritance
    对象使用引用以便关联到其它对象;关系型数据库表之间采用外键来描述表的关系。在关系型数据库中通常没有自然且有效的方法来描述类的继承关系。JPA通过Inheritance annotation提供了几种继承策略,它有以下属性:

  • InheritanceType strategy:用来声明继承策略。可选值是InheritanceType.SINGLE_TABLE、 InheritanceType.JOINED和InheritanceType .TABLE_PER_CLASS。缺省值是InheritanceType.SINGLE_TABLE。

   关于Inheritance的更多内容,可以参考Hibernate实战by Christian Bauer, Gavin King。

 

7.1 Single Table
    InheritanceType.SINGLE_TABLE 策略为类的继承体系采用同一个表。表名是基类的名称。例如:

Java代码
  1. @Entity  
  2. @Inheritance(strategy=InheritanceType.SINGLE_TABLE)  
  3. public class Base {  
  4.     @Id  
  5.     private int id;  
  6.       
  7.     @Basic  
  8.     private String baseName;  
  9. }  
  10.   
  11. @Entity  
  12. public class Derived1 extends Base {  
  13.     @Basic  
  14.     private String derived1Name;  
  15. }  
  16.   
  17. @Entity  
  18. public class Derived2 extends Base {  
  19.     @Basic  
  20.     private String derived2Name;  
  21. }  

 

   使用MappingTool建立的表结构如下:

Sql代码
  1. mysql> describe base;  
  2. +--------------+--------------+------+-----+---------+----------------+  
  3. | Field        | Type         | Null | Key | Default | Extra          |  
  4. +--------------+--------------+------+-----+---------+----------------+  
  5. | id           | int(11)      | NO   | PRI | NULL    | auto_increment |  
  6. | baseName     | varchar(255) | YES  |     | NULL    |                |  
  7. | DTYPE        | varchar(255) | YES  | MUL | NULL    |                |  
  8. | derived1Name | varchar(255) | YES  |     | NULL    |                |  
  9. | derived2Name | varchar(255) | YES  |     | NULL    |                |  
  10. +--------------+--------------+------+-----+---------+----------------+   
 
Java代码
  1. EntityManager em = entityManagerFactory.createEntityManager();  
  2. em.getTransaction().begin();  
  3. Base base = new Base();  
  4. base.setBaseName("base");  
  5. em.persist(base);  
  6. Derived1 d1 = new Derived1();  
  7. d1.setBaseName("derived1's base");  
  8. d1.setDerived1Name("derived1");  
  9. em.persist(d1);  
  10. Derived2 d2 = new Derived2();  
  11. d2.setBaseName("derived2's base");  
  12. d2.setDerived2Name("derived2");  
  13. em.persist(d2);  
  14. em.getTransaction().commit();  
  15. em.close();  
    以上代码执行后,数据库中base表的数据如下(其中DTYPE列由OpenJPA自动插入,用于区分不同的class,关于Discriminator的详细用法请参考OpenJPA User's Guide):
Sql代码
  1. mysql> select * from base;  
  2. +----+-----------------+----------+--------------+--------------+  
  3. | id | baseName        | DTYPE    | derived1Name | derived2Name |  
  4. +----+-----------------+----------+--------------+--------------+  
  5. |  1 | base            | Base     | NULL         | NULL         |  
  6. |  2 | derived1's base | Derived1 | derived1     | NULL         |  
  7. |  3 | derived2's base | Derived2 | NULL         | derived2     |  
  8. +----+-----------------+----------+--------------+--------------+   
 

7.1.1 Advantages
    InheritanceType.SINGLE_TABLE 策略的优势在于简单且性能高(因为不需要使用连接查询等)。如果类的继承体系中,子类和父类间的差异主要在于行为,同时子类之间以及子类和父类之间的属性 差异不大(例如子类不增加属性或者增加的属性数目比较少),那么适用于这个策略。

7.1.2 Disadvantages
    这个策略导致规范化级别降低。由于类继承体系中的每个类的属性都要映射到表的一列,因此当类的继承体系变的复杂的时候,表也随之变大。子类中属性对应的列必须声明为nullable。

 

7.2 Joined
    InheritanceType.JOINED策略为类继承体系中的每个类创建不同的表。每个表只包含类中定义的列,因此在load一个子类的时 候,JPA实现需要同时查询子类映射的表,以及通过关联查询所有的父类映射的表。PrimaryKeyJoinColumn annotation用来指定子类映射的表如何关联到父类映射的表。它有以下属性:

  • String name: 子类映射表中的列名。如果只有一个identity filed,那么缺省使用这个field对应的列名。
  • String referencedColumnName: 父类映射表中用来关联的列名。如果只有一个identity filed,那么缺省使用这个field对应的列名。
  • String columnDefinition: 数据库中列的数据类型。只有当JPA vendor支持通过metadata创建表的时候,这个属性才被使用。

   以下是个简单的例子:

Java代码
  1. @Entity  
  2. @Inheritance(strategy=InheritanceType.JOINED)  
  3. public class Base {  
  4.     @Id  
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  6.     private int id;  
  7.       
  8.     @Basic  
  9.     private String baseName;  
  10. }  
  11.   
  12. @Entity  
  13. @PrimaryKeyJoinColumn(name="id", referencedColumnName="id")  
  14. public class Derived1 extends Base {  
  15.     @Basic  
  16.     private String derived1Name;  
  17. }  
  18.   
  19. @Entity  
  20. @PrimaryKeyJoinColumn(name="id", referencedColumnName="id")  
  21. public class Derived2 extends Base {  
  22.     @Basic  
  23.     private String derived2Name;  
  24. }  
 

   使用MappingTool建立的表结构如下:

Sql代码
  1. mysql> describe base;  
  2. +----------+--------------+------+-----+---------+----------------+  
  3. | Field    | Type         | Null | Key | Default | Extra          |  
  4. +----------+--------------+------+-----+---------+----------------+  
  5. | id       | int(11)      | NO   | PRI | NULL    | auto_increment |  
  6. | baseName | varchar(255) | YES  |     | NULL    |                |  
  7. +----------+--------------+------+-----+---------+----------------+  
  8.   
  9. mysql> describe derived1;  
  10. +--------------+--------------+------+-----+---------+-------+  
  11. | Field        | Type         | Null | Key | Default | Extra |  
  12. +--------------+--------------+------+-----+---------+-------+  
  13. | id           | int(11)      | NO   | PRI |         |       |  
  14. | derived1Name | varchar(255) | YES  |     | NULL    |       |  
  15. +--------------+--------------+------+-----+---------+-------+  
  16.   
  17. mysql> describe derived2;  
  18. +--------------+--------------+------+-----+---------+-------+  
  19. | Field        | Type         | Null | Key | Default | Extra |  
  20. +--------------+--------------+------+-----+---------+-------+  
  21. | id           | int(11)      | NO   | PRI |         |       |  
  22. | derived2Name | varchar(255) | YES  |     | NULL    |       |  
  23. +--------------+--------------+------+-----+---------+-------+  

 

Java代码
  1. EntityManager em = entityManagerFactory.createEntityManager();  
  2. em.getTransaction().begin();  
  3. Base base = new Base();  
  4. base.setBaseName("base");  
  5. em.persist(base);  
  6. Derived1 d1 = new Derived1();  
  7. d1.setBaseName("derived1's base");  
  8. d1.setDerived1Name("derived1");  
  9. em.persist(d1);  
  10. Derived2 d2 = new Derived2();  
  11. d2.setBaseName("derived2's base");  
  12. d2.setDerived2Name("derived2");  
  13. em.persist(d2);  
  14. em.getTransaction().commit();  
  15. em.close();  
    以上代码执行后,数据库中base表的数据如下:
Sql代码
  1. mysql> select * from base;  
  2. +----+-----------------+  
  3. | id | baseName        |  
  4. +----+-----------------+  
  5. |  1 | derived2's base |  
  6. |  2 | derived1's base |  
  7. |  3 | base            |  
  8. +----+-----------------+  
  9.   
  10. mysql> select * from derived1;  
  11. +----+--------------+  
  12. | id | derived1Name |  
  13. +----+--------------+  
  14. |  2 | derived1     |  
  15. +----+--------------+  
  16.   
  17. mysql> select * from derived2;  
  18. +----+--------------+  
  19. | id | derived2Name |  
  20. +----+--------------+  
  21. |  1 | derived2     |  
  22. +----+--------------+  

 

7.2.1 Advantages
    InheritanceType. JOINED策略的优势在于数据库表中没有冗余字段,因此规范化级别比较高;当有新的子类加入到类的继承体系中时,已有表的schema无须修改。如果类 的继承体系中,子类和父类间的差异不在于行为,同时子类间的属性差异比较大,那么适用于这个策略。

7.2.2 Disadvantages
    由于在查询的时候需要进行关联,那么查询的速度会比其它方式慢。此外可能需要多个插入和更新语句来处理多个表。

 

7.3 Table Per Class
    InheritanceType.TABLE_PER_CLASS策略为类继承体系中的每个类创建不同的表。和 InheritanceType.JOINED策略不同的是,每个表中包含所有的子类和父类中定义的所有列。因此在load一个子类的时候,JPA实现只 需要同时查询子类映射的表。
    以下是个简单的例子:

Java代码
  1. @Entity  
  2. @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)  
  3. public class Base {  
  4.     @Id  
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  6.     private int id;  
  7.       
  8.     @Basic  
  9.     private String baseName;  
  10. }  
  11.   
  12. @Entity  
  13. public class Derived1 extends Base {  
  14.     @Basic  
  15.     private String derived1Name;  
  16. }  
  17.   
  18. @Entity  
  19. public class Derived2 extends Base {  
  20.     @Basic  
  21.     private String derived2Name;  
  22. }  

 

   使用MappingTool建立的表结构如下:

Sql代码
  1. mysql> describe base;  
  2. +----------+--------------+------+-----+---------+----------------+  
  3. | Field    | Type         | Null | Key | Default | Extra          |  
  4. +----------+--------------+------+-----+---------+----------------+  
  5. | id       | int(11)      | NO   | PRI | NULL    | auto_increment |  
  6. | baseName | varchar(255) | YES  |     | NULL    |                |  
  7. +----------+--------------+------+-----+---------+----------------+  
  8.   
  9. mysql> describe derived1;  
  10. +--------------+--------------+------+-----+---------+----------------+  
  11. | Field        | Type         | Null | Key | Default | Extra          |  
  12. +--------------+--------------+------+-----+---------+----------------+  
  13. | id           | int(11)      | NO   | PRI | NULL    | auto_increment |  
  14. | baseName     | varchar(255) | YES  |     | NULL    |                |  
  15. | derived1Name | varchar(255) | YES  |     | NULL    |                |  
  16. +--------------+--------------+------+-----+---------+----------------+  
  17.   
  18. mysql> describe derived2;  
  19. +--------------+--------------+------+-----+---------+----------------+  
  20. | Field        | Type         | Null | Key | Default | Extra          |  
  21. +--------------+--------------+------+-----+---------+----------------+  
  22. | id           | int(11)      | NO   | PRI | NULL    | auto_increment |  
  23. | baseName     | varchar(255) | YES  |     | NULL    |                |  
  24. | derived2Name | varchar(255) | YES  |     | NULL    |                |  
  25. +--------------+--------------+------+-----+---------+----------------+  
 
Java代码
  1. EntityManager em = entityManagerFactory.createEntityManager();  
  2. em.getTransaction().begin();  
  3. Base base = new Base();  
  4. base.setBaseName("base");  
  5. em.persist(base);  
  6. Derived1 d1 = new Derived1();  
  7. d1.setBaseName("derived1's base");  
  8. d1.setDerived1Name("derived1");  
  9. em.persist(d1);  
  10. Derived2 d2 = new Derived2();  
  11. d2.setBaseName("derived2's base");  
  12. d2.setDerived2Name("derived2");  
  13. em.persist(d2);  
  14. em.getTransaction().commit();  
  15. em.close();  

    以上代码执行后,数据库中base表的数据如下: 

Sql代码
  1. mysql> select * from base;  
  2. +----+----------+  
  3. | id | baseName |  
  4. +----+----------+  
  5. |  1 | base     |  
  6. +----+----------+  
  7.   
  8. mysql> select * from derived1;  
  9. +----+-----------------+--------------+  
  10. | id | baseName        | derived1Name |  
  11. +----+-----------------+--------------+  
  12. |  1 | derived1's base | derived1     |  
  13. +----+-----------------+--------------+  
  14.   
  15. mysql> select * from derived2;  
  16. +----+-----------------+--------------+  
  17. | id | baseName        | derived2Name |  
  18. +----+-----------------+--------------+  
  19. |  1 | derived2's base | derived2     |  
  20. +----+-----------------+--------------+  

 

7.3.1 Advantages
    对于已知class类型的实例来说,这个策略十分有效。跟InheritanceType.JOINED策略类似,当有新的子类加入到类的继承体系中时,已有表的schema无须修改。

7.3.2 Disadvantages
    这个策略在处理多态关系的时候会存在很多限制,此时某个引用(或者集合中的引用)可能指向任何子类的实例。由于无法使用关联查询,因此在查询的时候可能需要使用多个SQL语句或者使用UNION。


8 Object Locking
8.1 Configuring Default Locking
    如何使用lock对load时的性能有重要的影响。OpenJPA通过openjpa.ReadLockLevel和 openjpa.WriteLockLevel来配置缺省的事务读写lock level。这些缺省配置只适用于非乐观事务;在乐观事务中,OpenJPA缺省不进行lock。在尝试获取lock时,可以通过 openjpa.LockTimeout配置最长的等待时间(缺省值-1指定没有限制),超过这个时间后OpenJPA会抛出异常。配置方式如下:

Xml代码
  1. <property name="openjpa.ReadLockLevel" value="none"/>  
  2. <property name="openjpa.WriteLockLevel" value="write"/>  
  3. <property name="openjpa.LockTimeout" value="30000"/>  

 

8.2 Configuring Lock Levels at Runtime
    在每个事务开始时,OpenJPA初始化EntityManager的缺省lock levels和time out。在运行时可以通过EntityManager的FetchPlan 接口修改这些配置。也可以通过Query相关的fetch plan修改Query级别上的配置。这些运行时的修改可以在乐观事务中使用,但是不能在事务之外进行锁定。以下是个简单的例子:

Java代码
  1. // load stock we know we're going to update at write lock mode  
  2. em.getTransaction().begin();  
  3. Query q = em.createQuery("select s from Stock s where symbol = :s");  
  4. q.setParameter("s", symbol);  
  5.   
  6. OpenJPAQuery oq = OpenJPAPersistence.cast(q);  
  7. FetchPlan fetch = oq.getFetchPlan ();  
  8. fetch.setReadLockMode(LockModeType.WRITE);  
  9. fetch.setLockTimeout(3000); // 3 seconds  
  10. Stock stock = (Stock) q.getSingleResult();  
  11.   
  12. // load an object we don't need locked at none lock mode  
  13. fetch = OpenJPAPersistence.cast(em).getFetchPlan();  
  14. fetch.setReadLockMode(null);  
  15. Market market = em.find(Market.class, marketId);  
  16.   
  17. stock.setPrice(market.calculatePrice(stock));  
  18. em.getTransaction().commit();  

 

8.3 Lock Manager
    OpenJPA 内部使用org.apache.openjpa.kernel.LockManager 处理locking相关的实际工作。可以通过openjpa.LockManager 属性进行配置,它有以下选项。

 

8.3.1 pessimistic
    这个选项是org.apache.openjpa.jdbc.kernel.PessimisticLockManager 的一个别名。它使用SELECT FOR UPDATE (或其它等效的)语句锁定跟entity实例对应的数据库行,并且不区分read locks和write locks,也就是说所有的locks都是write locks。例如:

Xml代码
  1. <property name="openjpa.LockManager" value="pessimistic(VersionCheckOnReadLock=true,VersionUpdateOnWriteLock=true)"/>  

 

8.3.2 none
    这个选项是org.apache.openjpa.kernel.NoneLockManager的一个别名。它不进行任何锁定。例如:

Xml代码
  1. <property name="openjpa.LockManager" value="none"/>  

 

8.3.3 version
    这个选项是org.apache.openjpa.kernel.VersionLockManager的一个别名。它不进行排他锁定;相反,在事务结束 时,它通过校验version来确保被read locks锁定的对象的一致性。无论被write locks锁定的对象是否被修改,其version都会被累加。为了避免脏读,事务的隔离级别应该至少为"read committed"以上。

 

8.4 Rules for Locking Behavior
    OpenJPA的隐含锁定行为有以下规则:

  1. 在事务中,当第一次读取某个对象的persistent state的时候,OpenJPA使用fetch plan中当前的read lock level锁定这个对象。未来对这个对象上的lazy persistent state的读取也采用相同的read lock level(无论fetch plan中的lock level是否改变)。
  2. 在事务中,当第一次修改某个对象的persistent state的时候,OpenJPA使用该对象第一次被读取时的write lock level(无论fetch plan中的lock level是否改变)。如果对象在之前没有被读取过,那么使用当前的write lock level。
  3. 当使用persistent relation field 访问某个对象的时候,这个对象在load过程中被当前fetch plan中的lock level锁定,而不是持有这个field的对象所"记住"的那个lock level。
  4. 在事务中,每次访问某个对象的时候,这个对象会被当前的read lock level重新锁定,并且这个read lock level会被该对象"记住"(规则1,2)。
  5. 如果显式地通过locking APIs锁定某个对象,那么这些操作都是再次锁定,并且这个lock level会被该对象"记住"(规则1,2)。
  6. 如果某个对象已经被锁定,那么尝试使用低级别的lock level再次锁定该对象会被忽略,也就是说在事务中,lock level不能被降低。 

8.5 Known Issues and Limitations
    出于性能的考虑和数据库的限制等,locking有以下限制:

  • 在乐观事务中,OpenJPA通常会直到flush或commit前才真正开始事务。当使用pessimistic lock manager时,OpenJPA必须在乐观事务中锁定某个对象时开始事务。这因为pessimistic lock manager需要使用数据库的lock。此时OpenJPA会以INFO级别在openjpa.Runtime category中输出一条日志。
  • 出于性能的考虑,OpenJPA只保证在从datastore中得到了某个对象的persistent state之后才进行锁定。这意味这其它事务可能在锁定前修改其persistent state。可以通过在锁定后refresh这个对象来确保这个对象被成功锁定。当使用pessimistic lock manager的时候,这个情况只在无法使用SELECT FOR UPDATE(或其它等效的)语句的时候,例如某些数据库不支持SELECT FOR UPDATE中使用join。此时OpenJPA会以INFO级别在openjpa.Runtime category中输出一条日志。

 

9 Enhancement
    OpenJPA使用enhancer来支持运行时性能优化、延迟加载、脏检查等功能。被enhancer修改过的字节码是Java debugger兼容的,而且很好地保留了stack trace中的行号。唯一的需要注意的是,如果采用property access,那么修改后的getter和setter方法名,在stack track中或者step-through时,会加上"pc"前缀。

 

9.1 Enhancing at Build Time
    Enhancer可以在build的时候被调用。如果对已经加强过的class再次进行加强,那么不会对class文件做更多的修改。可以在命令行上通过PCEnhancer类进行加强,例如:

Java代码
  1. java org.apache.openjpa.enhance.PCEnhancer Magazine.java  

    以下是几个可选的命令行参数: 

  • -directory/-d <output directory>: 输出的class文件的路径。
  • -enforcePropertyRestrictions/-epr <true/t | false/f>: 如果entity使用property access,但是却没有遵守相关限制的时候,是否抛出异常。缺省是false。
  • -addDefaultConstructor/-adc <true/t | false/f>: JPA规范要求所有的persistence class必须提供一个无参构造函数。这个标志指示enhancer在persistence class没有提供无参构造函数的时候,是否创建一个protected型的无参构造函数。缺省是true。
  • -tmpClassLoader/-tcl <true/t | false/f>: 是否使用临时classloader加载persistence class。 缺省是true。

9.2 Enhancing on Deployment
    在entities被部署到container的时候,Java EE 5 规范包含hooks来自动加强这些entities。 因此,如果使用Java EE 5兼容的应用服务器,OpenJPA会自动在运行时加强entities。如果某些entites使用了build时的enhancement,那么 OpenJPA运行时enhancer会识别并略过这些已经加强过的entities。

9.3 Enhancing at Runtime
    在类被载入到JVM的时候,可以使用OpenJPA agent来加强被载入的persistence class。OpenJPA agent是在应用的main方法执行之前被调用的classes。OpenJPA agent使用JVM hooks来拦截并加强被载入的包含persistence metadata的类。在每一个被载入的类中查找persistence metadata可能会减慢应用的初始化速度。可以设置persistent class list来指示agent只查找包含在persistent class list中的类。以下是使用OpenJPA agent的几个例子:

Java代码
  1. java -javaagent:/home/dev/openjpa/lib/openjpa.jar com.xyz.Main  
  2. java -javaagent:/home/openjpa/lib/openjpa.jar=addDefaultConstructor=false com.xyz.Main  

    可以使用OpenJPA's plugin syntax来为agent传递配置。此外也支持以下的选项: 

  • addDefaultConstructor: 输出的class文件的路径。
  • enforcePropertyRestrictions: 如果entity使用property access,但是却没有遵守相关限制的时候,是否抛出异常。缺省是false。
  • scanDevPath: 是否检查classpath来查找persistence class。如果没有指定persistent class list,同时设置这个标志为true,那么OpenJPA会检查每一个被载入到JVM的类。
  • classLoadEnhancement: 指定是否使用load-time class enhancement。缺省是true。
  • runtimeRedefinition: 指定是否使用class redefinition。缺省是true。

9.4 Omitting the OpenJPA enhancer
    OpenJPA并不要求必须运行enhancer。如果没有运行enhancer,OpenJPA使用以下几种可能的替代方法。

9.4.1 Java 6 class retransformation
    如果使用Java 6,那么OpenJPA会自动检测并尝试动态注册ClassTransformer来重定义persistence class。同时OpenJPA也会为persistence class创建子类。当执行query或者遍历关系的时候,OpenJPA会返回子类的对象,因此instanceof操作符仍然会正确工作,但是 o.getClass() 会返回子类类型。

9.4.2 Java 5 class redefinition
    如果使用Java 5,并指定了OpenJPA agent,那么OpenJPA会使用Java 5 class redefinition 来重定义没有被agent加强的persistence class。由于agent缺省会进行加强,因此这只有在为agent设置classLoadEnhancement 为false的时候有效(或者其它特殊情况)。

9.4.3 State comparison and subclassing
    在以上情况外,OpenJPA会创建persistence class的子类。然而在有些情况下,OpenJPA无法在访问psersistence state的时候得到通知。
    如果使用property access,那么OpenJPA会为从数据库中查询得到的对象自动创建子类,并返回这个子类作为代理。这个子类中的getter和setter方法中增 添了额外的代码以便通知OpenJPA所有对persistence state的访问。对于你自己创建的对象,由于无法使用子类代理,因此会在脏检查的时候使用state比较的方式。在这种方式下要额外保存该对象的一个 snap shot以便用于比较。
    如果使用field access,那么OpenJPA无法跟踪对psrsistence state的访问。因此OpenJPA会在脏检查的时候使用state比较的方式,代价是性能上的降低和内存使用的增加。此外,single- valued fields (one-to-one, many-to-one, and any other non-collection or non-map field that has a lazy loading configuration)上的延迟加载的相关配置也被忽略,因为OpenJPA无法跟踪最这些field的访问。



10 Miscellaneous Features
10.1 Restoring State
    JPA规范要求不应该使用rolled back objects,但是这些对象在OpenJPA中是有效的。可以通过配置openjpa.RestoreState 属性来控制是否将对象的状态回滚到事务前的状态。它有以下可选值:

  • none: 不回滚对象状态,但是对象变成hollow,在下次访问的时候会重新加载。
  • immutable: 回滚不可变的状态,可变的状态变成hollow,在下次访问的时候会重新加载。
  • all: 回滚所有状态。

10.2 Typing and Ordering
    当为对象的属性加载persistent state的时候,OpenJPA会检查这个属性在声明时或者无参构造函数中被赋值的类型。如果这个类型比声明的类型更精确,那么OpenJPA会用这个 类型。如果你在初始化某个属性的时候使用了comparator,那么OpenJPA也会在每次加载persistent state的时候使用这个comparator。例如:

Java代码
  1. public class Company {  
  2.   
  3.     // OpenJPA 会在每次加载数据的时候使用SalaryComparator。  
  4.     private Collection employeesBySal = new TreeSet(new SalaryComparator());  
  5.     private Map departments;  
  6.   
  7.     public Company {  
  8.         // OpenJPA会使用TreeMap来保存departments 相关的persistent state。  
  9.         departments = new TreeMap();  
  10.     }  
  11. }  
 

10.3 Proxies
10.3.1 Smart Proxies
    在运行时,OpenJPA可以通过代理跟踪entity 实例的属性是否被修改过,以便更有效地更新数据库。当设计persistent class的时候,应该尽可能将collection field映射成java.util.Set、java.util.TreeSet或java.util.HashSet。Smart proxy对这些类型可以进行更好的优化。

10.3.2 Large Result Set Proxies
    当遍历persistent collection 或者map属性的时候,ORM的缺省行为是把所有persistent state载入到内存中。然而,如果载入的数据量过大,那么可能会降低性能。OpenJPA为这些large result set属性使用特殊的代理。它并不在内存中缓存任何数据,相反它会访问数据库来获得相关的结果。例如large result set collection的contains方法会导致在数据库中执行类似于SELECT COUNT(*) WHERE的查询。类似地,在每次试图获得iterator的时候,OpenJPA会使用当前的large result set配置来执行特定的query。在调用iterator.next方法的时候,OpenJPA会按需载入结果对象。此时需要通过 OpenJPAPersistence.close方法来释放资源,例如:

Java代码
  1. import org.apache.openjpa.persistence.*;  
  2.   
  3. @Entity  
  4. public class Company {  
  5.   
  6.     @ManyToMany  
  7.     @LRS  
  8.     private Collection<Employee> employees;  
  9.   
  10.     ...  
  11. }  
  12.   
  13. Collection employees = company.getEmployees(); // employees is a lrs collection  
  14. Iterator itr = employees.iterator();  
  15. while (itr.hasNext()) {  
  16.     process((Employee) itr.next());  
  17. }  
  18. OpenJPAPersistence.close(itr);  
 

   Large result set 属性只能被声明成java.util.Collection 或者java.util.Map;它不能包含externalizer;Large result set proxy不能从一个entity 实例转移到另外一个entity 实例中,例如以下代码会导致提交时的一个错误:

Java代码
  1. Collection employees = company.getEmployees()  // employees is a lrs collection  
  2. company.setEmployees(null);  
  3. anotherCompany.setEmployees(employees);  
 

10.3.3 Custom Proxies
    OpenJPA通过org.apache.openjpa.util.ProxyManager接口管理代理。其缺省的实现是org.apache.openjpa.util.ProxyManagerImpl。它有以下配置属性:

  • TrackChanges: 是否使用smart proxy,缺省是true。
  • AssertAllowedType: 在向collection或map中加入元素的时候,如果跟metadata 中声明的类型不符,是否抛出异常,缺省是false。

   以下是个简单的例子:

Xml代码
  1. <property name="openjpa.ProxyManager" value="TrackChanges=false"/>  
    缺省的proxy manager可以代理 Collection, List, Map, Queue, Date, or Calendar 等类上的标准方法,也可以代理定制类型,但是这些定制类型必须复合以下条件: 
  • Custom container types必须有一个公共的无参构造函数,或者一个公共的以Comparator为参数类型的构造函数。
  • Custom date types必须有一个公共的无参构造函数,或者一个公共的以long为参数 类型(代表当前的时间)的构造函数。
  • Other custom types必须有一个公共的无参构造函数,或者公共的拷贝构造函数。如果没有公共的拷贝构造函数,那么在进行拷贝的时候,首先会创建一个对象B,然后通过 以A上getter方法的返回值为参数,调用B的setter方法。 因此你要确保通过这种方式,B是A的完整拷贝。

   如果某个custom classes无法满足以上条件,那么OpenJPA允许你定义你自己的proxy class和proxy manager。 


10.4 Externalization
    OpenJPA 支持通过custom field mappings 来完全控制entity class的field在datastore中怎样被保存、查询和加载。然而一个更轻量级的方法是使用externalization 。Externalization 支持通过指定某些方法来控制保存和加载的方式。需要注意的是,不能在@EmbeddedId字段上使用externalization。
    OpenJPA使用org.apache.openjpa.persistence.Externalizer annotation指定将某个属性转换为external value的方法名。如果指定一个non-static方法,那么OpenJPA会假定目标对象是被Externalizer标记的属性对象;如果指定一 个static方法, 那么被OpenJPA会把被Externalizer标记的属性对象作为一个方法的参数。每个方法也可以接受一个StoreContext 型的参数。方法的返回值指定了缺省的external 类型。假设希望将某个CustomType类型的属性转换成String类型,那么可以采用以下的方法:

MethodExtension
public String CustomType.toString()@Externalizer("toString")
public String CustomType.toString(StoreContext ctx)@Externalizer("toString")
public static String AnyClass.toString(CustomType ct)@Externalizer("AnyClass.toString")
public static String AnyClass.toString(CustomType ct, StoreContext ctx)@Externalizer("AnyClass.toString")

 

   OpenJPA使用org.apache.openjpa.persistence.Factory annotation指定根据external value初始化某个属性的方法名。 如果指定static方法,那么这个方法必须返回这个属性类型的一个实例。这个方法可以接受StoreContext 类型的一个参数。如果没有指定factory annotation,那么这个属性的class必须包含以external form为参数的构造函数,否则会抛出一个异常。假设希望将String类型转换成某个CustomType类型,那么可以采用以下的方法:

MethodExtension
public CustomType(String str)none
public static CustomType CustomType.fromString(String str)@Factory("fromString")
public static CustomType CustomType.fromString(String str, StoreContext ctx)@Factory("fromString")
public static CustomType AnyClass.fromString(String str)@Factory("AnyClass.fromString")
public static CustomType AnyClass.fromString(String str, StoreContext ctx)@Factory("AnyClass.fromString")

 

   OpenJPA使用org.apache.openjpa.persistence.ExternalValues annotation指定external value的转换。 其value pairs用于指定Java和datastore的类型。如果datastore的类型不同于Java类型,那么通过 org.apache.openjpa.persistence.Type annotation 指定datastore类型。如果externalized属性不是标准的persistent type,那么必须用org.apache.openjpa.persistence.Persistent annotation显式地进行标记。如果externalized 属性是可变的,而且不是collection、map和date类型,那么OpenJPA无法进行脏检查。可以手动进行标记为dirty,或者使用 custom field proxy。以下是个简单的例子:

Java代码
  1. @Entity  
  2. public class Externalization {  
  3.     @Id  
  4.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  5.     private Integer id;  
  6.       
  7.     @Persistent  
  8.     @Externalizer("getName")  
  9.     @Factory("forName")  
  10.     private Class clazz;  
  11.       
  12.     @Persistent  
  13.     @Factory("Externalization.stringToURL")  
  14.     @Externalizer("Externalization.urlToString")  
  15.     private URL url;  
  16.       
  17.     @Basic  
  18.     @ExternalValues({"SMALL=5""MEDIUM=8""LARGE=10"})  
  19.     @Type(int.class)  
  20.     private String size;  
  21.       
  22.     public static URL stringToURL(String s) {  
  23.         try {  
  24.             return new URL(s);  
  25.         } catch (MalformedURLException e) {  
  26.             throw new RuntimeException(e);  
  27.         }  
  28.     }  
  29.       
  30.     public static String urlToString(URL url) {  
  31.         return url.toExternalForm();  
  32.     }  
  33.       
  34.     public String toString() {  
  35.         StringBuffer sb = new StringBuffer();  
  36.         sb.append("id: ").append(id);  
  37.         sb.append(", clazz: ").append(clazz);  
  38.         sb.append(", url: ").append(url);  
  39.         sb.append(", size: ").append(size);  
  40.         return sb.toString();  
  41.     }  
  42. }  
  43.   
  44. EntityManager em = entityManagerFactory.createEntityManager();  
  45. em.getTransaction().begin();  
  46. Externalization e1 = new Externalization();  
  47. e1.setClazz(Externalization.class);  
  48. e1.setUrl(new URL("http://www.abc.com"));  
  49. e1.setSize("MEDIUM");  
  50. em.persist(e1);  
  51. em.getTransaction().commit();  
  52. em.close();  
  53.   
  54. em = entityManagerFactory.createEntityManager();  
  55. Query q = em.createQuery("select m from Externalization m where m.url = 'http://www.abc.com'");  
  56. List<Externalization> r1 = (List<Externalization>)q.getResultList();  
  57. for(Iterator iter = r1.iterator(); iter.hasNext(); ) {  
  58.     System.out.println(iter.next().toString());  
  59. }  
  60. em.close();  
    以上代码执行完毕后数据库中externalization表的数据如下: 
Sql代码
  1. mysql> select * from externalization;  
  2. +----+-----------------+------+--------------------+  
  3. | id | clazz           | size | url                |  
  4. +----+-----------------+------+--------------------+  
  5. |  1 | Externalization |    8 | http://www.abc.com |  
  6. +----+-----------------+------+--------------------+  
 

10.5 Fetch Groups
    Fetch groups是一组同时被加载的属性。之前介绍的Fetch Type指定了如何通过annotations指定某个属性是eagerly还是 lazily 加载。Fetch groups 则是提供了动态指定加载方式的能力。.

10.5.1 Custom Fetch Groups
    OpenJPA缺省fetch group中的属性都会被eagerly加载。此外可以通过org.apache.openjpa.persistence.FetchGroup annotation定义named fetch groups以便在运行时激活它。在加载的时候,OpenJPA会eagerly加载所有被激活的fetch groups中的属性。FetchGroup annotation有以下属性:

  • String name: fetch group 的全局名。以下名字被OpenJPA保留:default 、values、 all、none以及任何以jdo、jpa或openjpa开头的名字。
  • FetchAttribute[] attributes: fetch group中persistent fileds或者properties数组。
  • String[] fetchGroups: 包含在次fetch group中的其它fetch groups名。

   org.apache.openjpa.persistence.FetchAttribute annotation的属性如下: 

  • String name: persistent field 或者 property 名。
  • recursionDepth: eager-fetch 的递归深度,缺省是1(-1无限制)。

   Entity class的某个属性可以包含在任何的fetch group中,它也可以声明其load fetch group。缺省情况下,OpenJPA在第一次访问lazy-loaded属性的时候访问数据库。然而如果你直到当你访问某个lazy-loaded属 性A的时候,你很可能也同时访问lazy-loaded 属性B和C,因此在一次数据库访问中同时加载A、B和C是更有效的。  
    通过org.apache.openjpa.persistence.LoadFetchGroup annotation为某个属性指定load fetch group。以下是个简单的例子:

Java代码
  1. import org.apache.openjpa.persistence.*;  
  2.   
  3. @Entity  
  4. @FetchGroups({  
  5.     @FetchGroup(name="detail", attributes={  
  6.         @FetchAttribute(name="publisher"),  
  7.         @FetchAttribute(name="articles")  
  8.     })  
  9. })  
  10. public class Magazine {  
  11.   
  12.    @ManyToOne(fetch=FetchType.LAZY)  
  13.    @LoadFetchGroup("detail")  
  14.    private Publisher publisher;  
  15. }  
 

10.5.2 Custom Fetch Group Configuration
    OpenJPAEntityManager接口和OpenJPAQuery接口可以用于访问 org.apache.openjpa.persistence.FetchPlan。FetchPlan通过以下方法维护当前活跃的fetch groups和maximum fetch depth:

Java代码
  1. public FetchPlan addFetchGroup(String group);  
  2. public FetchPlan addFetchGroups(String... groups);  
  3. public FetchPlan addFetchGroups(Collection groups);  
  4. public FetchPlan removeFetchGrop(String group);  
  5. public FetchPlan removeFetchGroups(String... groups);  
  6. public FetchPlan removeFetchGroups(Collection groups);  
  7. public FetchPlan resetFetchGroups();  
  8. public Collection<String> getFetchGroups();  
  9. public void clearFetchGroups();  
  10. public FetchPlan setMaxFetchDepth(int depth);  
  11. public int getMaxFetchDepth();  
    Maximum fetch depth指定了加载时遍历对象的深度,缺省值是-1(无限制)。如果MaxFetchDepth是1,那么OpenJPA将会加载目标实例和它的直接 relations; 如果MaxFetchDepth是2,那么OpenJPA会加载目标实例、直接relations和直接relations上的relations;如果 MaxFetchDepth是-1,那么OpenJPA会加载目标实例以及所有的relatons直到到达对象图的边缘。当然以上的加载过程依赖于被加载 属性是否是eagerly load,以及当前活跃的fetch group。以下是个简单的例子: 
Java代码
  1. OpenJPAQuery kq = OpenJPAPersistence.cast(em.createQuery(...));  
  2. kq.getFetchPlan().setMaxFetchDepth(3).addFetchGroup("detail");  
  3. List results = kq.getResultList();  

 

10.5.3 Per-field Fetch Configuration
    除了基于per-fetch-group 的配置外,OpenJPA也支持基于per-field的配置。通过以下方法将特定的field包含到当前的fetch plan中。

Java代码
  1. public FetchPlan addField(String field);  
  2. public FetchPlan addFields(String... fields);  
  3. public FetchPlan addFields(Class cls, String... fields);  
  4. public FetchPlan addFields(Collection fields);  
  5. public FetchPlan addFields(Class cls, Collection fields);  
  6. public FetchPlan removeField(String field);  
  7. public FetchPlan removeFields(String... fields);  
  8. public FetchPlan removeFields(Class cls, String... fields);  
  9. public FetchPlan removeFields(Collection fields);  
  10. public FetchPlan removeFields(Class cls, Collection fields);  
  11. public Collection<String> getFields();  
  12. public void clearFields();  
    需要注意的是,以上方法中的field必须定义在指定的类中,而不是在其父类中。如果field publisher被定义在Publication中,而不是其子类Magazine中,那么必须用 addField (Publication.class, "publisher"),而不是addField (Magazine.class, "publisher")。出于性能上的考虑,OpenJPA并不对class name / field name 对进行验证。如果指定了不存在的class name / field name 对,那么会被OpenJPA忽略。以下是个简单的例子: 
Java代码
  1. OpenJPAEntityManager kem = OpenJPAPersistence.cast(em);  
  2. kem.getFetchPlan().addField(Magazine.class"publisher");  
  3. Magazine mag = em.find(Magazine.class, magId); 


posted on 2010-09-01 22:39 礼物 阅读(4089) 评论(1)  编辑  收藏 所属分类: JPA

评论

# re: OpenJPA 2012-11-13 12:51 tanzijie11
真的是太全了。是在是太感谢了  回复  更多评论
  


只有注册用户登录后才能发表评论。

网站导航: