Dorado 5 : 9.4.主键处理 (RF1)

使用JDBCTransaction开发时,经常会遇到dataset的某个字段是由数据库自动生成的。
下面dorado默认对主键自动生成的实现方法以及建议:

数据库自动生成

大部分数据库都支持主键自动生成机制,通常遇到的是自增长的主键规则,例如Mysql, MSSQL等,还有Oracle中的Sequence生成机制。不同的数据库会有自己的不同处理机制。
由于dorado默认使用的是JDBCTransaction,因此在dorado中直接通过SQL处理主键就并不难解决。
而DBDataset这种自身具有持久化能力的对象,就需要通过一些特殊的设置来支持主键处理。
数据库自增长的类型:

数据库

数据库的主键处理机制

Mysql,HSQL

Mysql可以为主键添加自增的约束,当插入数据时,数据库会忽略主键上传入的值,并为该字段赋一个数据库产生的自增的值

SQLServer

SQLServer也是可以为主键添加自增的约束,但是主键字段在插入数据时不能赋值,数据库会为其产生一个自增的值

Oracle

Oracle可以使用Sequence产生一个自增的序列

DBDataset中针对不同的数据库就需要作不同的设置,
例如MySQL中表格的schema定义:

CREATE TABLE `log` (
`no` int(3) unsigned NOT NULL auto_increment,
`msg_time` timestamp(14) NOT NULL,
`msg` varchar(200) default NULL,
PRIMARY KEY (`no`),
UNIQUE KEY `no` (`no`),
KEY `no_2` (`no`)
) TYPE=MyISAM;

DBDaset定义如下:

<Dataset id="dsLog" type="AutoSql" originTable="log"
keyFields="no" readOnly="true">
<MasterLink />
<Fields>
<Field name="no" originField="no" table="log"
group="false" label="日志编号" dataType="string">
</Field>
<Field name="msg" originField="msg" table="log"
group="false" label="日志消息" dataType="string">
</Field>
<Field name="msg_time" originField="msg_time"
table="log" group="false" label="时间" dataType="datetime">
</Field>
</Fields>
</Dataset>


MYSQL,HSQL等类型

对于这种结构的表格使用DBDataset更新数据库时,利用DBDataset本身的持久化能力并不需要做什么特殊设定。

<Dataset id="dsLog" type="AutoSql" originTable="log"
keyFields="no" readOnly="true">
<MasterLink />
<Fields>
<Field name="no" originField="msg" table="log"
group="false" label="日志编号" dataType="int">
<Properties />
</Field>
<Field name="msg" originField="msg" table="log"
group="false" label="日志消息" dataType="string">
<Properties />
</Field>
<Field name="msg_time" originField="msg_time"
table="log" group="false" label="时间" dataType="datetime">
<Properties />
</Field>
</Fields>
</Dataset>

MSSQL

但是对于SQLServer,由于DBDataset更新时自动生成的SQL语句会包含主键更新信息执行时就必然会出错,但是我们可以通过Field的updateable属性为false,这样DBDataset持久化的时候就会忽略该字段,不对该字段作处理。

<Dataset id="datasetLog" type="AutoSql" originTable="log"
keyFields="no" readOnly="true">
<MasterLink />
<Fields>
<Field name="no" originField="msg" table="log"
group="false" label="日志编号" dataType="int"
updatable="false">
<Properties />
</Field>
<Field name="msg" originField="msg" table="log"
group="false" label="日志消息" dataType="string">
<Properties />
</Field>
<Field name="msg_time" originField="msg_time"
table="log" group="false" label="时间" dataType="datetime">
<Properties />
</Field>
</Fields>
</Dataset>


Oracle的序列处理

而对于Oracle,一般都是使用Oracle的Sequence处理主键生成,如上的范例在数据库中生成一个oracle的序列对象,代码如下:

create sequence msgno_sequence
increment by 1
start width 1
nomaxvalue
nocycle
cache 10;

这样在向msg表格插入记录的时候,就可以使用如下的sql语句实现no字段的自增长

insert into msg (no, msg, msg_time) values(msgno_sequence.nextval, '新增记录',c_date('20070320'))

在Dataset中,为需要定义为自增的字段添加一个Property,名称设定为oracle.sequence,value为数据库中建立的Sequence的名字。这样,在插入数据时,Dorado会调用指定的Sequence产生一个值作为该字段的值。

<Dataset id="datasetLog" type="AutoSql" originTable="log"
keyFields="no" readOnly="true">
<MasterLink />
<Fields>
<Field name="no" originField="msg" table="log"
group="false" label="日志编号" dataType="int">
<Properties>
<Property name="oracle.sequence" value="msgno_sequence" />
</Properties>
</Field>
<Field name="msg" originField="msg" table="log"
group="false" label="日志消息" dataType="string">
<Properties />
</Field>
<Field name="msg_time" originField="msg_time"
table="log" group="false" label="时间" dataType="datetime">
<Properties />
</Field>
</Fields>
</Dataset>

主键处理建议

因为不同的数据库有不同的主键处理机制,所以在程序的代码中部得不加入与业务无关的数据库主键处理机制,不利于系统的移植。因此目前流行的开源框架中,很多框架都建议使用自定义的主键生成机制,这对于系统业务层的管理和维护是大有裨益的,例如Hibernate中就建议尽量采用程序生成主键。
当像数据库中插入数据时,主键值的来源有一下三种方式:人工输入、程序生成和数据库生成,或者是以上三种的结合。

人工输入时

需要在数据插入之前校验该值的唯一性。这样就需要增加与数据库的交互次数,加大系统开销,这并不符合我们对一个优良系统设计的需求。
对于部分系统中确实已经作了这种设计的基础上,如果无法改变现实需求,则使用dorado中RPCCommand的技术,可以比较方便的提供客户端校验技术。通过RPCCommand对象先将要验证的值传回服务器端,然后服务器端验证后将验证结果传给客户端,客户端再将验证结果信息反馈到页面上。这种处理方式可以极大的改进用户的操作体验。

程序生成

通过代码可以生成一组唯一的数字作为数据的主键,比如Hibernate就提供了程序生成主键的功能,下边是一个可以生成18位主键的序列号生成器的简单实现:

Public class Serial {
private static long base = System.currentTimeMillis();
private static int op = 0;
private final static int limitation = 100000;
public static String getSerial(){
if(op>=limitation-1){
base = System.currentTimeMillis();
op = 0;
}
return (base*limitation+(+op))"";
}
}

数据库生成

不同的数据库可以生成主键的方法会有很大的不同,参考上节内容
主键规则复杂时,在Oracle或者SQLServer等数据库中可以通过触发器或存储过程利用复杂的SQL运算来生成表的主键值。

利用com.bstek.dorado.data.KeyGenerator

com.bstek.dorado.data.KeyGenerator接口用于在Dataset试图持久化新的记录时为记录生成主键值,用户可自行扩展一个KeyGenerator的实现类,并将其配置到setting.xml文件data.keyGenerator属性当中。

<property name="data.keyGenerator" value=""/>


使用时须在需要使用此功能的字段的properties中增加一个属性,名为keyGenerator,其值以将要传给KeyGenerator.getKey方法的自定义参数(可以为空)。
接口声明如下:

package com.bstek.dorado.data;



/**

  • 主键生成器的通用接口
    */
    public interface KeyGenerator {

    /**
  • 返回一个新的主键值。

  • @param dataset Dataset
  • @param record ReadRecord
  • @param arg 自定义参数.
  • @return 新主键值。
  • @throws Exception
    */
    String getKey(Dataset dataset, ReadRecord record, String arg)
    throws Exception;
    }

开发人员可以扩展该接口定义自己的实现类,通过继承getKey方法处理主键生成规则。用字符串返回新主键值。
dorado中现已包含一个用于调用Oracle Sequence的KeyGenerator实现类(com.bstek.dorado.data.db.dialect.OracleSequenceKeyGenerator),使用时只要在field的keyGenerator名称的property属性中设置其value为Sequence的名称即可自动生成新的键值。

主从表外键处理

在一次更新中,需要同时更新多个dataset,并且这些dataset包含了主从关系,由于主表的主键字段在数据库更新之前为空,这样从表数据对应的外键也必然为空。

人工输入

对于用户手工输入主键的处理方式,Dataset可以利用MasterLink的自动处理机制为子表的外键自动赋值,详情查看MasterLink

程序生成

这儿的程序生成是专指客户端不管主键的生成,而是在将主从表的数据同时提交到服务器处理逻辑层的时候通过代码生成。如:

public void saveAll(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
int count = 0;
Dataset dsDept = getDataset("dsDept");
Dataset dsEmployee = getDataset("dsEmployee");
Record deptRecord = dsDept.getCurrent();
String deptId = getKey();
deptRecord.setString("dept_id", deptId);
RecordIterator iter = dsEmployee.recordIterator();
while (iter.hasNext()) {
Record empRecord = iter.nextRecord();
empRecord.setString("dept_id", deptId);
count++;
}
MessageHelper.addMessage(
DoradoContext.getContext(), "共新增了" + count + "个员工!");
super.doUpdateData(parameters, outParameters);
}

服务器段代码生成时需要根据提交到服务器的dataset之间的关系,给对应的外键赋值,如上的代码中,通过getKey()函数获得主键,并且作为dsEmployee的dept_id外键的值保存到dsEmployee中的所有记录中。

数据库生成

采用数据库主键生成机制,dorado默认都是采用PreparedStatement实现持久化,大部分数据库都支持将自动生成的主键信息返回到负责数据更新的PreparedStatement中,dorado中的DBDataset也支持这种操作,通过设置DBDataset的RetrieveAfterUpdate属性为true,DBDataset在数据更新之后可以获得数据库自动生成的主键信息并更新内部的record对象。如上节中主从表数据更新的代码调整为:

public void saveAll(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
int count = 0;
Dataset dsDept = getDataset("dsDept");
Dataset dsEmployee = getDataset("dsEmployee");
//数据更新
dsDept.update();
//获取数据库同步过来的部门编号信息
String deptId = dsDept.getString("dept_id");

RecordIterator iter = dsEmployee.recordIterator();
while (iter.hasNext()) {
Record empRecord = iter.nextRecord();
empRecord.setString("dept_id", deptId);
count++;
}
MessageHelper.addMessage(
DoradoContext.getContext(), "共新增了" + count + "个员工!");
super.doUpdateData(parameters, outParameters);
}