2006年8月11日

论剑归来(一)

应朋友邀请,周六早从上海出发往杭州参加阿里巴巴网侠大会。同行四人,有锐道的macro chen、杨光(还是我师弟)、移动的王伟旭(特长是linux和网络安全,也是中国linux推广的先驱)。一路上,言谈甚欢。老庄给我们订的票,他一早肠胃有恙,仍然坚持把票送到火车站,之后去吊盐水,下午又出现在会场。确实精神可嘉,建议阿里巴巴颁发“最佳精神奖”。

到杭州已是中午,错过了上午大会。下午Robbin进行Java技术展望和RoR实现REST的演讲,既然是朋友,肯定是要捧场的。Robbin旁征博引,以其深厚的技术功底和对新技术的敏锐洞察赢得了听众。

晚上一堆人去聚会,各路豪杰纷至:有阿里巴巴的,有自己创业的,有技术大牛,还有媒体(Infoq),出版社(博文的周总领3员大将赴会)。大家互换名片,认识的不免寒暄几句,不认识的也很快就熟捻了,还不时有“原来你就是×××”的惊呼,原来网上就“互通心曲”,只是一直没机会认识罢了。

席间觥筹交错,具体内容暂且不表,只说一件令我感受颇深之事。一个阿里巴巴的员工表现出对公司的无比忠诚,讲起公司的奖惩制度,说是一个员工的绩效不仅跟所在项目相关,还与部门、其它部门甚至整个公司的业绩相关。所以只要是对公司有利的事情,即使与自己现在的工作无关,他们也会去做。按常理来说,这有点不公平,我只能努力做好自己的事情,而如果别人不努力,我就是白做。但如果大家都努力,又变成了共赢。

这里让我讲一个简单的博弈问题,就是“囚徒困境”。AB两个同犯被抓,因为没有其它任何证据和证人,只能让2人分别交供。如果AB都矢口否认,那么两人无罪释放。如果A承认,B不承认;A是坦白从宽,判1年;B抗拒从严,判5年,反之亦然。如果2人都承认,ok证据确凿,各判2年。如果2人都是理性人,且没有互通消息,按照博弈,每个人的最优解就是承认,也就是各判2年。其实对2人真正有利的就是打死不承认然后都无罪释放,而这种状态在理性人的假设下是很难实现的--除非有一个教父,一直灌输他们不要出卖同伙。

马云就是这个“教父”!

卡内基有篇文章,我总结成一句话就是:用崇高的理想打动别人。据说马云一直是以个人魅力及“创造中国电子商务的明天”类似的理想,激励员工的。有了统一的企业文化,员工都不计较个人得失,努力奋进,最终企业和所有员工取得共赢,这绝对是摆脱“囚徒困境”的典型案例。

话说回来,阿里巴巴能让你感受到团队的力量,一群精英在一块做很有价值的事情,对每个人也是很好的锻炼。个人认为,如果有吃苦耐劳的打算,眼光放长远点,又没有其它方面的束缚,阿里巴巴的确是不错的选择。(得向阿里巴巴收代言费,呵呵!)

第二天听了多场论道,主要是SAAS,搜索,分词方面。结合阿里巴巴的战略,我把几点融合起来讲一下。这个下篇再细细道来。

 

 

posted @ 2007-05-21 20:35 pesome 阅读(1097) | 评论 (1)编辑 收藏

sitemesh的问题及解决

再说 sitemesh 的问题,同样是做 openfans 网摘功能出现的。做好了 IE 插件,有 fans 反馈说弹出页面太大,这也难怪,用了 sitemesh head footer 都在页面上,想缩小页面,肯定得把 head footer 都去掉,做个干净点的。既然用了 sitemesh 肯定是希望最方便的搞定这个,而不用动任何逻辑(包括页面逻辑)。这个好办,文档里有说,通过在 sitemesh.xml 中增加

< mapper

           
class = " com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper " >

           
< param name = " decorator.parameter "  value = " decorator "   />

           
< param name = " parameter.name "  value = " confirm "   />

           
< param name = " parameter.value "  value = " true "   />

       
</ mapper >

就能搞定,这样只需要使用参数如 saveSnippet.html?decorator=simple&confirm=true ,就能让它使用指定的 decorator 。然后做一个去掉 head footer 只保留必要的 js css 引用的 simple.jsp 页面,同时在 decorator.xml 中配

< decorator name = " simple "  page = " simple.jsp " ></ decorator >

就应该可以搞定。

但怎么测,就是不使用这个 simple decorator 。没办法,只好跟进 sitemesh 源码中去调试,进入 ParameterDecoratorMapper 类,它继承了 AbstractDecoratorMapper 类,然后有一个 init 方法一个 getDecorator 方法。各打一个断点, init 方法在 tomcat 启动就会执行,也正常初始化了, getDecorator 方法却始终不调用,一时想不出办法。看到篇文章说不自己写 sitemesh.xml 而使用自带的 com/opensymphony/module/sitemesh/factory/sitemesh-default.xml 也可以,于是备份并删除我写的 sitemesh.xml ,居然成功了。为什么我写的就不行呢?差别一是我的简化了很多,只有

< mapper

    
class ="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper" >

           
< param  name ="config"  value ="${decorators-file}"   />

       
</ mapper >

和刚加的

ParameterDecoratorMapper ,二就是顺序,我的 ParameterDecoratorMapper 放在了 ConfigDecoratorMapper 后面,会不会是顺序原因呢,调换一下,果然成功了。问题是解决了,原理呢?再花点时间跟一下代码了解下原理,原来使用了 Factory ,它会调用各个配置的 Mapper init 方法,并放入 map 中,然后到时选择一个 Mapper ,如果符合条件就使用,否则继续往下找。这里就有了顺序的问题,因为我在 decorator.xml 中使用了
<decorator name="default" page="default.jsp">

        
<pattern>/*.html</pattern>

    
</decorator>

,这样如果 ConfigDecoratorMapper 放在前面,他就先找这个 Mapper ,一看 saveSnippet.html?decorator=simple&confirm=true ,符合 pattern 啊,就使用它了,另一个 ParameterDecoratorMapper 根本得不到机会,所以也进不了它的 getDecorator 方法。换了顺序就是它先进去,所以执行,其它的 url 也是先找它,但不匹配,就又交给 ConfigDecoratorMapper 了。呵呵,问题也解决了,对 sitemesh 的认识又进了一步。

posted @ 2007-03-11 22:54 pesome 阅读(2956) | 评论 (0)编辑 收藏

中文问题的解决

这次给 openfans 做网摘功能,主体程序倒是很快就写完了,另外要做个 IE 插件,却碰到了不少问题。 IE 插件其实很简单,就是用 js 获得页面的标题、 url 和选择的内容,然后通过弹出窗口,将其送到服务器。这里就有中文的问题了,开始使用 escape ,如 escape(title) 形式, request.getParameter 碰到中文就为 null ,网上搜了一通,说是可以通过 java 编码搞定,但拿到就为 null 了,还怎么换编码?忙活了好几个小时,又是 alert ,又是 document.write ,看上去也没什么问题。不 escape ,直接在浏览器中输入带中文的 url ,拿到的不为 null 了,拿到后,通过 new String(str.getBytes("ISO-8859-1"), "UTF-8"); 还真显示正常了。但用 window.open 又出乱码了。看到文章说还有 encodeURIComponent 方法可用,就试了下,把 escape 换成 encodeURIComponent 居然搞定了,服务端还是得用 new String(str.getBytes("ISO-8859-1"), "UTF-8") 进行处理。注意这里用的 tomcat ,它的默认编码就是 "ISO-8859-1" ,如果改了编码程序也得做相应的改动了。

posted @ 2007-03-11 22:51 pesome 阅读(1003) | 评论 (0)编辑 收藏

hibernate native sql的小技巧

为了性能考虑,使用了 native sql 。因为需要分页,需要 2 sql ,一个获取 list 一个取得总数。获取 list 很好写:

 

 1 private  List getListByNativeSQL( final  Class cls,  final  String sql)  {
 2
 3         return  (List) getHibernateTemplate().execute( new  HibernateCallback()  {
 4
 5             public  Object doInHibernate(Session session)
 6
 7                    throws  HibernateException  {
 8
 9                return  session.createSQLQuery(sql).addEntity(cls).list();
10
11            }

12
13        }
);
14
15     }

16

 

获取总数查了下 hibernate reference, 试了几次才明白用法 :

 1 private  BigInteger getCountByNativeSQL( final  String sql)  {
 2
 3         return  (BigInteger) getHibernateTemplate().execute(
 4
 5                new  HibernateCallback()  {
 6
 7                    public  Object doInHibernate(Session session)
 8
 9                           throws  HibernateException  {
10
11                       return  (BigInteger) (session.createSQLQuery(sql).uniqueResult());
12
13                   }

14
15               }
);
16
17     }

18

这里的

sql 是“ select count(*) 开头的”。这里大家可能要问,为什么要使用 BigInteger ,因为如果用 uniqueResult() 默认就返回 BigInteger ,而 BigInteger cast Integer 会出错。那么如果我就是要返回 Integer 呢,可以通过下面的办法实现:

 1 private  Integer getCountByNativeSQL( final  String sql)  {
 2
 3         return  (Integer) getHibernateTemplate().execute(
 4
 5                new  HibernateCallback()  {
 6
 7                    public  Object doInHibernate(Session session)
 8
 9                           throws  HibernateException  {
10
11                       return  (Integer) (session.createSQLQuery(sql).addScalar( " count " , Hibernate.INTEGER).uniqueResult());
12
13                   }

14
15               }
);
16
17     }

18

大家注意粗体的部分,这里是给一个

alias 赋予类型,那么 sql 就需要变成以 ”select count(*) as count ” 开头了。

posted @ 2007-03-02 10:52 pesome 阅读(5221) | 评论 (2)编辑 收藏

php5+mysql5+apache2需要注意的几点

今天为了在本机装个wordpress玩玩,搞了搞php5+mysql5+apache2。网上搜了一篇文档,很快就让php与apache跑起来了,但连mysql始终不行。报错:Call to undefined function mysql_connect()。查了一下半天,就是php关于mysql的ext没配好,但我改了php.ini啊,也把"extension=php_mysql.dll"放出来了。查了好久,看到一篇说php5需要加上"extension=php_mysqli.dll",试了下果然好了。
     然后需要以index.php作为默认的welcomefile(不知道怎么叫,web.xml里是这个),需要在"DirectoryIndex index.html index.html.var"后加上 index.php就行。
然后飞快的装了phpmyadmin、dvbbs的php版。发现php应用的安装的确很是方便,解压,拷贝到htdocs下,马上就能运行了,比java应用简单的多,更别提复杂的要死的企业应用了。这点上java要好好向php学习啊。

posted @ 2007-01-14 17:34 pesome 阅读(1260) | 评论 (0)编辑 收藏

开始研究电子支付

项目需要,开始研究电子支付。国外的电子支付提供商,得好好研究它的文档和api。全是e文,只能慢慢看了。

posted @ 2007-01-12 15:41 pesome 阅读(1261) | 评论 (1)编辑 收藏

准备给openfans添加spring2.0支持

  学习了下spring2.0。对openfans而言,有2个比较重要的改进。首先是aspectj的支持,可以方便的使用aspectj语法定义aspect和pointcut了,openfans准备在domain object的自动注入上和权限等方面使用aop。另外就是spring form标签库的引入,现在springmvc也有自己的标签库,以前自己给checkbox和radio写的request.getParameter可以改写了。

posted @ 2006-12-28 15:15 pesome 阅读(1114) | 评论 (1)编辑 收藏

一个定时更新cache框架

     摘要: 应项目需要做了一个定时更新的 cache 框架,采用 spring+quartz 很方便的实现,可以适用任何需要定时才更新的地方,比如静态网页 cache 等。代码很简单: ---------------------------------QuartzCacheHandler-------------------...  阅读全文

posted @ 2006-12-13 14:46 pesome 阅读(4097) | 评论 (7)编辑 收藏

这次做portal的一些总结(二)

接着前面的写。上文主要写了 ajax portal 中的使用,这篇写集群方面的体会。现在比较流行的架构就是前端 F5 做负载均衡,后面 2 websphere server 做成集群,各自都有 HttpServer ,每个 HttpServer 都向 2 was 做转发。这样每台都能独立完成从 HttpServer was 的流程。一台出现故障, F5 首先进行切换,只向正常 server HttpServer 发起请求,这台 HttpServer 再进行切换只向同一台 server 上的 was 做转发。这次 portal 就是采用的这种架构,不妨称为架构 A

另一种简单点的架构就是只做 F5 负载均衡,不做 was 集群,每台 websphere server 上的 HttpServer 接受 F5 转发的请求,只向本 server was 转发。这样每台 websphere server 保持独立,相互间没有数据交换和转发。不妨称为架构 B

架构 A B 各有优劣,适合不同的需要,下面进行些比较:

Ø         从应用部署上看:

A 使用了 websphere 集群,由一个 DeployManager 进行分发,部署应用,只需部署一次,由 DM 分发到几个节点上。而 B 每个 server 都是独立的,部署应用只能一台台部署,如果 server 较少差别还不明显,如果达到 10 台以上,一台台部署将是一个比较痛苦的事情。

Ø         session 上看:

A 使用了 websphere 集群,可以使用集群提供的 session 复制,对于一些关键应用(某台服务器宕机, session 也必须保持的应用)很有必要。而对于一些能够允许 session 丢失的应用,才可以使用 B 。当然 A 也可以关闭 session 复制,因为 session 复制不管是使用数据库方式还是内存方式,总会消耗一定的性能。具体消耗多少性能,就要看不同的 application server session 复制方案了,想深入了解,可以看集群方面的文档,我也只记得一个比较简单的 round robbin 了。

Ø         从架构复杂性看:

B 更为简单,因为没有 DM 的概念,每台 server 都保持独立。而使用了 DM 有时也会出现莫名奇妙的问题,这当然是由于不了解 DM 的机制所致,但总归也增加了复杂度,这点在后面的教训中进行说明。

Ø         从水平扩展性上看:

B 肯定更胜一筹。只要 F5 能支持,多少台 server 都没关系。而 A 多台 server 做集群,要看 websphere 支持的节点数量,应该不会太大。这个如果哪位同学知道,敬请告知。

当然 A B 在服务器较多的情况下是可以共存的,可以考虑几台机器做集群,然后集群间做负载均衡,这样既可以减少部署的复杂度,又可以带来较好的水平扩展。由于没做过更大型的项目,这个也只是我的假象,请做过的同学斧正。

 

说一说集群中碰到的问题。

Ø         首先是对各节点的同步:

有时为了方便测试,我们只对其中一个节点进行更改,测试通过再放到其它节点。而如果测试周期较长,有时就会造成节点的不同步,出现各种各样莫名其妙的问题。一个经验就是:无论如何,在每天下班前要保证各节点的同步,不同步的现象不要过夜。

Ø         然后是对 DM 的理解:

我现在还只是实践阶段,没有看过相关文档。从意义上看,它控制了相关的配置文件,如果进行节点同步,就会由它把配置文件同步到它管理的节点上。这对配置文件的修改提出了要求。我们开始只修改节点的配置文件而没有修改 DM 的,结果进行节点同步就会覆盖修改的配置文件,带来很多不必要的工作。经验就是:或者修改 DM 的配置文件,然后进行节点同步,或者直接同时修改所有节点和 DM 的。

Ø         还有关于 cache 的:

Cache 是性能优化的一个有效手段。在单机环境下,最简单的就是内存 cache ,使用 static Map 就行。而在集群环境中, cache 就变的比较复杂了。首先还是从应用需求入手,是否要保持每台机器的 cache 同步。如果只是信息展示等要求不高的 cache ,不需保证 cache 的同步,问题也比较简单,自己写内存 cache ,或者使用开源的 cache 组件如 ehcache,oscache 等就可以很好的解决问题。而如果需要 cache 在几个节点保持同步,就需要特殊的机制了, ehcache 等号称支持分布式 cache ,但好像需要 jgroup ,配置比较麻烦,我没有用过,有用过的同学请指教。我本来想使用 session 保存,然后进行 session 同步,后来 IBM 建议使用数据库 cache ,即自己写代码, cache 在数据库中。这样不需要 session 同步,对象不大,性能也能得到保证,现在用下来效果还可以。

 

posted @ 2006-12-13 13:39 pesome 阅读(3757) | 评论 (9)编辑 收藏

这次做portal的一些总结(一)

这次做 ibm portal ,算是临危受命。做了几个月的 SA 离职,留下一个功能和性能都有很多问题的项目,临时让我顶上。经过一个多月的紧张工作(经常加班,上班上不了网,也没时间上网),总算功能和性能上都能达到客户要求了。而我也由一个不懂 portal 的人,经过项目中实战,不说成为高手,一般的概念、开发、配置、优化等也都有了很多体会。

这次技术上值得推荐的就是合理的使用 ajax ,既加快了首页的 load 速度,又带来了很好的用户体验。开始首页上所有 portlet 都是串行加载,有的 portlet 比如新邮件,依赖于 mail 系统提供的接口。开始这个接口在较大压力下就出现性能瓶颈,后在我们的要求下替换了协议,性能也在 1s-2s 之间。如果采用常规的办法,加上 wps 验证、运算,显示主题、皮肤,加载所有 portlet ,响应时间肯定在 10s 以上。

我在 openfans 中使用了 ajax ,有些经验,所以决定采用异步加载:首页 load 时一些 portlet 直接显示正在 loading 的字样,在 body onload 时再使用 ajax 填充内容;使用 iframe portlet ,也是 src 先指向一个静态的正在 loading 页面, body onload 时再替换 src 到实际地址(这是 ajax 模式的一种)。这样首页登录实际上只经过 wps 内部的验证和显示,所有业务逻辑都是加载成功后再并行进行。实际表现效果就是:头上的主题很快出来,一块块区域显示正在 loading 字样,性能快的 portlet 很快出来,需要几秒的 portlet 随后出来,而不是让用户傻等 10 s 再一下全部显示。

使用 ajax 同时也能解决页面刷新问题和获取返回值的问题。比如前面显示新邮件的 portlet ,用户点击了一封邮件,新邮件数应该减 1 ,刚点击的邮件也应该上页面上消失。原始的做法就是刷新整个页面,既加大服务器压力,又带来很差的用户体验。使用 ajax ,在点击后 1s (或者更长,这取决于邮件系统对点击操作的响应快慢)刷新 div 的内容,用户甚至感觉不到内容已经更新。其它 portlet 也不需要重新载入,大大减轻服务器的压力。有的操作需要提交给其它系统,而且可能成功可能失败,这就需要获得返回值。如果使用普通的 form 提交,需要更新整个页面。而使用 ajax 提交,可以方便的获得其返回值,进而显示不同的提示。

另一个架构上的特点就是 portal 服务器职责单一 。开始所有的业务逻辑都是写在 portlet 里,加重了 portlet 服务器的压力。我进来后做的一个大的规划就是,把业务逻辑抽离到其它 server 上,然后通过 ajax 加载到 portlet 中。这样既可以充分利用服务器资源(新的 server 使用单独的内存空间和线程池),又使得 portal 服务器职责更单一:仅进行验证、权限控制、主题、皮肤和 portlet 的展示。

先写这么多。因为使用了 2 server 做集群,在分布式环境下,开发也有了更多的要求(比如 cache ),后一篇文章再细细道来。

posted @ 2006-11-28 14:49 pesome 阅读(3586) | 评论 (9)编辑 收藏

程序之外

难得有空,写篇程序之外的文章,关于压力的,也是自己近来的亲身体会。

众所周知软件这行压力是很大的。各种各样的问题层出不穷,每天上班工作内容都是排的满满的,遇到突发问题就得加班。如果不及时进行疏解,积累到一定的程度,就可能产生一定的负面问题,比如上班精神状态差、注意力不能集中、遇事喜欢逃避等等。我就亲身经历了这样的状况,明知自己工作积极性差、效率很低,但也很难一下子找回自我。

一次偶然的出游让我从中很快走了出来。一个亲戚考上厦大的博士,我请了 2 天的假,利用周末时间顺便去厦门旅游。厦门依山傍海,的确是旅游的好去处。晚上到海边,凉风习习,光脚沿着沙滩走过,任起落的潮水在腿上脚上留下层层薄沙。内心也变得平静,能够感受到海的呼吸。天地间仿佛只剩下我和大海,在进行心灵的交流,俗世烦扰皆抛诸脑后,只剩下对海的依恋。白天去爬南普陀山,并不太高,慢慢爬到山顶,整个思明区尽收眼底,远处一艘快艇在海面掠过,留下一条美丽的浪花。然后顺山而下到植物园,途径无数奇花异草、层天老树,走得累了,找个湖边石凳休息一下,人也觉得轻松愉快。

经过大自然的洗礼,回到单位,人的精神面貌焕然一新,抱着积极的心态处理事情,很多问题迎刃而解。压力测试做的很累,经常要熬夜,但通过一轮轮的测试,逐步定位到性能问题所在,自己也学了不少相关知识,想想也就没那么烦了。

做事的方式,也有了长进。我现在信奉人一时只做一件事效率最高的原则。事情再多,也是一件件做,每天安排好近日的工作,并排个优先级,什么是要亲自处理的,什么是让别人处理的,什么是需要预先通知他人的,需要什么资源,每件事情的预计时间如何,需要如何 check 等等。做好一件事就打个勾,做到心中有数。如果事情有延误,分析是什么原因,该如何补救,而不要有太大的心理负担,自己尽力了就好,是自己的责任就要勇敢扛下,死不了人的。这其实是很简单的原则,谁都能够学会,但的确很管用。

总结:压力是无处不在的,关键在于如何应对和排解。用积极的心态和恰当的方法面对,压力也就没那么大了。感觉压力积累到一定程度,在还未影响正常工作之前就先想办法排解,出去旅游、运动等都是缓解压力的好办法。

posted @ 2006-11-06 22:27 pesome 阅读(1124) | 评论 (2)编辑 收藏

关于代码的一些比喻

最近对项目组的一些较差的代码进行了些重构,同时灵光一闪,对代码有些比较形象的比喻。

坏的代码就象揉面团,管什么接口什么实现全揉成一团,一个方法几百行,注释写再多也是面团(夹了些小纸条而已)。然后需要重用了,就是从中抓起一把面团,然后放到其它的面团里继续揉。这样重复代码一堆,什么易读性、扩展性、可维护性都是无从谈起。

好的代码就象堆积木,接口实现定义清清楚楚,每个接口只做一件事情,重复代码都是通过更细的接口来消除。重用就是把积木块往该放的地方堆,这样的代码,几个大块几个小块一目了然,只要方法命名规范,连注释都可以省去。这样耦合性低,易读性、扩展性、可维护性都可以得到保证。

把面团变成积木并不复杂,定义好模具,面团一团团往里面填充,待稳定下来,就成了一块块积木。这里关键就是模具的制作,推荐制作宝典: martin fowler 的那本重构。还得有模具的丈量工具,就非 junit 莫属了。

posted @ 2006-10-30 17:42 pesome 阅读(1440) | 评论 (0)编辑 收藏

数据库同步trigger的记录

    项目需要写了几个数据库同步用的 trigger ,就是记录用户的操作到一个 temp 表,然后每天通过 webservice 同步到其它系统,同步成功清空该 temp 表。自认为写的还行,做个记录。是 db2 的。

 

-- 用户组新增触发器

--DROP TRIGGER TG_USERG;

CREATE TRIGGER LIBING.TG_USERG AFTER INSERT ON LIBING.TM_USERG

  REFERENCING NEW AS NROW

  FOR EACH ROW

  MODE DB2SQL  

  BEGIN ATOMIC

 

  declare @groupId integer;

  declare @name varchar(30);

  declare @descn varchar(100);

  declare @syntype varchar(4);

  declare @ddlsql varchar(1024);

  declare @isprimary char(1);

  declare @updateTime timestamp;

  declare @createTime timestamp;

  declare @createBy integer;

  declare @updateBy integer;

  declare @groupType integer;

  declare @adminType integer;

  declare @appId integer;

 

  declare @oldGroupId integer;

 

  set @groupId=NROW.GROUP_ID;

  set @name=NROW.name;

  set @descn=NROW.descn;

  set @syntype=NROW.syn_type;

  set @ddlsql=NROW.ddlsql;

  set @isprimary=NROW.isprimary;

  set @updateTime=NROW.update_time;

  set @createTime=NROW.create_time;

  set @createBy=NROW.create_by;

  set @updateBy=NROW.update_by;

  set @groupType=NROW.group_type;

  set @adminType=NROW.admin_type;

  set @appId=NROW.app_id;

 

  INSERT INTO TM_USERG_TEMP(GROUP_ID,NAME,DESCN,DDLSQL,ISPRIMARY,UPDATE_TIME,CREATE_TIME,

              CREATE_BY,UPDATE_BY,GROUP_TYPE,ADMIN_TYPE,APP_ID,ACTION) VALUES (@groupId,@name,@descn,

               @ddlsql,@isprimary,@updateTime,@createTime,@createBy,@updateBy,@groupType,@adminType,@appId,'INSERT');

  END;

 

  -- 更新用户组数据的触发器

 -- DROP TRIGGER TG_USERG_UPDATE;

  CREATE TRIGGER TG_USERG_UPDATE AFTER UPDATE ON TM_USERG

              REFERENCING NEW AS NROW

               FOR EACH ROW

               MODE DB2SQL

               BEGIN ATOMIC

               

               declare @groupId integer;

              declare @name varchar(30);

              declare @descn varchar(100);

              declare @syntype varchar(4);

              declare @ddlsql varchar(1024);

              declare @isprimary char(1);

              declare @updateTime timestamp;

              declare @createTime timestamp;

              declare @createBy integer;

              declare @updateBy integer;

              declare @groupType integer;

              declare @adminType integer;

              declare @appId integer;

               

               set @groupId=NROW.GROUP_ID;

              set @name=NROW.name;

              set @descn=NROW.descn;

              set @syntype=NROW.syn_type;

              set @ddlsql=NROW.ddlsql;

              set @isprimary=NROW.isprimary;

              set @updateTime=NROW.update_time;

              set @createTime=NROW.create_time;

              set @createBy=NROW.create_by;

              set @updateBy=NROW.update_by;

              set @groupType=NROW.group_type;

              set @adminType=NROW.admin_type;

              set @appId=NROW.app_id;

               

               -- 如果已经有 update 则只记录最后一条 update

               IF EXISTS(SELECT GROUP_ID FROM TM_USERG_TEMP WHERE GROUP_ID=@groupId AND ACTION='UPDATE') THEN

                     UPDATE TM_USERG_TEMP SET GROUP_ID=@groupId,

                                   NAME=@name,DESCN=@descn,DDLSQL=@ddlsql,

                                   ISPRIMARY=@isprimary,UPDATE_TIME=@updateTime,

                                   CREATE_TIME=@createTime,CREATE_BY=@createBy,

                                   UPDATE_BY=@updateBy,GROUP_TYPE=@groupType,

                                   ADMIN_TYPE=@adminType,APP_ID=@appId,ACTION='UPDATE'

                                   where GROUP_ID=@groupId AND ACTION='UPDATE';

               -- 如果有 insert 则把后面的 update 当作 insert

              ELSEIF  EXISTS(SELECT GROUP_ID FROM TM_USERG_TEMP WHERE GROUP_ID=@groupId AND ACTION='INSERT') THEN

                     UPDATE TM_USERG_TEMP SET GROUP_ID=@groupId,

                                   NAME=@name,DESCN=@descn,DDLSQL=@ddlsql,

                                   ISPRIMARY=@isprimary,UPDATE_TIME=@updateTime,

                                   CREATE_TIME=@createTime,CREATE_BY=@createBy,

                                   UPDATE_BY=@updateBy,GROUP_TYPE=@groupType,

                                   ADMIN_TYPE=@adminType,APP_ID=@appId,ACTION='INSERT'

                                   where GROUP_ID=@groupId AND ACTION='INSERT';

              ELSE      INSERT INTO TM_USERG_TEMP(GROUP_ID,NAME,DESCN,DDLSQL,ISPRIMARY,UPDATE_TIME,CREATE_TIME,

                              CREATE_BY,UPDATE_BY,GROUP_TYPE,ADMIN_TYPE,APP_ID,ACTION) VALUES (@groupId,@name,@descn,

                               @ddlsql,@isprimary,@updateTime,@createTime,@createBy,@updateBy,@groupType,@adminType,@appId,'UPDATE');

              end if;

               END;          

 

 

-- 删除用户组触发器

--DROP TRIGGER TG_USERG_DELETE;

CREATE TRIGGER TG_USERG_DELETE AFTER DELETE ON TM_USERG

         REFERENCING OLD AS OROW

         FOR EACH ROW

         MODE DB2SQL

         BEGIN ATOMIC

        

         declare @groupId integer;

        declare @name varchar(30);

        declare @descn varchar(100);

        declare @syntype varchar(4);

        declare @ddlsql varchar(1024);

        declare @isprimary char(1);

        declare @updateTime timestamp;

        declare @createTime timestamp;

        declare @createBy integer;

        declare @updateBy integer;

        declare @groupType integer;

        declare @adminType integer;

        declare @appId integer;

               

         set @groupId=OROW.GROUP_ID;

        set @name=OROW.name;

        set @descn=OROW.descn;

        set @syntype=OROW.syn_type;

        set @ddlsql=OROW.ddlsql;

        set @isprimary=OROW.isprimary;

        set @updateTime=OROW.update_time;

        set @createTime=OROW.create_time;

        set @createBy=OROW.create_by;

        set @updateBy=OROW.update_by;

        set @groupType=OROW.group_type;

        set @adminType=OROW.admin_type;

        set @appId=OROW.app_id;

        

          -- 如果没有操作记录,则插入 delete 记录

          IF NOT EXISTS(SELECT GROUP_ID FROM TM_USERG_TEMP WHERE GROUP_ID=@groupId) THEN

              INSERT INTO TM_USERG_TEMP(GROUP_ID,NAME,DESCN,DDLSQL,ISPRIMARY,UPDATE_TIME,CREATE_TIME,

              CREATE_BY,UPDATE_BY,GROUP_TYPE,ADMIN_TYPE,APP_ID,ACTION) VALUES (@groupId,@name,@descn,

               @ddlsql,@isprimary,@updateTime,@createTime,@createBy,@updateBy,@groupType,@adminType,@appId,'DELETE');

               

               -- 如果有 insert 记录,则整体结果相当于没有进行任何操作

               ELSEIF EXISTS(SELECT GROUP_ID FROM TM_USERG_TEMP WHERE GROUP_ID=@groupId and ACTION='INSERT') THEN

                            DELETE FROM TM_USERG_TEMP WHERE GROUP_ID=@groupId and ACTION='INSERT';

               -- 如果没有 insert 记录,则只需记录最后的 delete 操作

               ELSE

                       UPDATE TM_USERG_TEMP set ACTION='DELETE' where GROUP_ID=@groupId;

          END IF;

         

          END;

posted @ 2006-10-30 17:06 pesome 阅读(1397) | 评论 (0)编辑 收藏

关于重用

在项目中碰到一些重用上的问题,有些想法,就先写一点。

重用应该是高层的复用,逻辑的复用,接口的复用,而不是具体实现的复用。 我们项目开始讲复用,就是大家把别人的代码拿过来,可用的地方就用,不同的地方改改,结果问题一堆。说到底就是接口没有定义清楚的,很多该复用的逻辑隐藏在了具体的实现中。这样导致无法进行接口的复用,转而使用具体的实现复用。从程序员的角度看,他们总会使用成本最小的方法完成任务。所以我们要时刻思考如何能让最正确的方法在他们看来同时也是成本最小。

这里有一个较为简单的办法,就是尽量使用方法封装实现,使接口的粒度最小。如果一个实现需要几百行,且其中包含多个逻辑,就最好抽取出多个方法,然后在主体接口内进行调用。这样的代码逻辑清晰易读,可重用性也高。看看大师们对代码的不断重构,很大程度上就是重构出粒度最细,复用性最高的接口。

如何达到最大程度的复用,其实是非常复杂的问题,还需要在今后的项目中不断体会。

posted @ 2006-10-15 23:13 pesome 阅读(461) | 评论 (0)编辑 收藏

最近做压力测试的总结

最近做 portal 的压力测试,一个字“累”。其中犯了不少错误,白白加了几天班,也有一些体会,就记录下来,希望对大家有所帮助。

首先讲压力测试环境。这个很是关键,我们就是在这个上面吃了苦头。我们用的 loadrunner ,原理也很简单,一台主控机,控制多台客户机,模拟并发用户访问应用。然后需要能实时监控各相关应用服务器, ldap 服务器等的性能。这里每台客户机最好能使用同样的配置,使用足够带宽的网络,给予同样的负载(模拟同样数量的用户)。同时要注意监控客户机的 cpu 和网络状况,时刻保证 cpu 和网络利用率低于 100% 。我们犯的很大错误就是使用各自的笔记本,而且都使用的是一个 10M hub 牵出的网线,这样导致实际的网络阻塞,既没有给予服务器足够的负载,又导致报告的响应时间比实际更长,从而带来了后续很多的无用测试。

然后讲测试方法。用的较多的还是持续压力测试,就是持续给予服务器一段时间的并发量(一般为 5 10 分钟),然后看平均响应时间是否在可以接受的范围内。这个“可接受”要视应用类型和实际的并发用户而定,如何估计并发就要靠经验了。对于 portal 而言,由于要与众多的应用接口,如进行 SSO ,获取数据等,有很大程度也依赖于其它应用的性能,其性能要求不会太高。我们测试首页的性能,在放上全部的 portlet 的情况下, 100 个并发的平均响应时间就在 17s 左右,这肯定是不能接受的( 10s 只能算勉强可以)。接下来就是发现性能瓶颈,并尝试进行优化了。

初步的发现瓶颈的方法也很简单,通过对 portlet 的增减,发现最影响性能的 portlet ,然后不断优化,直至达到可以接受范围。发现瓶颈所在了,就得进一步确定是什么原因:是我们本身程序的问题,还是其它应用接口性能不佳等等。这里光靠猜是不行的,要讲数据讲事实,记录时间日志就是简单有效的办法,我们对各个时间点打印了日志,比如 doview 方法的全部执行时间, jsp 的载入时间,具体接口的执行时间等。有些接口可能在压力较小的情况下性能不错,而在大压力情况下出现性能隐患,所以一定要在进行压力测试后查看日志。我们就是这样发现了性能隐患,同时更进一步对各方面进行优化,直至达到客户可以接受为止。

由于不是专门的测试人员,很多地方都是实际项目中的体会,也没什么理论基础。有什么不对的地方,大家多交流。

posted @ 2006-10-15 23:13 pesome 阅读(2472) | 评论 (1)编辑 收藏

项目应该提倡的一些做法

接着昨天的写。今天写我认为的一个 javaEE 项目中应该提倡的做法。

1.       开发流程尽量简化,采用迭代增量的模式,做适合项目需要的文档。很多时候千言不如一图,原型开发我认为也非常重要。

2.       采用成熟的框架, ssh 组合或更多 full-stack 的框架如 seam 等都是不错的选择。如果一定要用公司的框架,至少 SA 要非常熟悉这个框架,在出现问题时要能快速的解决。

3.       对业务的分析做到越细越好,如果有条件让更多的开发人员参与业务的分析,同时形成项目通用的业务语言(实在不行,精简的 user story 也可以)。对于每个达成共识的业务都要能记录下来,并能方便的进行查阅。业务模型和业务规则要始终与当前需求、代码和数据库保持一致。

4.       在团队的建设上,需要更多的投入。不要为了节约成本,让很多程序员老后面才加入团队。一个稳定、团结、有冲劲的团队能比松散而人数更多的团队,完成的更快更好。然后要加强沟通,比如每天开个小的茶话会,大家交流下各自的工作情况,有什么困惑和疑难,提出来大家一起解决,避免大家各自做相同的逻辑(很多东西经过抽象可能就是一个)。在工作之余大家一块吃吃饭,打打游戏等都是增进感情的好方法,大家彼此熟悉了,工作上也能更好的协作。

5.       对程序员要有更高的要求, SA 有责任让程序员了解更多的东西,如面向对象的 5 大原则、一些模式、 junit 、重构等,这些其实并不是什么高深的东西,仅仅是掌握一些方面也能对代码质量和开发中的愉悦度产生很大促进。要激发他们对技术的热爱和对代码质量的追求,因为最终受益的还是他们。 XP 所提倡的结对编程也是快速进行知识传递的好办法。

6.       采用 wiki 进行项目进度跟踪和一些文档的展示。这次用 excel+cvs 的方式感觉很是麻烦,在 spring 翻译中我们采用 wiki 的方式就感觉很好。

暂时先想到这么多,有更多体会,再来补充!

posted @ 2006-09-21 15:59 pesome 阅读(1008) | 评论 (2)编辑 收藏

用Maven做项目管理

     摘要: 在程序员发表的一篇maven文章,跟大家共享。用 Maven 做项目管理 在 Java世界中我们很多的开发人员选择用 Ant来构建项目,一个 build.xml能够完成编译、测试、打包、部署等很多任务,但我们...  阅读全文

posted @ 2006-09-21 11:37 pesome 阅读(18641) | 评论 (10)编辑 收藏

从错误中学到更多

开发进行到尾声了,但 bug 仍然层出不穷。总的来说,算是一个比较失败的项目,原因很多,有外在因素也有我作为一个 SA 不可推卸的责任。正好借加班的时间写点总结,也算是在失败总吸取教训,从错误中感受更多吧。

首先是开发流程。我是 xp 的坚定支持者,但在项目中由于外界原因还是采用了传统的开发流程,没有迭代,就是需求 -> 设计 -> 程序员进场开发 -> bug 。由于程序员进场时间较晚,一上来就开始开发,没有时间进行培训和团队的融合。然后开发中缺少沟通,就是一个人负责一块,开发完了再做其它。结果开发到现在,还有人不清楚我们项目的全貌,到底是为了解决什么业务。

然后是开发框架。使用了公司的框架,而我们作为 SA (我们是双 SA ),都是第一次接触,程序员也就一个人用过。我们最早是达成共识采用 SSH 的组合(我至少还算是了解吧,其它人也都用过),但由于上层因素没有实施(这也导致我好长一段时间进入不了状态)。开发前期大家都在探索这个框架(的确很难用,出错机制较差,配置文件很多,耦合较强 ... ),在一堆莫名奇妙的问题中摸索前行,花费大量的精力。而比较搞笑的是,在大家开始学习这个框架之时,我作为 SA ,因为要写一堆只为应付客户的设计文档(后面就没人看过),错过了和大家共同进步的机会,后面总是感觉“低人一等”。

在业务方面也存在很多问题。很多业务逻辑并没有以很好的载体保存下来,在需求文档中很多逻辑并没有体现。我维护了一套 pd 的业务模型,从概念模型 -> 物理模型 -> 数据库,这解决了后面的一些沟通问题,但由于更多体现的是静态的实体及关联,对于一些动态的业务流程没法体现。我们 SA 之间有时在一些问题上的理解还存在分歧(讨论过也达成过共识,但没有记录下来,后面可能就忘了),程序员就更是无所适从。谈到这,我更感受到 DDD 这本书的价值,他所提倡的开发人员参加模型的讨论,形成项目的模型语言,并不断随着业务进行演化。。。好多理念都是项目经验的结晶啊。

在开发管理上我也是无所作为。 Junit 都没有推广下去,更别说 TDD 了,这也与框架相关,它就没提供写 test case 的地方,等我搞明白一堆配置文件,做出脱离 web 容器的 test 框架,都开发一大半了,说起 test 的好处,大家也表示不理解(或者表示理解但没时间 = 没理解),就让他们慢慢 debug 吧!代码的质量也没有保证,程序员不明白代码的味道,更别说理解重构的意义以及进行恰当的重构了。一个函数写上 100 多行,什么逻辑都混在一块,但由于时间较紧,我也只好睁一只眼闭一只眼“功能完成就行吧!也不是我一个人在管”,到现在很多代码混成一团,展现层直接调用 dao (又是框架惹得祸),相同的逻辑 copy n 处,我也是后悔莫及。

今天先写失误,明天写从中学到的东西,从错误中学到的也许更多!

posted @ 2006-09-20 21:24 pesome 阅读(869) | 评论 (1)编辑 收藏

对领域模型的认识

最近看了看领域模型驱动这本书,只看了前面几章,但也深切的感受到了模型的重要性。通过与代码同步的模型,能够维护一个很好的知识共享的空间,包括设计者与程序员之间,客户与设计者之间 …… 而且模型应该尽可能简单,让不同背景的人都能够很快学会,并都能对模型有所增益。

那么这个模型应该是什么样的?书我没有细看,只说说自己的体会。关于设计,很早就有数据驱动和对象驱动的提法。在 Without EJB 里, Rod 也有讲:数据驱动或者说面向数据库设计更成熟,工具更多;而对象驱动更符合面向对象程序的特性,但由于掌握的人较少,风险较大。而通过模型驱动,我认为很大程度填补了 2 种方式的鸿沟,核心是模型,具体是对象模型还是数据模型并不重要,重要的是这个模型能够与需求、代码、数据库保持一致。

说到这里,顺便谈一谈我对文档的理解。我一直是 XP 的坚定支持者,甚至有点偏执。而由于文档不易阅读和沟通,且经常会出现与设计和代码的脱节,导致其可读性更差,所以我一向对文档不大感冒,更倾向于使用代码说话。但在目前的公司项目中,由于更多采用传统的软件过程,我也写了很多的文档,包括需求规格说明书、概要设计文档、详细设计文档等等。从对项目的帮助来看,文档作用并不太大,或者说是付出收益比太低,更多的是给客户写的,而不是给程序员写的。从程序员的需要来看,他关心的是每个实体的属性和关联,核心的接口、输入和输出,页面间的跳转和数据流,然后有一个统一的框架和编程模式。我的体会是:如果以文档为核心,很难描述清楚这些东西,且难以应对变化。

而通过以模型为核心(项目现在采用的 power designer 的概念模型为基础),辅以适当的描述,既能够加快大家对项目的认识(程序员是后面才加入),又能够节省一些写文档的时间,更早投入开发。

说到 power designer ,我也比较惭愧。用了好久,一直只是把它当成看数据库的工具。项目一开始就是从物理模型入手,结果举步维艰。后面从概念模型入手,就感受到了它的好处。使用概念模型,不用考虑太多关联表、外键什么的,而是从实体出发,然后确定相互间的关联,是一对一、一对多还是多对多。然后自动转成物理模型,并直接与相应的数据库挂钩。从这点上看与从对象设计出发真的非常相似。其实这也是合情合理的,正体现了这个世界的统一性吧(物理学界不也在搞什么统一场理论的证明吗)。 Power designer 也做了 conceptual model, physical model, object-oriented model xml model 的自动转换,我现在还没全部摸熟。

openfans 则是从对象入手,并通过 hibernate 建立与数据库的联系,也体现了一定的方便灵活性。但比较糟糕的是,只有代码和配置文件,没有清晰的便于交流的模型,谁要想参与只能先去慢慢看代码。所以我先通过 together reverse 出来一个类图,然后适当加以文字进行说明。类图已经做好,但比较乱,还需要更多的图例加以说明。文字说明就是下一篇 blog 的工作了。也算是预告吧!

posted @ 2006-08-13 17:41 pesome 阅读(3613) | 评论 (3)编辑 收藏

项目感悟

近来在一个项目做 SA ,也是第一次做比较大的项目的设计,感觉比较吃力。同时又要参与 spring 文档的翻译,一直没时间写 blog 。今天终于有点时间,就写一下最近的感悟。

首先是不适应。要参与需求阶段,因为需求初期并不确定,客户都不清楚他们需要什么东西,只是有一个很模糊的概念。我们得不断调研、讨论、出方案、出原型 …… 而这都是我比较不擅长的。还好有个职务较高的老大带着我们,才能逐渐把需求理顺。我也从他身上学到不少,准备写一篇“如何做需求”,但毕竟是第一次做较大的需求,理解还不很深刻,怕贻笑大方,所以只拿 MindManager 列了个提纲。

其次还是不适应。项目开始好几个月,没写过一行代码。项目没有采用 XP 的方式,而是普通的瀑布。需求就做了几个月,然后做概设、详设。我是 XP 的支持者,所以对这种方式持反对态度,但老大不同意,没办法!写文档,我也是很不情愿,但转念一想: Rod Without EJB ,但他 ejb 的理解比谁都深,什么方式都实践下可能更好。由于同时在看 Joel on software ,他对需求规格说明书却很是强调,我也就听听大师的话,好好写需求,顺便把他的一招用上了 ------- 写的有趣点,就当写故事吧。

最后还是不适应。以前做程序员,可以好好研究很多东西,现在不行了。有个 xml bean 转换的技术要解决,我能研究不?不行,我得写文档,这种比较 detail 的事情得给程序员做。看着程序员兴高采烈的比较各种开源工具,最后选定 JIBX openfans 发挥了一定的作用),然后跟我讲这个如何如何好,我只有附和的份。

讲到这里,让我想到一则小故事:有一个学钢琴的拜一个牛人为师。牛人交给他一个曲谱,说:“回去练好,一个月再过来。”他好歹把这个曲练熟了,还想展示一下,牛人又交给他一个更难的曲谱,又是同样的话。他只好回去继续苦练,每次都感觉不适应。这样往返多次,他忍不住了,问牛人:“你是不是故意整我,每次都给我更难的,还不给我表现的机会”。牛人让他把上次的曲弹弹,他感觉不错,让弹再上次的,更是轻松,最后弹第一次,他弹的是出神入化。他明白了!

大家都明白没:只有不断的感到不适应,才能进步。如果一切感觉良好,没什么挑战,就该考虑。。。。。。(此处省略 2 字)了。

posted @ 2006-08-11 18:34 pesome 阅读(1050) | 评论 (3)编辑 收藏

<2006年8月>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

导航

统计

公告

主要记录作者在学习java中的每一步足迹。除非特别说明,所有文章均为本blog作者原创,如需转载请注明出处和原作者,如用于商业目的,需跟作者本人联系。
欢迎大家访问:

常用链接

留言簿(16)

随笔分类

随笔档案

文章分类

文章档案

相册

收藏夹

java技术

人间百态

朋友们的blog

搜索

最新评论

阅读排行榜

评论排行榜