上兩篇文章主要分析activiti數據庫相關初始化、sessionFactory初始化及創建實體管理類的原理。實體管理類封裝了activiti底層增刪查改操作。上層命令類程序不需要直接去調用mybatis的sqlSession,通過實體管理類間接調用。本文通過例子分析實體管理類是如何調用mybaits的。
我們以DeploymentEntityManager爲例:
public void insertDeployment(DeploymentEntity deployment) {
getDbSqlSession().insert(deployment);
for (ResourceEntity resource : deployment.getResources().values()) {
resource.setDeploymentId(deployment.getId());
getResourceManager().insertResource(resource);
}
}
上面是當部署流程文檔時,調用deploymentBuilder.deploy()部署,最終調用DeploymentEntityManager的insertDeployment方法插入流程文檔到ACT_RE_DEPLOYMENT表。第2行getDbSqlSession()獲取DbSqlSession的實例,然後調用insert方法把DeploymentEntity插入到ACT_RE_DEPLOYMENT表。4-7行則DeploymentEntity對應的資源插入到ACT_GE_BYTEARRAY中。
接下來看看DbSqlSession.java的insert方法:
public void insert(PersistentObject persistentObject) {
if (persistentObject.getId()==null) {
String id = dbSqlSessionFactory.getIdGenerator().getNextId();
persistentObject.setId(id);
}
Class<? extends PersistentObject> clazz = persistentObject.getClass();
if (!insertedObjects.containsKey(clazz)) {
insertedObjects.put(clazz, new ArrayList<PersistentObject>());
}
insertedObjects.get(clazz).add(persistentObject);
cachePut(persistentObject, false);
}
protected CachedObject cachePut(PersistentObject persistentObject, boolean storeState) {
Map<String, CachedObject> classCache = cachedObjects.get(persistentObject.getClass());
if (classCache==null) {
classCache = new HashMap<String, CachedObject>();
cachedObjects.put(persistentObject.getClass(), classCache);
}
CachedObject cachedObject = new CachedObject(persistentObject, storeState);
classCache.put(persistentObject.getId(), cachedObject);
return cachedObject;
}
所有實體類XXXEntity都實現PersistentObject接口。第2行如果該對象沒有設置id的話,第3-4行則通過id生成器生成其id並設置之。第7-12行把待插入對象添加到insertedObjects中。insertedObjects是一個以插入對象class爲key,以ArrayList爲value的HashMap對象。插入操作根據不同的插入對象的類型,添加到insertedObjects不同的隊列中。此時新增對象並未插入到數據庫中,還保留在隊列裏。cachePut方法會把準備插入的內容緩存到cachedObjects中。若在把新增對象刷新到數據庫前發生修改、刪除、查詢等操作時,則操作cachedObjects中的數據即可。
數據已經被保存到緩存裏了,那什麼時候刷新到數據庫呢?還記得《activiti學習(八)——自定義攔截器和命令類》一文中提到攔截器調用鏈,如下圖所示:
刷新的操作在命令執行結束後,回調到上下文攔截器的時候觸發,我們查看CommandContextInterceptor的execute方法:
public <T> T execute(CommandConfig config, Command<T> command) {
CommandContext context = Context.getCommandContext();
boolean contextReused = false;
if (!config.isContextReusePossible() || context == null || context.getException() != null) {
context = commandContextFactory.createCommandContext(command);
}
else {
log.debug("Valid context found. Reusing it for the current command '{}'", command.getClass().getCanonicalName());
contextReused = true;
}
try {
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(processEngineConfiguration);
return next.execute(config, command);
} catch (Exception e) {
context.exception(e);
} finally {
try {
if (!contextReused) {
context.close();
}
} finally {
Context.removeCommandContext();
Context.removeProcessEngineConfiguration();
Context.removeBpmnOverrideContext();
}
}
return null;
}
15行執行調用鏈,待命令類的execute方法調用完畢後,如果設置了上下文不重用,則執行21行context.close方法,把緩存刷到數據庫的調用就在這裏面。接着我們看CommandContext的close方法:
public void close() {
try {
try {
try {
//......省略
if (exception == null) {
flushSessions();
}
}
//......省略
} catch (Throwable exception) {
exception(exception);
} finally {
closeSessions();
}
} catch (Throwable exception) {
exception(exception);
}
//.....省略
}
protected void flushSessions() {
for (Session session : sessions.values()) {
session.flush();
}
}
第9行調用flushSessions刷新會話,29行調用DbSqlSession的flush方法。下面看DbSqlSession的flush方法是如何刷新數據的:
public void flush() {
List<DeleteOperation> removedOperations = removeUnnecessaryOperations();
flushDeserializedObjects();
List<PersistentObject> updatedObjects = getUpdatedObjects();
if (log.isDebugEnabled()) {
Collection<List<PersistentObject>> insertedObjectLists = insertedObjects.values();
int nrOfInserts = 0, nrOfUpdates = 0, nrOfDeletes = 0;
for (List<PersistentObject> insertedObjectList: insertedObjectLists) {
for (PersistentObject insertedObject : insertedObjectList) {
log.debug(" insert {}", insertedObject);
nrOfInserts++;
}
}
for (PersistentObject updatedObject: updatedObjects) {
log.debug(" update {}", updatedObject);
nrOfUpdates++;
}
for (DeleteOperation deleteOperation: deleteOperations) {
log.debug(" {}", deleteOperation);
nrOfDeletes++;
}
log.debug("flush summary: {} insert, {} update, {} delete.", nrOfInserts, nrOfUpdates, nrOfDeletes);
log.debug("now executing flush...");
}
flushInserts();
flushUpdates(updatedObjects);
flushDeletes(removedOperations);
}
第2行removeUnnecessaryOperations移除不必要的操作,具體的意思是這樣:命令類的操作把添加的記錄放在insertedObjects緩存中,在deleteOperations中添加了需要刪除的操作,removeUnnecessaryOperations函數匹配在insertedObjects中是否存在deleteOperations需要刪除的數據,如果有,則把這個對象從insertedObjects中剔除,因爲沒必要先進行插入,然後又做刪除這樣無意義的操作。詳細源碼可以讀者可自行分析。第3行flushDeserializedObjects刷新序列化變量,把流程中實現了Serializable接口的變量刷新到數據庫中。第4行獲取更新對象。27行刷新插入對象,28行刷新更新對象,29行刷新刪除操作。我們暫時只看插入操作,跟蹤flushInserts:
protected void flushInserts() {
for (Class<? extends PersistentObject> persistentObjectClass : EntityDependencyOrder.INSERT_ORDER) {
if (insertedObjects.containsKey(persistentObjectClass)) {
flushPersistentObjects(persistentObjectClass, insertedObjects.get(persistentObjectClass));
insertedObjects.remove(persistentObjectClass);
}
}
if (insertedObjects.size() > 0) {
for (Class<? extends PersistentObject> persistentObjectClass : insertedObjects.keySet()) {
flushPersistentObjects(persistentObjectClass, insertedObjects.get(persistentObjectClass));
}
}
insertedObjects.clear();
}
protected void flushPersistentObjects(Class<? extends PersistentObject> persistentObjectClass, List<PersistentObject> persistentObjectsToInsert) {
if (persistentObjectsToInsert.size() == 1) {
flushRegularInsert(persistentObjectsToInsert.get(0), persistentObjectClass);
} else if (Boolean.FALSE.equals(dbSqlSessionFactory.isBulkInsertable(persistentObjectClass))) {
for (PersistentObject persistentObject : persistentObjectsToInsert) {
flushRegularInsert(persistentObject, persistentObjectClass);
}
} else {
flushBulkInsert(insertedObjects.get(persistentObjectClass), persistentObjectClass);
}
}
protected void flushRegularInsert(PersistentObject persistentObject, Class<? extends PersistentObject> clazz) {
String insertStatement = dbSqlSessionFactory.getInsertStatement(persistentObject);
insertStatement = dbSqlSessionFactory.mapStatement(insertStatement);
if (insertStatement==null) {
throw new ActivitiException("no insert statement for " + persistentObject.getClass() + " in the ibatis mapping files");
}
log.debug("inserting: {}", persistentObject);
sqlSession.insert(insertStatement, persistentObject);
if (persistentObject instanceof HasRevision) {
((HasRevision) persistentObject).setRevision(((HasRevision) persistentObject).getRevisionNext());
}
}
protected void flushBulkInsert(List<PersistentObject> persistentObjectList, Class<? extends PersistentObject> clazz) {
String insertStatement = dbSqlSessionFactory.getBulkInsertStatement(clazz);
insertStatement = dbSqlSessionFactory.mapStatement(insertStatement);
if (insertStatement==null) {
throw new ActivitiException("no insert statement for " + persistentObjectList.get(0).getClass() + " in the ibatis mapping files");
}
if (persistentObjectList.size() <= dbSqlSessionFactory.getMaxNrOfStatementsInBulkInsert()) {
sqlSession.insert(insertStatement, persistentObjectList);
} else {
for (int start = 0; start < persistentObjectList.size(); start += dbSqlSessionFactory.getMaxNrOfStatementsInBulkInsert()) {
List<PersistentObject> subList = persistentObjectList.subList(start,
Math.min(start + dbSqlSessionFactory.getMaxNrOfStatementsInBulkInsert(), persistentObjectList.size()));
sqlSession.insert(insertStatement, subList);
}
}
if (persistentObjectList.get(0) instanceof HasRevision) {
for (PersistentObject insertedObject: persistentObjectList) {
((HasRevision) insertedObject).setRevision(((HasRevision) insertedObject).getRevisionNext());
}
}
}
2-5行根據EntityDependencyOrder初始化時的順序逐個實體類型調用flushPersistentObjects插入數據庫。activiti定義了插入和刪除的先後順序,解決依賴的問題。8-12行插入用戶自定義的實體類型。17行判斷被插入的實體數量,如果爲1個,則使用常規插入,19行判斷改類型是否可批量插入,不可的話20-22行仍然是常規插入,否則走24行批量插入。28-42行是常規插入。29行獲取插入語句,其實這個不是指sql語句,是指mybatis的xml文件裏sql語句對應的id。30行鍼對特殊語句,不同數據庫要替換成不同的語句,這體現了activiti如何屏蔽數據庫差異。37行調用mybatis的sqlSession的insert方法進行插入。39行判斷如果實體類實現了HasRevision接口,則設置其revision值。revision用於樂觀鎖。activiti使用樂觀鎖對數據版本進行控制。各位可以結合映射文件裏的語句研究。我們看看dbSqlSessionFactory.getInsertStatement函數具體怎麼獲取語句,DbSqlSessionFactory.java:
public String getInsertStatement(PersistentObject object) {
return getStatement(object.getClass(), insertStatements, "insert");
}
private String getStatement(Class<?> persistentObjectClass, Map<Class<?>,String> cachedStatements, String prefix) {
String statement = cachedStatements.get(persistentObjectClass);
if (statement!=null) {
return statement;
}
statement = prefix + persistentObjectClass.getSimpleName();
statement = statement.substring(0, statement.length()-6); // removing 'entity'
cachedStatements.put(persistentObjectClass, statement);
return statement;
}
第2行調用getStatement函數。第6-10行實質是查看insertStatements緩存中是否已保存該實體類型對應的插入語句,如果有則直接返回。11-12行先爲實體類加上前綴,再去掉實體類的“Entity”。以本文一開始通過Deployment舉例,這裏插入的是DeploymentEntity,首先在前面添加“insert”前綴,再去掉“Entity”,最後statment爲“insertDeployment”。結合mybatis的映射,我們查找org/activiti/db/mapping/entity/Deployment.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.activiti.engine.impl.persistence.entity.DeploymentEntity">
<!-- DEPLOYMENT INSERT -->
<insert id="insertDeployment" parameterType="org.activiti.engine.impl.persistence.entity.DeploymentEntity">
insert into ${prefix}ACT_RE_DEPLOYMENT(ID_, NAME_, CATEGORY_, TENANT_ID_, DEPLOY_TIME_)
values(#{id, jdbcType=VARCHAR}, #{name, jdbcType=VARCHAR}, #{category, jdbcType=VARCHAR}, #{tenantId, jdbcType=VARCHAR}, #{deploymentTime, jdbcType=TIMESTAMP})
</insert>
<!-- 省略 -->
</mapper>
假設這裏的插入語句activiti不需要做特殊處理,所以最後執行的是insertDeployment對應的sql語句。
到此爲止本文已經分析了deployment插入的大致過程。剩餘批量插入、更新、刪除等有興趣各位可自行研究。