缓存介绍-页面缓存,Model缓存与基础资料

栏目:云苍穹知识作者:金蝶来源:金蝶云社区发布:2024-09-23浏览:1

缓存介绍-页面缓存,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


//方法一:将值置空后,可以触发前端非空校验,
  this
.getModel().setValue("AAAA",nullindex)

//方法二:将值置空后,不能触发校验。

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)        组合使用DeleteServiceHelperSaveServiceHelper导致缓存不一致

Ø  示例

错误用法:先删除数据,再保存数据

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缓存与基础资料

【导语】日常插件开发过程中,我们经常会与缓存打交道,对于苍穹来说缓存主要分为三部分:页面缓存、Model缓存、以及基础资料缓存,分别作...
点击下载文档
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息