8 Object Locking
8.1 Configuring Default Locking
如何使用lock对load时的性能有重要的影响。OpenJPA通过openjpa.ReadLockLevel和openjpa.WriteLockLevel来配置缺省的事务读写lock level。这些缺省配置只适用于非乐观事务;在乐观事务中,OpenJPA缺省不进行lock。在尝试获取lock时,可以通过openjpa.LockTimeout配置最长的等待时间(缺省值-1指定没有限制),超过这个时间后OpenJPA会抛出异常。配置方式如下:
<property name="openjpa.ReadLockLevel" value="none"/> <property name="openjpa.WriteLockLevel" value="write"/> <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级别上的配置。这些运行时的修改可以在乐观事务中使用,但是不能在事务之外进行锁定。以下是个简单的例子:
// load stock we know we're going to update at write lock mode
em.getTransaction().begin();
Query q = em.createQuery("select s from Stock s where symbol = :s");
q.setParameter("s", symbol);
OpenJPAQuery oq = OpenJPAPersistence.cast(q);
FetchPlan fetch = oq.getFetchPlan ();
fetch.setReadLockMode(LockModeType.WRITE);
fetch.setLockTimeout(3000); // 3 seconds
Stock stock = (Stock) q.getSingleResult();
// load an object we don't need locked at none lock mode
fetch = OpenJPAPersistence.cast(em).getFetchPlan();
fetch.setReadLockMode(null);
Market market = em.find(Market.class, marketId);
stock.setPrice(market.calculatePrice(stock));
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。例如:
<property name="openjpa.LockManager" value="pessimistic(VersionCheckOnReadLock=true,VersionUpdateOnWriteLock=true)"/>
8.3.2 none
这个选项是org.apache.openjpa.kernel.NoneLockManager的一个别名。它不进行任何锁定。例如:
<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的隐含锁定行为有以下规则:
- 在事务中,当第一次读取某个对象的persistent state的时候,OpenJPA使用fetch plan中当前的read lock level锁定这个对象。未来对这个对象上的lazy persistent state的读取也采用相同的read lock level(无论fetch plan中的lock level是否改变)。
- 在事务中,当第一次修改某个对象的persistent state的时候,OpenJPA使用该对象第一次被读取时的write lock level(无论fetch plan中的lock level是否改变)。如果对象在之前没有被读取过,那么使用当前的write lock level。
- 当使用persistent relation field 访问某个对象的时候,这个对象在load过程中被当前fetch plan中的lock level锁定,而不是持有这个field的对象所"记住"的那个lock level。
- 在事务中,每次访问某个对象的时候,这个对象会被当前的read lock level重新锁定,并且这个read lock level会被该对象"记住"(规则1,2)。
- 如果显式地通过locking APIs锁定某个对象,那么这些操作都是再次锁定,并且这个lock level会被该对象"记住"(规则1,2)。
- 如果某个对象已经被锁定,那么尝试使用低级别的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 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 -javaagent:/home/dev/openjpa/lib/openjpa.jar com.xyz.Main
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的访问。