activiti學習(十四)——activiti數據庫存儲(三)——實體管理類封裝mybatis調用原理

上兩篇文章主要分析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插入的大致過程。剩餘批量插入、更新、刪除等有興趣各位可自行研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章