目前对会签的表单我们采用了两种不同的方案,也代表两种不同的风格。第一种是moss风格,第二种是通达风格。Moss风格不共用会签区,不同的会签人会签意见签署区不同。这种风格的会签人是固定配置在节点上的,如果会签人不固定,那么会签区表单权限控制就会出现混乱;通达风格则是属于共用会签区的形式,不同会签人都在同一个输入框中输入审批信息。点击审批时,审批人、审批时间和审批意见存储在另外一张表里,展示的时候使用拼接html的table的形式展示在相应位置。效果对比如下:
1.moss风格
2.通达风格1
3.通达风格2
在通达风格,即共用会签区时,我们是不需要考虑并发控制的,因为每次审批形成的审批数据都单独存储在一张表中。对于不共用会签区的,除了考虑权限控制以外,并发控制同样重要。当一个多人参与会签的任务到达后,如有两个以上的会签人打开并签署意见后,先审批的意见就被后审批的会签动作覆盖,其他用户打开查看时,会签意见只有一条。
以上是对会签并发控制的问题的一个概述。
针对并发问题,特别是数据库的并发问题,有乐观锁和悲观锁的概念。
悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。
乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。
针对会签并发的特殊性,我们采用了乐观锁的并发控制形式,即在读取和打开审批页面时并没做任何限制,待发表审批意见并提交后系统会检测读取到的时间戳与当前实时时间戳是否一致。如一致,则提交审批意见;如不一致,则提示用户刷新。这种思路严格意义上与乐观锁的定义并不匹配,但却与乐观锁的实现具有异曲同工的效果。
下面是实现代码:
1.定义时间戳
时间戳采用流水号形式,所有的表单的时间戳定义需完全一致。
2.提交时进行判断
if(isSign == "1"){ var overDateDataMap; $.oaPlugin.ajaxJson({ data : paras, async : false, url : "isDataOverDate.action", success : function(result) { overDateDataMap = result.overDateDataMap; if(overDateDataMap.flag == '1'){ isDateOver = true; $.tipAlert({ tipMsg:"本步骤其他会签用户已经提交审批信息,请刷新页面!!", type: 0 }); //$.errorMsgAlert("本步骤其他会签用户已经提交审批信息,请刷新页面!!", result.responseText); } }, error : function(result){ //0标识需要提示疑问或者错误信息 1 提示正确信息 $.tipAlert({ tipMsg:"出现错误!", type: 0 }); } }); }
3.判断逻辑
public JSONObject isDataOverData(FlowRunTime flowRunTime) { JSONObject overDateDataMap = new JSONObject(); if(StringUtils.isNotEmpty(flowRunTime.getTableName()) && StringUtils.isNotEmpty(flowRunTime.getPkValue())){ Map<String, Object> dataMap = dymcQueryService.executeQuery(flowRunTime.getTableName(), flowRunTime.getPkValue()); String formdata = flowRunTime.getFormData(); Map<String, Object> map = Json2MapUtil.parseJSON2Map(formdata); Map<String, Object> mainMap = (Map<String, Object>)map.get("main"); Map<String, Object> mainFiledsMap = (Map<String, Object>)mainMap.get("fields"); JSONObject newValue = new JSONObject(); JSONObject oldValue = new JSONObject(); if(mainFiledsMap != null && dataMap != null){ if(mainFiledsMap.containsKey("zhxgsj") && dataMap.containsKey("f_zhxgsj")){ if(mainFiledsMap.get("zhxgsj") != null){ if(!mainFiledsMap.get("zhxgsj").toString().equals(dataMap.get("f_zhxgsj").toString())){ overDateDataMap.put("flag", "1"); // for(Entry<String, Object> entry : mainFiledsMap.entrySet()){ // if(dataMap.containsKey("f_" + entry.getKey())){ // if(StringUtils.isNotEmpty(entry.getValue().toString()) && !entry.getValue().toString().equals(dataMap.get("f_" + entry.getKey()).toString())){ // newValue.put(entry.getKey(), entry.getValue()); // oldValue.put(entry.getKey(), dataMap.get("f_" + entry.getKey())); // } else if((StringUtils.isEmpty(entry.getValue().toString()) && StringUtils.isNotEmpty(dataMap.get("f_" + entry.getKey()).toString())) || (StringUtils.isNotEmpty(entry.getValue().toString()) && StringUtils.isEmpty(dataMap.get("f_" + entry.getKey()).toString()))){ // newValue.put(entry.getKey(), entry.getValue()); // oldValue.put(entry.getKey(), dataMap.get("f_" + entry.getKey())); // } // }else { // if(StringUtils.isNotEmpty(entry.getValue().toString())){ // newValue.put(entry.getKey(), entry.getValue()); // oldValue.put(entry.getKey(), ""); // } // } // } } else { overDateDataMap.put("flag", "0"); } } else { overDateDataMap.put("flag", "0"); } } else { overDateDataMap.put("flag", "0"); } overDateDataMap.put("new", newValue); overDateDataMap.put("old", oldValue); }else { overDateDataMap.put("flag", "0"); } } return overDateDataMap; }
逻辑释义:当前表单数据中的时间戳字段与实时数据库中的时间戳字段如果不一致,则可以判断数据过期;如果一致,则表明无其他会签改变数据,可以提交。注释掉的部分,则是将不一致的数据按属性名:属性值的形式取出,在前端将实时数据合并后在向后端发送保存。
4.时间戳的更新:
if(formData.getMainFields().containsKey("f_zhxgsj")){ formData.getMainFields().put("f_zhxgsj", DateUtils.getNowDateTimeToStr("yyyyMMddhhmmss")); }
通过以上思路,可以有效解决会签时的并发问题。