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的隐含锁定行为有以下规则:
8.5 Known Issues and Limitations
出于性能的考虑和数据库的限制等,locking有以下限制:
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
以下是几个可选的命令行参数:
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传递配置。此外也支持以下的选项:
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的访问。