实体数据导入与导出
加载实体 XML 和 CSV
实体记录可以用 EntityDataLoader 从XML和CSV文件中导入。可以通过 EntityFacade API 用 ec.entity.makeDataLoader() 方法来获取一个实现了此接口的对象,并使用它的方法来实现:
- 指定加载什么数据并加载(load()方法)
- 获取一个记录的 EntityList(list()方法)
- 校验数据库的数据(check()方法)
指定加载什么数据有一些选项。你能用location(String location)和locationList(List
要设置与默认不同的事务超时时间,一般在处理大文件时需要更长的时间,使用transactionTimeout(int tt)方法。 如果知道绝大部分是插入,你可以传true到useTryInsert(boolean useTryInsert)方法来不查询是否记录已存在就执行插入、在插入失败是再尝试更新来提高性能。
为了帮助处理外键,当记录没有按顺序,而你知道后面将会载入外键依赖的数据时,传 true 到 dummyFKs(boolean dummyFks)方法,在没有存在的外键记录时,它将创建空记录。当此外键对应的真是记录加载时,它将简单地更新那条空的虚设记录。要在数据加载时禁止实体ECA规则,传true到disableEntityEca(boolean disableEeca)方法。
对于CSV文件,你可以指定在解析文件时使用哪些字符:csvDelimiter(char delimiter)(默认为都好',')、csvCommentStart(char commentStart)(默认为'#')和csvQuoteChar(char quoteChar)(默认为'"')。
注意EntityDataLoader上的所有这些方法都返回一个对自身的引用,这样你能把调用链起来。它是一个DSL风格的API。例如:
ec.entity.makeDataLoader().dataTypes([‘seed’, ‘demo’]).load()
除了直接使用API以外,你可以使用Moqui默认runtime中自带的tool组件的 Tool => Entity => Import 页面来加载数据。也可以用可执行的WAR文件加-load参数在命令行运行来加载数据。这里有数据加载器相关的命令行参数:
参数 | |
---|---|
load | 运行数据加载器 |
types=<type>[,<type>] | 要加载的数据类型(可以是任意,一般是: seed, seed-initial, demo, ...) |
location=<location> | 要加载的数据文件的位置 |
timeout=<seconds> | 处理每个文件的事务超时时间,默认为600秒(10分钟) |
dummy-fks | 使用虚构的外键key来避免引用完整错误 |
use-try-insert | 尝试插入并在有错时更新,而不是先检查 |
tenantId=<tenantId> | 加载数据的目标租户的ID |
例如:
$ java -jar moqui.war load types=seed,demo
实体数据XML文件的根元素必须是 entity-facade-xml,它有一个 type 属性来指定文件中数据的类型,(如果指定类型加载)和特定类型比较,只在类型在集合中或者所有类型都加载的情况下加载。在根元素下,每个元素的名字是一个实体或服务名。对于实体,每个属性是一个字段名,对于服务每个属性是一个输入参数。
这里有一个实体数据XML文件的例子:
<entity-facade-xml>
<moqui.basic.LocalizedMessage original="Example" locale="es" localized="Ejemplo"/>
<moqui.basic.LocalizedMessage original="Example" locale="zh" localized="样例"/>
</entiy-facade-xml>
这有个调用服务的CSV文件例子(同样的模式也应用于加载实体数据)
# first line is ${entityName or serviceName},${dataType}
org.moqui.example.ExampleServices.create#Example, demo
# second line is list of field names
exampleTypeEnumId, statusId, exampleName, exampleSize, exampleDate
# each additional line has values for those fields
EXT_MADE_UP, EXST_IN_DESIGN, Test Example Name 3, 13, 2014-03-03 15:00:00
写实体 XML
导出实体数据到XML文件的最简单的方法是用 EntityDataWriter,你可以用 ec.entity.makeDataWriter() 获得。通过这个接口你可以:
- 指定要都指出的实体的名字和多种其他选项,然后它执行查询并导出:
- 到文件(用int file(String filename) 方法)
- 一个目录下每个实体一个文件(用int directory(String path)方法)
- 一个Writer对象(用 int writer(Writer writer)方法)。
- 所有这些方法返回一个整数,表示写入的记录数。
这些指定导出参数的方法都返回一个对自身的引用,以启用链式调用。下面是设置查询和导出参数的方法:
- entityName(String entityName): 指定要查询和导出的一个实体的名字。导出多个实体时,调用此方法或entityNames方法多次,将按调用的顺序进行对数据的查询和导出。
- entityNames(List
entityNames):要查询和导出的实体名称列表。按调用此方法或时列表中的顺序或多次调用entityName方法的顺序进行对数据的查询和导出。 - dependentRecords(boolean dependents):如果为true,将导出每条记录的依赖记录。这显著降低了导出速度,所以只在较小的数据集上用它。关于会包含什么的细节请查看 依赖实体 的章节。
- filterMap(Map
filterMap):用以过滤结果的一个字段名、值的映射表。每个名字/值只被用在有字段匹配此名字的实体上。 - orderBy(List
orderByList): 用于排序结果的字段名列表。每个名字只被用在有字段匹配此名字的实体上。可以被调用多次。每个条目可以是都好分隔的字段名列表。 - fromDate(Timestamp fromDate), thruDate(Timestamp thruDate):用以过滤记录的起始、结束日期,和EntityFacade自动加到每个实体(如果在实体定义中没有关闭)的 lastUpdatedStamp 字段比较。
这有个导出所有在一个时间范围内的所有OrderHeader记录及其依赖的例子:
ec.entity.makeDataWriter()
.entityName("mantle.order.OrderHeader")
.dependentRecords(true)
.orderBy(["orderId"])
.fromDate(lastExportDate).thruDate(ec.user.nowTimestamp)
.file("/tmp/TestOrderExport.xml")
另一个导出实体记录的方法是执行一个查询并获取一个EntityList或EntityListIterator对象,并调用它的int writeXmlText(Writer writer, String prefix, boolean dependents)方法。此方法将XML写到writer,可选地在每个元素前加上前缀以及包含依赖。
与实体导入界面类似,你可以使用默认的Moqui运行时中自带的tools组件中的 Tool => Entity => Export 页面来导出数据。
简化查看和导出的页面和表单
这么多工具一起使得能够非常容易地查看、导出来自许多不同表的数据库数据。我们看了各种选项包括静态(XML)、动态和数据库定义的实体。在 用户界面 章节有关于XML表单的详情,特别是列表表单。
当 form-list 有 dynamic=true 并且在 auto-fields-entity.entity-name 属性中有 ${} 字符串扩展时,那么在页面被渲染时将被即时扩展,意味着提供实体名作为页面参数时,一个单一表单能被用来为任意实体生成表格HTML或CSV输出。
更有趣的是,被查看的结果可以被过滤,一般使用一个有 auto-fields-entity 的 dynamic form-single 来基于实体生成一个搜索表单,一个有 search-form-inputs 的 entity-find 元素来基于实体名参数和搜索表单的搜索参数来执行查询。
下面是这些特性连同页面转换(transition DbView.csv)来导出一个CSV文件的例子。别担心太多关于页面、转换、表单和渲染选项的细节,它们将会在用户界面章节中详细介绍。界面定义来自Moqui框架自带的tools组件中的 ViewDbVie.xml 页面。
<screen>
<parameter name="dbViewEntityName" required="true"/>
<transition name="filter">
<default-response url="."/>
</transition>
<transition name="DbView.csv">
<default-response url="."><parameter name="renderMode" value="csv"/>
<parameter name="pageNoLimit" value="true"/><parameter name="lastStandalone" value="true"/></default-response>
</transition>
<actions>
<entity-find entity-name="${dbViewEntityName}" list="dbViewList">
<search-form-inputs/>
</entity-find>
</actions>
<widgets>
<container>
<link url="edit" text="Edit ${dbViewEntityName}"/>
<link url="DbView.csv" text="Get as CSV"/>
</container>
<label text="Data View for: ${dbViewEntityName}" type="h2"/>
<container-dialog id="FilterViewDialog" button-text="Filter ${ec.entity.getEntityDefinition(dbViewEntityName).getPrettyName(null, null)}">
<form-single name="FilterDbView" transition="filter" dynamic="true">
<auto-fields-entity entity-name="${dbViewEntityName}" field-type="find"/>
<field name="dbViewEntityName"><default-field><hidden/></default-field></field>
<field name="submitButton"><default-field title="Find"><submit/></default-field></field>
</form-single>
</container-dialog>
<form-list name="ViewList" list="dbViewList" dynamic="true">
<auto-fields-entity entity-name="${dbViewEntityName}" field-type="display"/>
</form-list>
</widgets>
</screen>
这个页面被设计为供用户使用的同时,它也能在web或其他UI上下文外被渲染来发送到一个文件或其他位置。如果你只是为那写一个页面,将会简单得多,基本上只有parameter元素,单个entity-find动作和简单的form-list定义。不需要转换和搜索表单。
使用页面渲染器的代码会像是这样:
ec.context.putAll([pageNoLimit:"true", lastStandalone:"true", dbViewEntityName: "moqui.example.ExampleStatusDetail"])
String csvOutput = ec.screen.makeRender().rootScreen("component://tools/screen/Tools/DataView/ViewDbView.xml").renderMode("csv").render()