OpenJPA (7)

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。例如:

public class Company {

    // OpenJPA 会在每次加载数据的时候使用SalaryComparator。
    private Collection employeesBySal = new TreeSet(new SalaryComparator());
    private Map departments;

    public Company {
        // OpenJPA会使用TreeMap来保存departments 相关的persistent state。
        departments = new TreeMap();
    }
}
 

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方法来释放资源,例如:

import org.apache.openjpa.persistence.*;

@Entity
public class Company {

    @ManyToMany
    @LRS
    private Collection<Employee> employees;

    ...
}

Collection employees = company.getEmployees(); // employees is a lrs collection
Iterator itr = employees.iterator();
while (itr.hasNext()) {
    process((Employee) itr.next());
}
OpenJPAPersistence.close(itr);
 

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

Collection employees = company.getEmployees()  // employees is a lrs collection
company.setEmployees(null);
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。

   以下是个简单的例子:

<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类型,那么可以采用以下的方法:

Method Extension
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类型,那么可以采用以下的方法:

Method Extension
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。以下是个简单的例子:

@Entity
public class Externalization {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Persistent
	@Externalizer("getName")
    @Factory("forName")
	private Class clazz;
	
	@Persistent
	@Factory("Externalization.stringToURL")
    @Externalizer("Externalization.urlToString")
	private URL url;
	
	@Basic
	@ExternalValues({"SMALL=5", "MEDIUM=8", "LARGE=10"})
    @Type(int.class)
    private String size;
	
	public static URL stringToURL(String s) {
		try {
			return new URL(s);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}
	
	public static String urlToString(URL url) {
		return url.toExternalForm();
	}
	
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("id: ").append(id);
		sb.append(", clazz: ").append(clazz);
		sb.append(", url: ").append(url);
		sb.append(", size: ").append(size);
		return sb.toString();
	}
}

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Externalization e1 = new Externalization();
e1.setClazz(Externalization.class);
e1.setUrl(new URL("http://www.abc.com"));
e1.setSize("MEDIUM");
em.persist(e1);
em.getTransaction().commit();
em.close();

em = entityManagerFactory.createEntityManager();
Query q = em.createQuery("select m from Externalization m where m.url = 'http://www.abc.com'");
List<Externalization> r1 = (List<Externalization>)q.getResultList();
for(Iterator iter = r1.iterator(); iter.hasNext(); ) {
	System.out.println(iter.next().toString());
}
em.close();

    以上代码执行完毕后数据库中externalization表的数据如下: 

mysql> select * from externalization;
+----+-----------------+------+--------------------+
| id | clazz           | size | url                |
+----+-----------------+------+--------------------+
|  1 | Externalization |    8 | http://www.abc.com |
+----+-----------------+------+--------------------+
 

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。以下是个简单的例子:

import org.apache.openjpa.persistence.*;

@Entity
@FetchGroups({
    @FetchGroup(name="detail", attributes={
        @FetchAttribute(name="publisher"),
        @FetchAttribute(name="articles")
    })
})
public class Magazine {

   @ManyToOne(fetch=FetchType.LAZY)
   @LoadFetchGroup("detail")
   private Publisher publisher;
}
 

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

public FetchPlan addFetchGroup(String group);
public FetchPlan addFetchGroups(String... groups);
public FetchPlan addFetchGroups(Collection groups);
public FetchPlan removeFetchGrop(String group);
public FetchPlan removeFetchGroups(String... groups);
public FetchPlan removeFetchGroups(Collection groups);
public FetchPlan resetFetchGroups();
public Collection<String> getFetchGroups();
public void clearFetchGroups();
public FetchPlan setMaxFetchDepth(int depth);
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。以下是个简单的例子: 

OpenJPAQuery kq = OpenJPAPersistence.cast(em.createQuery(...));
kq.getFetchPlan().setMaxFetchDepth(3).addFetchGroup("detail");
List results = kq.getResultList();

 

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

public FetchPlan addField(String field);
public FetchPlan addFields(String... fields);
public FetchPlan addFields(Class cls, String... fields);
public FetchPlan addFields(Collection fields);
public FetchPlan addFields(Class cls, Collection fields);
public FetchPlan removeField(String field);
public FetchPlan removeFields(String... fields);
public FetchPlan removeFields(Class cls, String... fields);
public FetchPlan removeFields(Collection fields);
public FetchPlan removeFields(Class cls, Collection fields);
public Collection<String> getFields();
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忽略。以下是个简单的例子: 

OpenJPAEntityManager kem = OpenJPAPersistence.cast(em);
kem.getFetchPlan().addField(Magazine.class, "publisher");
Magazine mag = em.find(Magazine.class, magId);

你可能感兴趣的:(apache,openjpa)