Appfuse 最佳实践

声明: 本文从 黄隽实ID:shagoo 博客转帖(http://blog.csdn.net/shagoo/archive/2009/05/05/4151001.aspx),如作者不允许转帖,请联系我删除之,谢谢。

以下是原文:

 

前段时间刚写了《Catalyst Tutorial 最佳实践》,现在又手痒,给大家奉献这篇《Appfuse 最佳实践》,目的主要是趁这段相对比较空闲的时间,多写一些有用的教程,一方面在网上也看到过很多关于 Appfuse 的教程,但是总觉得写的不够系统,看起来不够过瘾~所以这次石头特意通过一个完整的"员工管理系统"的实例来比较系统的介绍一下这个框架的开发技巧,希望大家喜欢~

[Appfuse Best Tutorial]

首先,按照《Appfuse & tapestry 小记》中的第3节(开发笔记)中建立好`Employee`表并用appfuse工具把代码生成好了。
附带DDL:
CREATE TABLE `Employee` (
`id` bigint(20) NOT NULL auto_increment,
`code` varchar(10) NOT NULL,
`dept` varchar(50) NOT NULL,
`name` varchar(20) NOT NULL,
`status` varchar(10) NOT NULL,
`telephone` varchar(20) default NULL,
`title` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后启动jetty,就可以看到首页的"登录"菜单旁边多出来一个"Employee List"的菜单项。
接下来我们做一些界面上的修改(在ApplicationResources_zh.properties添加):
... ...
# -- add by james --
webapp.name=员工管理系统
webapp.tagline=我们以一个员工管理系统来作为开发Appfuse的入门实例.
company.name=员工管理系统
company.url=http://localhost:8080
... ...
# -- Employee-START copied from ApplicationResources.properties
employee.id=Id
employee.code=Code
employee.dept=部门
employee.name=姓名
employee.status=目前状态
employee.telephone=电话
employee.title=职位

employee.added=新员工添加成功。
employee.updated=员工信息更新成功。
employee.deleted=员工信息删除成功。

# -- employee list page --
employeeList.title=员工管理
employeeList.heading=员工列表
employeeList.employee=员工
employeeList.employees=员工

# -- employee detail page --
employeeDetail.title=员工详细信息
employeeDetail.heading=员工详细信息
# -- Employee-END
... ...
然后为菜单赋权,即在menu-config.xml的EmployeeMenu加上roles="ROLE_ADMIN,ROLE_USER",允许用户添加/修改,重启后你就可以看到菜单和界面变成中文了,登录之后你可以试着在"员工管理"板块下做一些简单的CRUD操作。
以下是appfuse:gen所产生/改动的代码,请参考:
resources/struts.xml
resources/ApplicationResources.properties
webapp/WEB-INF/applicationContext.xml
webapp/WEB-INF/menu-config.xml
webapp/common/menu.jsp
webapp/pages/employeeList.jsp
webapp/pages/employeeForm.jsp
java/com/appfuse/app/model/Employee.java
java/com/appfuse/app/model/Employee-validation.xml
java/com/appfuse/app/dao/EmployeeDao.java
java/com/appfuse/app/dao/hibernate/EmployeeDaoHibernate.java
java/com/appfuse/app/service/EmployeeManager.java
java/com/appfuse/app/service/impl/EmployeeManagerImpl.java
java/com/appfuse/app/webapp/action/EmployeeAction.java
java/com/appfuse/app/webapp/action/EmployeeAction-validation.java
这里遇到两个问题需要注意:
a> 输入中文的时候保存数据不正常。
解决方法:这种问题一般都是数据库字段字符集问题,我建议你先修改mysql的配置文件my.ini的default-character-set=utf8,然后再重新建表,检查一下新表的字段字符集是否都为utf8_general_ci,如果是的话这个问题应该就能迎刃而解。
b> 重启jetty的时候修改过的数据会被覆盖回去。
解决方法:重新设置pom.xml里面关于hibernate3-maven-plugin的配置,删除executions命令(把pom.xml的154行到161行注释掉)。同时把969行的<dbunit.operation.type>CLEAN_INSERT</dbunit.operation.type>改成<dbunit.operation.type>NONE</dbunit.operation.type>。
接下来我们就可以开始做一些更深入的设计和编码。

>>> 前期系统设计

光是一张Employee表当然没有办法架设出一个比较完整的公司员工结构,于是我又添加了`Status`,`Dept`和`Title`分别用于存储员工状态、部门信息和职位头衔,DDL如下:
CREATE TABLE `Status` (
`id` int(11) NOT NULL auto_increment,
`status` varchar(10) NOT NULL,
`description` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Status` SET `status`='在职', `description`='工作中';
INSERT INTO `Status` SET `status`='入职', `description`='等待入职';
INSERT INTO `Status` SET `status`='离职', `description`='离开公司';
==========
CREATE TABLE `Dept` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`description` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Dept` SET `name`='董事', `description`='决策';
INSERT INTO `Dept` SET `name`='人事', `description`='招聘';
INSERT INTO `Dept` SET `name`='财务', `description`='算账';
INSERT INTO `Dept` SET `name`='开发', `description`='产品';
INSERT INTO `Dept` SET `name`='市场', `description`='宣传';
==========
CREATE TABLE `Title` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
`description` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Title` SET `name`='经理', `description`='决策管理';
INSERT INTO `Title` SET `name`='主管', `description`='管理员工';
INSERT INTO `Title` SET `name`='员工', `description`='日常工作';
然后生成model并为`Dept`建立基本代码结构(关于appfuse命令,参考http://static.appfuse.org/plugins/appfuse-maven-plugin/plugin-info.html):
#mvn appfuse:gen-model
#mvn appfuse:gen -Dentity=Dept
#mvn appfuse:gen -Dentity=Title
#mvn appfuse:gen-core -Dentity=Status
这里我们生成了"Employee List"和"Title List"两个完整模块以及Status的Dao和Manager核心类。这里值得注意的是,由于我们只需要Status的数据结构,不需要Action和页面代码,所以这里我们使用"mvn appfuse:gen-core -Dentity=Status"命令指定只生成"核心代码",执行过程中有报错,不过没关系,代码还是正确生成的。
然后我们要做的就是和上面提到的"Employee List"类似的设置(但是DeptMenu最好加上roles="ROLE_ADMIN"只允许admin用户添加/修改,这样比较不容易出问题),重启服务,看到界面已经变了,多出了一个菜单"部门管理",你会注意到这个菜单跑到第二行去了,不是很美观,于是我首先考虑能不能把一些没用的菜单去掉~ 按照主流的设计风格,我们很自然的会想要把"退出"这个菜单移到右上方的位置。于是打开menu-config.xml,删除"<Menu name="Logout" title="user.logout" page="/logout.jsp" roles="ROLE_ADMIN,ROLE_USER"/>"这行,以及menu.jsp的"<menu:displayMenu name="Logout"/>"这行,然后在header.jsp相应位置加上如下代码:

由于"退出"选项只对登录用户才有意义,所以我这里使用了SpringSecurity的authorize标签来限定用户。这里顺便提一下这个比较常用的SpringSecurity标签的用法(参考http://static.springframework.org/spring-security/site/reference/html/authorization-common.html):
*ifAllGranted: 满足所有角色。
*ifAnyGranted: 满足任意一个角色。
*ifNotGranted: 所有角色都不被允许。
到这里该系统最主要的系统前期设计工作已经完成,基本代码也生成好了,接下来我们从一些细节地方进行讨论。

>>> 基本功能设计
代码Appfuse已经帮我们生成了,真是省去了我们不少"造轮子"的时间,但是仔细看看,还是有一些不合理的地方,我们到"员工管理"打开"添加"页面,我们看到"部门"这里还是一个输入框,这显然不合理,接下来我们要把这里变成一个下拉菜单并关联刚才添加的"部门管理"的数据。
applicationContext-struts.xml:

struts.xml

 

EmployeeAction.java:

employeeForm.jsp:

做完这些修改后会发现,员工编辑页面"部门"这一栏已经不再是随便输入的文本框,而是下拉菜单了,这样子不仅从系统操作安全性方面提高了很多,而且也使整个系统的各个数据结构可以更好的结合起来。接下来,我们用同样的方法加入"职位管理"这个模块,同样的代码修改过后,于是员工编辑页面的"职位"这个选项也变成关联的下拉菜单了。
我们保存一下,功能正常,但是返回"员工列表"的时候发现了一个不好的事情,那就是编辑过的"部门"和"职位"栏都变成数字了,这是怎么回事呢,很明显我们刚才使用的方法是有问题的~ 我们只是从界面的角度把`Dept`和`Title`这两张表的内容结合到"员工管理"去,但是实际上从数据层面这几张表并没有真正的"关联"起来~ 于是我们对`Employee`表作如下调整:
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL auto_increment,
`code` varchar(10) NOT NULL,
`dept_id` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
`status` varchar(10) NOT NULL,
`telephone` varchar(20) default NULL,
`title_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
我们看到原varchar型的dept和title字段分别变成了int类型的dept_id和title_id(建议这里命名遵循一般的Hibernate的设计原则),准备作为外键关联`Dept`和`Title`表(如果是大范围的字段修改我们也可以使用mvn appfuse:gen-model来重新生成model类,但是像这种小范围的修改,我还是建议大家手动来修改一下对应的model类)。
然后我们修改com.appfuse.app.model.Employee类:

 

可以看到我们为Employee的新model加入了@ManyToOne映射(注意appfuse新版本使用的是JPA的设置规范,个人认为这比写映射文件更简洁和直观),接着我们修改employeeList.jsp:

然后我们重启一下服务,重新进入"员工列表"页面,我们发现原先的数字不见了,取代的是关联的职位名称,酷~ 到这里大家应该也可以体会我开始的时候为什么说appfuse是"第一次让我感觉到‘轻量'的J2EE框架"了吧,代码改动可以说是"前所未有"的小了~
* 这里大家可能会遇到以下问题:org.hibernate.ObjectNotFoundException: No row with the given identifier exists
有两张表,table1和table2. 产生此问题的原因就是table1里做了关联<one-to-one>或者<many-to-one unique="true">(特殊的多对一映射,实际就是一对一)来关联table2.当hibernate查找的时候,table2里的数据没有与table1相匹配的,这样就会报这个错(简单来说就是数据的问题)。
别忘了还有employeeForm.jsp:


这里看到"employee.dept"和"employee.title"的select控件的默认值我们已经设成"employee.dept.id"和"employee.title.id",这样才可以在编辑页面载入正确的默认值,至于"employee.status"我们做的修改是把listKey="id"变成listKey="status"直接记录status的值到`Employee`表的status字段中去,并没有关联`Status`表,其实对于一些比较固定的小配置表我们完全可以选择这种方案(减少表关联操作),适合的才是最好的嘛,呵呵~
* 另外,大家如果要查看Hibernate生成的sql语句,可以在log2j.xml里面打开如下注释即可:
 

>>> 高级功能设计


到这里我们的"员工管理系统"的功能已经基本完整了,但是如果要变成一个真正的企业管理工具,还有很多的工作要做,由于篇幅限制,我们在这个部分只介绍一下分页功能的实现吧,其他更高级的用法就留给大家自由发挥了:)
实际上由于Appfuse使用displayTag作为表格展示的工具,所以也使我们省去了不少界面和编码方面的工作,所以我们接下来就来介绍一下displayTag的常用功能,然后分析一下优缺点,最后我们就大数量分页进行一下研究。


1> 常用功能


分页功能:如果想对代码分页,只需在display:table标签中添加一项pagesize="每页显示行数",为了测试我们在employeeList.jsp里面把分页数设小一点<display:table name="employees" class="table" requestURI="" id="employeeList" export="true" pagesize="5">,这样应该就可以看到分页的links,至于显示样式,我们可以在styles/displaytag.css修改。
排序功能:可以为table设置默认排序列(defaultsort),以及排序方式(defaultorder:"ascending"or"descending"),以及排序范围(sort:"page"or"list"),另外,需要排序的列只要为该column加上sortable="true"就好了。
导出数据:默认有CSV,Excel,XML,PDF这几种输出方式,appfuse默认就设置了可以看看代码。
其他功能:displayTag还有很多功能,例如计算总数等,可以参考:http://displaytag.sourceforge.net/1.2/displaytag/tagreference.html


2> 优缺点分析


优点:很明显用displayTag帮我们节省了很多重复"造轮子"的时间,而且看起来功能也算强大。
缺点:分页样式不够灵活(links),似乎只能打出所有页数。另外,如果你打出取数据的sql语句就可以发现,displayTag默认是一次把所有的数据取出来,然后再排序的,如果数据量比较大的时候会有性能问题。


3> 大数据分页


上面分析到了在默认情况下,displayTag的分页是很低效的,因此我们必须像一个方法来,关于这点displayTag推荐两种解决方案,一种是使用partialList="true"和size="resultSize"这两个标签来解决,但实际上这个方案是从内存分页的基础上改过来的,打出来的sql实际上没有变化,所以我们使用第二种方式,那就是实现PaginatedList接口。
PageList.java:

 

以上就是PaginatedList的实现。
GenericDao.java:


以上为GenericDao添加两个方法,准备在GenericDaoHibernate中实现,getListForPage取得分页列表,getTotalCount取得数据总数。
GenericDaoHibernate.java:


以上为getListForPage和getTotalCount两个方法的实现,注意的一点是我们这里使用hibernateTemplate的回调函数来传参给hibernate的sessionFactory进行处理,参考这种方法可以自己编写需要的sql,让程序更加灵活。
EmployeeDao.java:


以上使用GenericDao中定义的两个方法很方便的取得employee分页信息。
applicationContext-struts.xml:


然后把employeeDao通过Spring Ioc注入到EmployeeAction中使用。
EmployeeAction.java:

 

以上使用getPageList和getPageCount两个方法把取得的分页信息赋给pageList,然后传给显示层的displayTag组件进行渲染展示。
employeeList.jsp:

 


我们这里把name换成employeePageList,但是要注意这么做了之后,原先的排序等功能就不再起作用的,而必须通过程序实现,所以这里可以先把sortable="true"去掉。
到这里我们这次的代码修改算是结束了,我们成功的实现了PaginatedList接口,并通过自己编写的sql来取得分页信息,应该说通过这次修改diplayTag的性能已经"脱胎换骨",可以适用于大数量的结果查询了。

 

>>> 教程总结


到这里我们使用appfuse框架只花了很少的时间,编写了很少的代码,就已经搭建了一个完整的包含权限管理的员工管理系统,我们可以使用admin用户建立新的普通管理帐户来分配给指定的人员来使用这个系统,普通管理帐户只有"员工管理"模块的权限,而admin则还有"部门管理"和"职位管理"的权限,整个系统层次清晰,功能完整;另外,基于Spring Security的框架还让该系统可以无缝集成到一些主流的SSO系统集群和基于LDAP的企业工具中去,真是非常棒一个解决方案~

 

>>> 回顾展望


实际上appfuse还有很多功能没有介绍完,比如xfire做webservice(访问http://localhost:8080/services可见),dwr的使用以及发送邮件等,但是由于篇幅问题,这里只介绍到这里了,有空的话我会抽时间把这些部分内容补充一下,如果朋友有什么疑问或者建议,欢迎与我联系交流~ 待续~

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值