由前面的文章大致知道,Play的事务由过滤器中处理,这里理一下Play框架与数据库相关的部分。
主要是play.db包中的DBPlugin/DB类,与play.db.jpa包中的JPAPlugin/JPA类有关,前者管理数据源,后者管理JPA。另外因为play是基于ActiveRecord模型,在play.db.jpa.JPAEnhancer类中,play织入了许多辅助方法。
DBPlugin/JPAPlugin类属于PlayPlugin子类,在play发出的事件中处理相关操作。因为DBPlugin的index比JPAPlugin的大,所以DBPlugin的事件处理会在JPAPlugin的前面。这里需要注意play处理顺序,在前半部分请求是从0到100000,后半部分响应时是从100000到0处理,所以整个处理过程是0、100、1000、...1000、100、0。
0:play.CorePlugin 100:play.data.parsing.TempFilePlugin 200:play.data.validation.ValidationPlugin 300:play.db.DBPlugin 400:play.db.jpa.JPAPlugin 450:play.db.Evolutions 500:play.i18n.MessagesPlugin 600:play.libs.WS 700:play.jobs.JobsPlugin 100000:play.plugins.ConfigurablePluginDisablingPlugin
从配置文件中加载数据源主要发生在onApplicationStart,并可配置多个数据源。在配置数据源的时候,play会试探性连接,用来判断数据源是否可用。
public class DBPlugin extends PlayPlugin { @Override public void onApplicationStart() { if (changed()) { String dbName = ""; try { ... Set dbNames = Configuration.getDbNames(); Iterator it = dbNames.iterator(); while(it.hasNext()) { dbName = it.next(); ... if (isJndiDatasource || datasourceName.startsWith("java:")) { ... } else { ... url = ds.getJdbcUrl(); Connection c = null; try { c = ds.getConnection(); } finally { if (c != null) { c.close(); } } Logger.info("Connected to %s for %s", ds.getJdbcUrl(), dbName); DB.datasources.put(dbName, extDs); } } } catch (Exception e) { ... } } } }
之前写过一篇基于play 1.2.3的多数据库文章: 多数据库切换。 JPAPlugin中,处理逻辑较多。主要集中在下面四个方法:
public class JPAPlugin extends PlayPlugin { @Override public Object bind(RootParamNode rootParamNode, String name, Class clazz, java.lang.reflect.Type type, Annotation[] annotations) {....} @Override public void enhance(ApplicationClass applicationClass) throws Exception {...} @Override public void onApplicationStart() {...} @Override public Filter getFilter() {...} }
bind方法,将请求中域对象加入HibernateSession之中。
enhance方法,为基于play.db.jpa.JPABase的子Model织入相应辅助代码。
onApplicationStart方法,根据数据配置实例工厂。
getFilter方法,获取过滤器,这里就是事务相关的过滤器。
在play.db.jpa.JPA:withTransaction()方法中,可以看出,play已经可以同时管理多个数据库的事务,在1.2.3版本这是没有见过。
public class JPA { public static T withTransaction(String dbName, boolean readOnly, F.Function0 block) throws Throwable { if (isEnabled()) { boolean closeEm = true; // For each existing persisence unit try { // we are starting a transaction for all known persistent unit // this is probably not the best, but there is no way we can know where to go from // at this stage for (String name : emfs.keySet()) { EntityManager localEm = JPA.newEntityManager(name); JPA.bindForCurrentThread(name, localEm, readOnly); if (!readOnly) { localEm.getTransaction().begin(); } } T result = block.apply(); boolean rollbackAll = false; // Get back our entity managers // Because people might have mess up with the current entity managers for (JPAContext jpaContext : get().values()) { EntityManager m = jpaContext.entityManager; EntityTransaction localTx = m.getTransaction(); // The resource transaction must be in progress in order to determine if it has been marked for rollback if (localTx.isActive() && localTx.getRollbackOnly()) { rollbackAll = true; } } for (JPAContext jpaContext : get().values()) { EntityManager m = jpaContext.entityManager; boolean ro = jpaContext.readonly; EntityTransaction localTx = m.getTransaction(); // transaction must be active to make some rollback or commit if (localTx.isActive()) { if (rollbackAll || ro) { localTx.rollback(); } else { localTx.commit(); } } } return result; } catch (...) { } finally { ... } } else { return block.apply(); } } }
对于每个数据库实例逐一开启事务,处理用户代码之后,检查每个实例,如果有一个回滚,则所有事务都回滚,如果没有则逐一提交。
虽然Play2.x已经横在前方,但是Play1.x还在演进,我想这是所有Play1.x用户所期望的。