WMframework提供了固定,规范的一体化代码编写过程,最终目的即为清晰的系统开发流程、减少开发人员代码编写时间并大大降低其工作量、方便项目组全局实现代码的风格的规范统一等
简要描述
1、确认页面元素
参照用户需求及相关功能设计文档,确认当前表单页面元素和整体风格布局;如 用户基本信息维护中可能涉及的html Dom 表单元素可能有:用户名、密码、组织机构、帐号、有效期等。
2、定位功能数据表
以详细设计文档为标准,查找数据库设计及PDM模型中主要操作的数据表。如 用户基本信息维护可能涉及的表有:用户帐号信息、用户基本信息、操作日志、组织机构等
3、业务功能原型实现
使用WMframework代码生成工具(系统),创建并生成相应功能代码:jsp、domain、sqlmap、s2配置、sping配置等
4、功能处理扩展、定制
编辑已生成代码,使其附和并满足当前业务功能实现需要。
5、前台框架引入
检查修改用户表单jsp页面是否正常引入前台js提交、处理框架(当前部分已由系统代码自动生成,仅需做check即可),是否正确完成用户提交请求处理。
6、后台框架配置、变更
常规情况下,WMframework已经实现有后台通用action、service、dao处理流程,若非个性化特殊情况下,不提倡自定义实现action、service、dao等。
check当前domain(pojo)是否完全满足用户表单处理的数据模型映射,适当做出修改、变更
前端展现页面编写
1、代码创建
用户表单jsp页面当前提倡使用自动代码生成后,修改布局及提交数量方式。对于多条记录同时提交方式需要自行修改实现,当前代码生成器仅实现一个domain,单记录提交的方式。
example(一个数据域某个属性页面展现):
1 2 3 4 5 6 7 8 9 10 |
|
2、 代码修改
修改页面布局,个性化样式、记录提交数
3、 数据提交实现
当前部分主要有关联的页面按钮触发事件基于js的function实现,且原型也自动生成,单数据提交时无需修改,其余需要修改。
若由自动生成代码则仅实现数据提交方式一,即页面元素构建枚举数组获得XMLDOC对象方式
example:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
后台处理实现
1、总控制器响应接口
当前部分由ParentsAction.java完成,它形如阀门一般管理着所有request请求的进出。即全部的action从这个口进\出。
2、公用(或自定义)控制器处理
由于ParentsAction.java被定义为抽象父类,故各具体业务处理器须继承并扩展实现对于业务处理操作类型。
CommonAction.java公共处理器已实现常规的增、删、改、存储过程执行等操作,用户可在不满足下进行自定义扩展、重载等。
QueryAction.java 查询处理器主要完成涉及list列表、明细detail页面的数据抽取请求控制。
TreeAction.java 实现了js树形对象的查询加载操作。
3、XML字符串请求数据拆分读取可持久化domain对象
XmlMapping.java、BeanProperty.java 共同完成了数据对象domain到xml层次节点数据的拆分、格式化封装等
4、数据库交付、操作
基于开源ibatis的主要API接口实现了JDBC方式的数据库连接、数据编辑操作。
5、返回domain数据对象格式化生成XML格式
参考步骤3描述
6、 响应字符串数据写入repsonse
Command.java输出数据执行器主要完成不同方式数据的response响应输出。已实现方式 text/plain 文本数据、text/xml XML对象数据、Serialized Java Objects 可序列化对象等
7、前台js对象读取、渲染(提示)用户操作
在前台构建js框架中,WMservice.js承接了数据的响应操作,并依据用户参数对象WMparamObject.js中给出的操作类型,分派给相关服务子类(WMeditService 数据编辑、WMqryService 查询\树装载渲染、WMoptService 实时联动对象)执行业务处理
基于WMframework构建的web系统,对于前端用户表单页面jsp,主要由如下部分构成:表头(head)+ 业务展现自定义部分 + 表尾(bottom),对于表头(head)、表尾(bottom)常规情况下我们均是按照include的形式公共引用的,基于WMframework构建的web系统也是如此。
1、前台页面头(head)
1
2
3
4
5
6
7
8
|
<span style=
"font-size: 14px;"
><%@ page contentType=
"text/html;charset=UTF-8"
%>
<%@include file=
"/includes/IncludeTop.jsp"
%>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html>
<head>
<title>用户基本信息</title>
</head></span>
|
这里我们通过<%@include file="/includes/IncludeTop.jsp" %>实现了公共表头的引入。
2、 页面表单
2.1、HTML DOM元素属性说明
除了常见html元素(element)对象所具备的属性外,本次系统我们加入了几个自定义的属性(名称对大小写敏感):
issave:当前元素element值是否需要保存(默认为不需要,所以不写表示不提交该元素的值)
fieldname: 当前元素element对应后台数据对象(domain)的属性名(名称对大小写敏感)
require: 当前元素element值是否为必填(默认为不需要,所以不写表示选填)
datatype:当前元素element值的数据类型
目前已定义的数据类型:
数据类型值 | 数据类型说明 |
date | 日期类型 yyyy-mm-dd |
number | 数字类型0-9 |
integer | 整形数字 |
double | 浮点数字(仅支持小数点后2位) XXXX.XX |
chars | 字符型(目前用于校验用户名、密码等) |
showmessage: 当前元素element值没有通过校验时提示的消息;
以下属性可选择添加
(仅在需要进行dom对象元素比较的时候添加)
compareObject:当前元素与之比较的元素id
compareType:比较类型
目前已定义的比较类型:
比较类型值 | 比较类型说明 |
equal | = 等于 |
notequal | != 不等于 |
greaterthan | >= 大于等于 |
greaterequal | > 大于 |
lessthan | <= 小于等于 |
lessequal | < 小于 |
compareMessage:对比错误时需要给出的提示信息
(仅在使用form构建XmlDocument 对象时使用)
vos:当前元素对应domain对象名称
vo:当前元素对应domain对象组的序号
funcname:当前元素对应操作sqlmap的名称
以下是一个demo,供大家参考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<
span
style="font-size: 14px;"><
input
type="text"
maxlength="20" class="textfile" id='unit_account'
/****自定义属性如下***/
issave="true" //需要保存\处理
fieldname="userAccount" //对应domain中的属性值
require="true" //必填标志
datatype="chars" //当前数据类型
showmessage="用户账号为空或设置错误" //数据错误时给出的提示信息
compareObject="confirmPassword"//比对对象id
compareType="equal" //比对类型
compareMessage="两次输入的密码不一致!"//比对错误时给出的提示信息
vos="EmpDomain" //提交元素属于的domain对象
vo="emp1" //当前提交多个对象时需要区分的标志
funcname="insertEmp"//当前提交执行sqlmap中的function
/>
</
span
>
|
3、数据校验
全部页面表单通过/WebRoot/js/ validate.js进行数据校验,必须把提交的数据封装在一个form(一个页面可以设置多个form表单)中,详情可查看/WebRoot/js/oa/user/user.jsp中的格式。
4、表单数据提交
可以使用两种方式提交:页面元素构建枚举数组获得XMLDOC对象和页面元素直接拼接XML字符串获得XMLDOC对象
4.1、页面元素构建枚举数组获得XMLDOC对象方式
(一) 新增
- 单表(一个domain)数据提交
代码自动生成,修改表单元素布局即可。对应HTML DOM元素属性vo属性值可不加考虑。
注意:须保证HTML DOM元素存放于<form>节点中
- 单表(多个domain)数据提交
代码自动生成,修改表单元素布局即可。复制已生成domain各属性HTML DOM对象,以vo属性值标识当前所属记录。
注意:须保证HTML DOM元素存放于<form>节点中
- 多表(一个domain)数据提交
代码自动生成,修改表单元素布局即可。不同domain对象以vos属性值标识、对应HTML DOM元素属性vo属性值可不加考虑。
注意:须保证HTML DOM元素存放于<form>节点中
- 多表多domain数据提交
综合以上
(二) 修改
代码自动生成、修改表单元素布局即可。
(三) 保存
代码自动生成、修改表单元素布局即可。
(四) 删除
代码自动生成、修改表单元素布局即可。
(五) 查看
代码自动生成、修改表单元素布局即可。
(六) 查询
代码自动生成、修改表单元素布局即可。
- 分页条显示设置
1.grid中默认显示数据每页为15条,
2.自定义页显示数量,在QueryDomain节点中增加pagesize节点、数量,如:+" < pagesize>20</ pagesize> "
3. 当pagesize节点设置为 -1 时,对当前数据部采用分页显示,也不显示分页条
- 序号显示设置
1.grid中设置序号列显示表头的位置,及相关其他属性(注意必须设置到用户结果集表头前)
如:mygrid.setHeader("选择,序号,用户ID,用户账号,用户名称,创建时间");
2. 在QueryDomain节点中增加显示序号标志,默认不需要序号,即不添加+" <isIdx>true</isIdx> "
- 单元数据码名转换设置
在QueryDomain节点中增加optflds节点,并设置需要转换的属性和option名称,多个以逗号隔开。
格式如下: optionName@fieldName,optionName@fieldName
如:+" <flds> OrgOption@orgId,DocTypeOption@docType</flds> "
- 自定义查询js函数设置
系统中默认的js查询函数名称为go,可自行定义,在QueryDomain节点中增加jsFunction节点即可。也可以设置参数。如果一个页面涉及多个查询,多个grid时可以使用当前方式区别
如: +”<jsFunction>testgo<jsFunction>”
4.2、字符串获得XMLDOC对象方式
(须注意XML格式的规范性,不然不能获得XMLDOC对象)
1、由js脚步执行,遍历当前提交表单。获得用户数据,基于js脚本的字符串拼接操作构建XML格式数据。
2、将当前字符串对象赋值WMparamObject.js对象、其余不变
可供参考的列子example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<span style=
"font-size: 14px;"
>
function
doxmlsave() {
var
ac =
new
WMaction();
var
po =
new
WMparamObject();
var
xmlStr =
" <?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+
" <root>"
+
" <vos id=\"UserDomain\"> "
+
" <vo> "
+
" <userName>测试20100816</userName>"
+
" <userAccount>test01</userAccount>"
+
" <userType>1</userType>"
+
" <passWord>123456</passWord>"
+
" <function>insertUser</function>"
+
" </vo> "
+
" </vos> "
+
" </root> "
;
po.setXmlStr(xmlStr);
po.setActionType(
"0"
);
po.setHitObjId(
"hitDiv"
);
po.setTextObjId(
"textdiv"
);
po.setIsasy(
true
);
po.setUrl(cps+
'/testAction.action?action=add'
);
ac.setParamobj(po);
ac.execute();
}
</span>
|
5、XML数据节点说明
WMframework只需要将要提交到后端进行业务处理的数据打包成如下的XML文档提交到后台即可由后台的共用服务组件进行业务处理。
5.1、提交数据格式
节点结构
example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<
span
style="font-size: 14px;"><?
xml
version=’1.0’ encoding=’UTF-8?>
<
root
>--- 根节点
<
vos
id=”OaUser”> -- 数据对象domain的类名(不用加包路径,加已无所谓)
<
vo
>
<
userName
>测试用户</
userName
> ---- 数据对象domain的一个属性值,对应页面元素的fieldname="userName" ,和元素的value
<
userAccount
>demo</
userAccount
> -- 同上
<
password
>123</
password
>
<
userType
>1</
userType
>
<
departmentId
>1</
departmentId
>
<
function
>insertUser</
function
> -- 数据对象domain执行
</
vo
>
--同时提交多个相同的数据对象时
<
vo
>
….
</
vo
>
<
vo
>
….
</
vo
>
…..
</
vos
>
--同时提交多个不相同的数据对象时
<
vos
id=”OaUnit”>
…..
</
vos
>
<
vos
id=”OaDoc”>
…..
</
vos
>
……
</
root
></
span
>
|
5.2、后台返回数据处理
对于通常情况下主要业务的新增、删除、更新操作后直接调用父类,开发人员无需自定义相关后台处理(action、dao、service)等。操作成功:super.doWriteResponse(response, "1");操作失败:super.doWriteResponse(response, "0");
如当前需自定义扩展后台处理,需在继承ParentAction的基础上进行改写,并实现虚方法getData。具体可参考com.bizsys.web.UserAction的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<span style=
"font-size: 14px;"
>
public
ActionForward add(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)
throws
Exception {
UserService userService = UserService.getInstance();
try
{
XmlMapping xm =
new
XmlMapping();
super
.readXMLFromRequestBody(request);
//获得请求数据xml
List list = xm.parseXML2VoLists(DocumentHelper.parseText(
super
.xmls),
true
);
//获得业务数据对象集合
userService.insertObject((List)list.get(
0
));
//单个vos的操作
super
.doWriteResponse(response,
"1"
);
//操作成功
}
catch
(Exception e) {
e.printStackTrace();
//捕获异常,调试的时候用,运行后去掉
super
.doWriteResponse(response,
"0"
);
//操作失败
throw
e;
//抛出异常
}
return
null
;
}
</span>
|
6、注意的问题
1) 现有数据表的映射对象已存放在 package com.ccs.govoa.domain 下,需要自行增加时请提交相关说明
2) 所有sqlmap必须全部存放在package com.ccs.govoa. mappings 下,sql中执行方法的<insert id="insertUser" parameterClass="user"> 名称必须全局(本系统内所有sqlmap)唯一,大小写敏感
3) <vos id="OaUser">节点的属性值id必须是domain对象的类名,大小写敏感
4) <function>insertUser</function> 必须对应sqlMap中执行中的方法名称,大小写敏感
5) 请严格按照系统开发流程制作每个模块
6) 请完全按照页面表单元素属性说明来添加自定义的属性、值
7) 业务后台类中不能写任何关闭当前连接(connect)的语句
8) 业务后台类中不能写任何开启事务,关闭事务的语句
9) 业务类中的异常可捕获调试,但必须向外仍出
1、源码修改记录
1.1、 sqlmap中新增执行节点statements
修改了sqlmap校验解析文件sql-map-2.dtd
修改了sqlmap解析器:SqlMapParser.java
example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<
statements
id="insertUsersList" parameterClass="test.A">//一层节点
<
selectKey
keyProperty="userId" resultClass="String" type="pre">
SELECT P_GET_KEY('SEQ_USER_KEY') from DUAL
</
selectKey
>
<
insert
id="insertUserO" parameterClass="test.A">//二层节点
INSERT INTO A
(
USER_ID ,
USER_NAME ,
PASSWORD ,
create_time
)
values(
P_GET_KEY('SEQ_USER_KEY'),
#userName#,
#password#,
SYSDATE
)
</
insert
>
<
insert
id="insertUserB" parameterClass="test.A">
INSERT INTO SUBA
(
USER_ID ,
EMP_ID
)
values(
P_GET_KEY('SEQ_USER_KEY'),
P_GET_KEY('SEQ_USER_KEY')
)
</
insert
>
</
statements
>
|
1.2、 ClassKey生成使用
修改了sqlmap校验解析文件sql-map-2.dtd
新定义classkey处理类:com.ibatis.sqlmap.engine.mapping.statement. ClassKeyStatement记录当前需要生成的key的class路径
在sqlmap解析器中新增classkey节点的检查校验
com.ibatis.sqlmap.engine.builder.xml.SqlStatementParser
修改了sql执行代理器中关于classkey的取值及参数对象赋值
com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate
example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<!-- classkey产生主键 -->
<
insert
id="insertUserClassKey" parameterClass="test.A">
<![CDATA[ INSERT INTO A
(
USER_ID ,
USER_NAME ,
PASSWORD ,
create_time
)
values(
#userId#,
#userName#,
#password#,
SYSDATE
) ]]>
<
classKey
keyProperty="userId" resultClass="String" type="pre"
className="test.util.GenerateSerial"
methodName="getUUID"
args="1,2"
paramTypes="java.lang.String,int"
>
</
classKey
>
</
insert
>
|
1.3、 insert中多SQL同步保存
sql表示器com.ibatis.sqlmap.engine.mapping.statement. MappedStatement新增属性resSql,用于记录sqlmap中原始定义的sql,便于后期检查是否存在多个sql的分割标志。当前设置为分号“;”
修改sql执行方法,检查当前是否存在多个sql同时提交的情况;
修改sql执行器com.ibatis.sqlmap.engine.execution.SqlExecutor中对于参数的赋值方法
重构参数映射类
com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap中设定参数的方式,即存在多个sql公用某个参数类时,需要进行键值提取,并处理先后顺序
1.4、 批处理优化
修改了sql执行器com.ibatis.sqlmap.engine.execution.SqlExecutor,
添加了sql执行保存器,用于存储已经添加过在批处理桶列中的sql识别集合map。
即扩展当前检查sql执行的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
if
(currentSql !=
null
&& currentSql.equals(sql)) {
//检查是否与前一个执行sql相同
int
last = statementList.size() -
1
;
ps = (PreparedStatement) statementList.get(last);
//add by warison 20101019 检查当前sql是否已经使用过了
}
else
if
(psMap.containsKey(sql)) {
//检查是否已经在过去生成过当前sql
ps = (PreparedStatement) psMap.get(sql);
}
else
{
|
1.5、Debug模式下打印完整的SQL语句
- 改进目标
即使用ibatis时需要在控制台或日志文件中打印其当前执行的完整sql,而不是常见的参数 问号 ? 语句。
- 执行顺序分析
Ibatis中常见API执行流程:
- 修改说明
主要修改com.ibatis.sqlmap.engine.execution.SqlExecutor,找到方法executeUpdate,即ps.execute();语句执行之前,新增如下代码:
1
2
3
4
5
6
7
8
9
|
//打印调试信息 start
if
(log.isDebugEnabled()) {
int
count = ps.getParameterMetaData().getParameterCount();
for
(
int
i =
0
; i < count; i++) {
sql = sql.replaceFirst(
"\\?"
, parameters[i].getClass().getName().equals(
"java.lang.String"
) ?
"'"
+parameters[i].toString()+
"'"
: parameters[i].toString());
}
log.debug(
"===当前执行SQL为==="
+ sql +
"."
);
}
//打印调试信息 end
|
2、扩展使用说明
2.1、sqlmap中新增执行节点statements
sqlmap中新增执行节点 statements,其中可以包含多个子节点(insert、update、delete、statement均可),改功能扩展后用处不太理想
使用参考:参考以上
2.2、ClassKey生成使用
classKey获取主键即为,执行某个class中的方法后生成主键序列的方式,格式定义是为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<
classKey
keyProperty="userId" //生成key存放如parameterClass的属性名称,必填
resultClass="String" //返回key结果类型,必填
type="pre"//insert执行前、后执行当前生成主键方式,必填
className="test.util.GenerateSerial"//当前执行类全路径,必填
methodName="getUUID"//当前执行方法,必填
args="1,2"//参数值,多个以逗号隔开,选填
paramTypes="java.lang.String,int"//参数类型,多个以逗号隔开,选填
>
</
classKey
>
|
以上即表示执行test.util.GenerateSerial中getUUID,并传递参数1,2。返回字符串类型数据存放在userId中
2.3、insert中多SQL同步保存
在sqlmap中扩展insert节点,可确保一次性提交同步执行多条sql,各sql之间以逗号;隔开(当前分隔符可自行定义),这有利于保证主子表同步保存时,关联外键的相同性。而且多个sql语句仅需要定义一个参数类或map等形式。这样我们就没必要再service层定义aop之类以事务管理了,也减少了很多没必要的代码编写
这这里统一可以使用selectKey、classKey产生主键。
当然对于,update、delete同样适用当前方法。
具体写法参考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<
insert
id="insertUserO" parameterClass="test.A">
INSERT INTO A
(
USER_NAME ,
USER_ID ,
PASSWORD ,
create_time
)
values(
#userName#,
#userId#,
#password#,
SYSDATE
);
INSERT INTO SUBA
(
CNAME,
USER_ID ,
FLAG ,
EMP_ID
)values(
#cname#,
#userId#,
#flag#,
P_GET_KEY('SEQ_USER_KEY')
)
|
1
|
</
insert
>
|