由前面的文章大致知道,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用户所期望的。