缓存介绍-页面缓存,Model缓存与基础资料
【导语】
日常插件开发过程中,我们经常会与缓存打交道,对于苍穹来说缓存主要分为三部分:页面缓存、Model缓存、以及基础资料缓存,分别作用于:
l 页面缓存:主要用于存储页面的打开参数,业务处理过程中的中间数据等;
l Model 缓存:用于存储单据体或者字段相关的数据
l 基础资料缓存:用于缓存基础资料的数据
由于缓存资源有限,如果使用不当可能会造成JVM OMM或者Redis OOM,除了苍穹需要对业务缓存数据进行限流或截断处理外,开发过程中良好的设计和编码也能在多数情况下规避这些问题。
【缓存的使用场景和注意事项】
苍穹框架的缓存主要是分为页面缓存,控件状态数据缓存(Model),以及基础资料缓存,与之相对应的类分别是,如下图
在开发中我们经常会使用getPageCache,getModel分别操作缓存与数据, getPageCache使用的是PageCache对象,getModel操作的是FormDataModel对象,下面向大家介绍一下这三种对象的实现原理以及注意事项。
1. 页面缓存
PageCache存储的是所有业务相关数据包括页面打开参数FormShowParameter,如果需要通过FormShowParameter参数传递,使用setCustomParam方法来设置,由于使用redis作为框架的缓存底层 ,会将FormShowParameter整体作为一个String类型的Key进行存储,如果存储的内容过多,会导致redis产生bigkey 。
Ø 示例:
错误用法:在FormShowParameter 的自定义参数中放大对象,通过FormShowParameter传递树形结点所有数据,
Java | |
FormShowParameter formShowParameter=new FormShowParameter(); TreeView treeView= getControl("treeView"); formShowParameter.setCustomParam("treenode", treeView); |
Java | |
FormShowParameter formShowParameter=new FormShowParameter(); TreeView treeView= getControl("treeView"); formShowParameter.setCustomParam("treenode",treeView.getTreeState().getCheckedNodeIds());
|
众所周知,redis是单线程的,如果有bigkey 会阻塞redis主线程,从而导致redis存在访问性能问题,所以一般禁止在FormShowParameter里面传递过多内容 。同理,在插件代码里需要存储处理过程的中间状态时也是禁止在pagecache存储过多内容。
2. Model缓存
Model对象保存的是单据上字段的数据,包括单据体和子单据体的数据信息
1) 2.1 Model和动态对象DynamicObject的区别 Model操作字段数据时可以触发一些与字段相关的事件,比如值更新事件
2) 同时会触发控件代理FieldEdit的binddata方法将变更后的value值通过指令发送给前端
3) 同时Model 会记录单据数据包的字段是否被修改记录,这样ORM在保存数据时会通过这些记录识别是否是新增或者更新操作。
如果只是操作 DynamicObject对象,上述过程则都不会触发,从而导致功能异常,所以在插件操作字段数据时优先推荐使用Model 对象。
Java | |
//方法一:将值置空后,可以触发前端非空校验, //方法二:将值置空后,不能触发校验。 DynamicObjectCollection strategyEntrys = this.getModel().getEntryEntity("entryentity"); strategyEntrys.forEach(dy->dy.set("AAAA",null)); |
2.2 操作单据体建议用批量方式
使用Model操作单据体时,推荐使用性能更好的batch 开头的方法,同时在批量更新单据体数据时,如果考虑性能问题,可以使用 getEntryEntity方法获取DynamicObjectCollection对象,然后遍历每行来操作DynamicObject对象内容,最后需要通过getView.updataView(“单据体标识”)将修改后的数据通过指令批量发送给前端。
Ø 示例:
1. 给单据体添加10行
2. 逐行填写整数字段值
Java | |
@Override public void afterCreateNewData(EventObject e) { int rowCount = this.getModel().getEntryRowCount(KEY_ENTRYENTITY); if (rowCount < 10){ // 给单据体补足10行 this.getModel().batchCreateNewEntryRow(KEY_ENTRYENTITY, 10 - rowCount); rowCount = 10; }
// 逐行给整数字段设置默认值 for(int row = 0; row < rowCount ; row++){ int fldValue = row + 1; this.getModel().setValue(KEY_INTEGERFIELD1, fldValue, row); } } |
3. 基础资料缓存
为了提高查询时性能,减少查询时与基础资料表的关联,框架提供了基础资料缓存,基础资料缓存主要用于列表或者报表查询。
l SaveServiceHelper类:平台提供的用于数据保存和修改的服务类
l DeleteServiceHelper 类:平台提供的用于数据删除的类
如何使用 ServiceHelper类正确保存数据
1) load方法使用不当,导致需要修改的数据变为新增
Ø 示例
错误用法:使用loadSingleFromCache加载数据修改并调用保存,导致数据都是新增到数据表
Java | |
DynamicObject bosorgData = BusinessDataServiceHelper. loadSingleFromCache (10000L, "bos_org"); bosorgData.set("name","test"); SaveServiceHelper.save(new DynamicObject[]{bosorgData}); |
正确用法
Java | |
DynamicObject bosorgData = BusinessDataServiceHelper. loadSingle (10000L, "bos_org"); bosorgData.set("name","test"); SaveServiceHelper.save(new DynamicObject[]{bosorgData}); |
Ø 说明
SaveServiceHelper的save方法会自动判断是数据新增还是修改,判断依据主要是DynamicObject对象中,是否保存了数据包里用于修改的脏数据信息;loadxxxCache加载数据时是从缓存加载的,所以不会包含脏数据信息,加载的数据都认为是新增的;load方法加载的DynamicObject是带有脏数据信息可以用于修改。
2) 组合使用DeleteServiceHelper和SaveServiceHelper导致缓存不一致
Ø 示例
错误用法:先删除数据,再保存数据
Java | |
DeleteServiceHelper.deleteOperate(“xxx”,new Object[]{xxx}); SaveServiceHelper.save(DynamicObject[]{xxx}); |
正确用法
Java | |
SaveServiceHelper.save(DynamicObject[]{xxx}); |
上面的写法之所以错误,主要是违背了先更新数据再删除缓存的规则。下面举例说明:
Ø 案例讲解
按不同的删除、更新顺序,分成两种情况来看。在这两种情况下,解决方法也有所不同。
情况一:先删除缓存,再更新数据库
假设应用先删除缓存,再更新数据库,如果缓存删除成功,但是数据库更新失败;那么,应用再访问数据时,缓存中没有数据,就会发生缓存缺失,然后,应用再访问数据库,但是数据库中的值为旧值,应用就访问到旧值。
情况二:先更新数据库值,再删除缓存值。
如果应用先完成了数据库的更新,但在删除缓存时失败;那么,数据库中的值是新值,而缓存中的是旧值。此时,如果有其他的并发请求来访问数据,按正常缓存访问流程,会先在缓存中查询,但此时,就会读到旧值。
结论
从以上内容可以看出,两种处理都是存在缺陷的,但是实际应用中我们会根据问题出现的概率,来选择相应的解决方案;通常情况下,我们会选择情况二来处理缓存,原因如下:
1) 情况一:大概率会导致缓存与数据库的数据不一致,因为删除缓存后,由于数据库操作相对较慢,这时应用大概率会读取到旧的数据。上面的例子先用DeleteServiceHelper删除数据,再调用SaveServiceHelper保存数据就造成了情况一,所以大概率会出现数据不一致问题。
2) 情况二:缓存相对来说更新失败的概率较小,而且请求很快,大概率不会有问题
【划重点】
使用基础资料缓存时需注意:
1. 基础资料的数据不能过多:如果数据量超过10W以上,load或者loadxxxCache可能会导致JVM OOM;这种情况建议分批进行数据加载,或者使用orm的 queryDataSet方法来遍历 dataset数据
2. 使用loadxxxCache方法加载的数据不能用于数据更新,因为loadxxxCache加载数据时缺少ORM的需要的状态数据,所以不能进行更新,数据都会当作新增数据处理
3. 不能直接通过SQL修改基础资料数据,直接通过SQL修改数据会导致数据库实现的数据和缓存不一致,现象是单据上看到是最新的,列表上看到的是旧的数据
4. 禁止使用先用DeleteServiceHelper删除数据再使用SaveServiceHelper保存数据的方式修改数据。
【相关链接】
更多参考资料详见:
https://developer.kingdee.com/article/358630628662976000?productLineId=29&isKnowledge=2
缓存介绍-页面缓存,Model缓存与基础资料
本文2024-09-23 00:20:57发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-138895.html