Dorado 7 : 07. 数据常用操作技巧(SEFC)

Server端

EntityUtils工具类和虚拟实体属性

Dorado以数据模型驱动开发,对于数据实体的设计,Dorado在Server端不提供专门的数据实体对象,而是直接由POJO对象或Collection集合定义。
但当我们在Dorado编程的过程中希望给POJO添加一些额外属性,或者还想给它添加一个特别的数据校验器,或者想添加一个状态体现其是新增、修改还是删除的动作。对于这些特性我们都是无法直接通过POJO本身解决的。为此,Dorado特别提供了一个工具类EntityUtils,用它来实现这种特殊处理,使得我们一个实体Bean具有上述的各种特性。

举例说明:
问题1:如何设置虚拟属性

Category category = new Category();
category = EntityUtils.toEntity(category);
//注意Category.java中没有prop1,prop2,prop3等属性
EntityUtils.setValue(category, "prop1", "value1");
EntityUtils.setValue(category, "prop2", 3);
EntityUtils.setValue(category, "prop3", true);
String prop1 = EntityUtils.getString(category, "prop1");
int prop2 = EntityUtils.getInt(category, "prop2");
boolean prop3 = EntityUtils.getBoolean(category, "prop3");

问题2:如何存取数据实体的状态

由于采用数据模型开发机制,且很多情况下客户端会对数据实体做各种复杂的增删改的多次操作,但是当我们把这个数据实体交付给业务逻辑层的时候我们需要知道这个数据实体的状态,我们就可以通过如下的方式进行状态的管理:

//该代码
Category category = new Category();
category = EntityUtils.toEntity(category);
EntityUtils.setState(category, EntityState.MODIFIED);
if (EntityState.NEW==EntityUtils.getState(category)){
	//TODO
}

以上代码中toEntity的代码并不是一定需要使用,视场景而定,例如一般来说如果我们在Resolver中获取到Dorado提交上来的数据,例如主从表范例CategoryInterceptor.java中的saveAll方法:

@DataResolver
@Transactional
public void saveAll(Collection<Category> categories) {
	for (Category category : categories) {
		EntityState state = categoryDao.persistEntity(category);
		if (EntityState.isVisible(state)) {
			Collection<Product> products = category.getProducts();
			if (products != null) {
				for (Product product : products) {
					if (EntityState.NEW.equals(EntityUtils
							.getState(product))) {
						product.setCategoryId(category.getId());
					}
				}
				productService.saveAll(products);
			}
		}
	}
}

在这段方法中我们注意到其中Product的并没有直接调用EntityUtils.toEntity方法,这是因为Dorado已经自动帮助我们做好转换工作了。

数据实体的状态,共有5种:

状态

含义

EntityState.NONE

原始状态

EntityState.NEW

新增数据实体

EntityState.MOVED

被移动过的数据实体,专指树控件节点移动引起

EntityState.MODIFIED

被编辑过的数据实体

EntityState.DELETED

做过删除标记的数据实体

问题3.如何批量的获取所有被删除,或被修改的记录

public void saveAll(Collection<Category> categories) {
	for (Category category : categories) {
		EntityState state = categoryDao.persistEntity(category);
		if (EntityState.isVisible(state)) {
			Collection<Product> products = category.getProducts();
			for (Product product:EntityUtils.getIterable(products, FilterType.MODIFIED, Product.class)){
				//TODO
			}
			for (Product product:EntityUtils.getIterable(products, FilterType.DELETED, Product.class)){
				//TODO
			}				
			for (Product product:EntityUtils.getIterable(products, FilterType.NEW, Product.class)){
				//TODO
			}				
		}
	}
}

问题4. 某个数据实体在客户端被编辑过,如何获取原始值
例如产品中的productName属性从"Aniseed Syrup"改为"Chef Anton's Gumbo Mix",unitPrice从3600.00调整为3200.00,而提交到服务器端后,我们获得的是Product的实体对象,通过它的getProductName()方法只能拿到"Chef Anton's Gumbo Mix",如果我们还希望取得该属性的原始值,方法为:

public void saveAll(Collection<Category> categories) {
	for (Category category : categories) {
		EntityState state = categoryDao.persistEntity(category);
		if (EntityState.isVisible(state)) {
			Collection<Product> products = category.getProducts();
			if (products != null) {
				for (Product product : products) {
					if (EntityState.MODIFIED.equals(EntityUtils
							.getState(product))) {
						String oldString = EntityUtils.getOldString(product, "productName");//获取原始的productName值
						float unitPrice = EntityUtils.getOldFloat(product, "unitPrice");//获取原始的unitPrice值
						//TODO
					}						
				}
				productService.saveAll(products);
			}
		}
	}
}

Client端

在立体数据模型一节我们提及Dorado在客户端使用的数据载体,除了JSON和Array之外,也提供了Dorado7中专用的数据载体Entity/EntityList。简单说Entity/EntityList是JSON和Array的一个封装,它主要提供了异步数据装载、状态管理、翻页、管理当前记录、数据校验等功能。

Entity

Entity对象的在一般情况下都是由DataProvider自动创建的,如果需要手工创建也并不复杂,在设计好DataType之后,我们可以如下的方式创建Entity对象:
范例:

var employee = {
     $dataType : "Employee",
     id : "0001",
     name : "John",
     sex : true
}

使用上的差别在于$dataType,这种用法与我们之前学习过的控件的JSON创建方式非常类似,它就是一个标准的JSON声明,另外需要通过$dataType指定DataType的类型。
创建好的Entity对象就具有一些特性,如:
状态管理:

employee.setState(dorado.Entity.STATE_DELETED);

虚拟属性和oldValue的访问:

employee.set("name", "ANLIN");
alert(employee.getOldData().name);//oldData()方法返回数据实体内部用于保存原有属性值的JSON对象
alert(employee.get("sex"));

其它更多的属性和功能请参考JSDOC中的Entity对象: http://bsdn.org/projects/dorado7/deploy/jsdoc/class.html?symbol=dorado.Entity

API中的几个方法强调一下:我们知道现在Dorado7中的数据载体支持立体数据模型,JSON可以包含一个立体数据模型的数据,通过DataType我们可以完成立体数据的描述,但是怎么通过Entity对象将相关的数据加入呢,这种Java对象间的应用关系,我们就可以通过两个关键方法createBrother()和createChild(),以树为例:createBrother是用于创建当前节点的平行节点,而createChild是用于创建当前节点的子节点。

 (  ) 和  )

返回或设置当前数据实体关联的额外信息的数组。

在一般情况下这里的信息都对应了数据校验的验证结果。返回的对象是一个JSON数组,JSON结构说明:

  • state {String} 信息级别。取值范围包括:info、ok、warn、error。默认值为error。
  • text {String} 信息内容。

由于一个PropertyDef可能会添加多个数据校验器,数据校验器的验证结果都可以通过getMessages(String property)进行存取。因此这个方法返回的是一个JSON数组

如果不指定property参数,返回的JSOn数组表示当前实体对象的信息集合,如果指定property属性,则表示指定属性的信息集合。如指定属性的数据校验结果。

entity.setMessages("desc",{state:"info", text:"这是备注信息的提示信息"});
entity.setMessages("salary",{state:"warn", text:"薪水必须大于上海市最低薪资"});
entity.setMessages("sex",{state:"error", text:"性别字段非空"});

以上三行代码会使相应编辑框上出现不同的编辑提示框,提示信息为text中的内容。

entity.setMessages("A") 或 entity.setMessages("A", undefined) 表示清空掉验证状态
EntityList

insert(dorado.Entity|Object entity , String[insertMode] , dorado.Entity[refEntity] )
向集合中插入一个数据实体。如:

entitylist.insert({
	id : "0001",
	name : "John",
	sex : true
},"begin");

insertMode参数为可选项,共有四种取值:

模式

含义

begin

在集合的起始位置插入

before

在refEntity参数指定的数据实体之前插入

after

在refEntity参数指定的数据实体之后插入

end

在集合的末尾插入

如果不指定insertMode,系统默认为end。

remove(dorado.Entity[entity] , boolean[detach])
从集合中删除一个数据实体。

entitylist.remove();//对当前的实体对象添加删除标记
entitylist.remove(null, true);//彻底删除当前的实体对象

detach用于控制是否做彻底删除,如果做了彻底删除,则你无法再通过JS访问到它,同理你也无法在DataResolver提交到服务器端的时候取得这个对象。

each (Function fn , Object [scope])
用于遍历当前集合的所有实体对象,范例:

// 将每一个集合元素的name属性连接成为一个字符串
var names = "";
var entityList = ...
entityList.each(function(entity){
     names += data.get("name");
});

该方法需要特别说明的两点:
要点一:
已经打上删除标记的实体对象使用each遍历的时候也能访问到
要点二:
如果EntityList是一个支持分页的集合,则会遍历所有已经加载过的分页中的实体对象,注意这儿说的是已经加载过的,未加载过的分页不会去遍历。如果想遍历未加载数据的分页建议采用iterator()方法

iterator (Object|boolean [options])
each是EntityList提供的一个简单易用的遍历函数,如果希望对遍历规则做更多的控制,我们就需要通过iterator方法,举例:
问题一:不希望对已经做过删除标记的数据进行遍历
解决的办法代码如:

// 将每一个集合元素的name属性连接成为一个字符串
var names = "";
var entityList = ...
var iterator = entityList.iterator(false);
iterator.first();
while(iterator.hasNext()){
	names += iterator.next().get("name");
}

问题二:采用了分页技术,但是希望对未加载的页也进行数据遍历

// 将每一个集合元素的name属性连接成为一个字符串
var names = "";
var entityList = ...
var iterator = entityList.iterator({includeUnloadPage:true});
iterator.first();
while(iterator.hasNext()){
	names += iterator.next().get("name");
}

includeUnloadPage会触发DataProvider处理机制引起AJAX数据请求,使用时要注意性能问题
问题三:希望从指定位置开始进行遍历

// 将每一个集合元素的name属性连接成为一个字符串
var names = "";
var entityList = ...
var iterator = entityList.iterator({nextIndex:20});
iterator.first();
while(iterator.hasNext()){
	names += iterator.next().get("name");
}

问题四:希望从指定页开始遍历

// 将每一个集合元素的name属性连接成为一个字符串
var names = "";
var entityList = ...
var iterator = entityList.iterator({pageNo:5});
iterator.first();
while(iterator.hasNext()){
	names += iterator.next().get("name");
}

问题五:只遍历当前页

// 将每一个集合元素的name属性连接成为一个字符串
var names = "";
var entityList = ...
var iterator = entityList.iterator({currentPage:true});
iterator.first();
while(iterator.hasNext()){
	names += iterator.next().get("name");
}
DataSet.getData和DataSet.queryData

getData默认只返回DataPath命中的第一笔数据,可能是Entity,也可能是EntityList。
queryData返回DataPath完整的执行结果,且默认只返回Entity(只在使用#等确定只可能有最多一个命中数据实体的DataPath时)或Entity的数组。

以SimpleCRUD为例: http://bsdn.org/projects/dorado7/deploy/sample-center/com.bstek.dorado.sample.data.SimpleCRUD.d

getData几个范例:

范例1:获取产品分类为2的所有产品

var entityList = ds.getData("[@.get('categoryId')==2]");

范例2:清除DataSet中原有的数据并重新提取数据后,再查询产品分类为2的所有产品

var entityList = ds.getData("[@.get('categoryId')==2]",{flush:true});

范例3:在已经加载到客户端的数据中查询产品分类为2的所有产品

var entityList = ds.getData("[@.get('categoryId')==2]",{loadMode:"never"});

queryData几个范例:
范例1:获取产品分类为2的所有产品

var entityArr = ds.queryData("[@.get('categoryId')==2]");

范例2:清除DataSet中原有的数据并重新提取数据后,再查询产品分类为2的所有产品

var entityArr = ds.queryData("[@.get('categoryId')==2]",{flush:true});

范例3:在已经加载到客户端的数据中查询产品分类为2的所有产品

var entityArr = ds.queryData("[@.get('categoryId')==2]",{loadMode:"never"});