体验经济与牙膏

体验经济其实对于我们来说是个很好的东西,因为可以提高生活的质量,让我们不知不觉地小资一下。体验经济在我工作的IT界非常流行,Web2.0的很多观点都是体验经济的一种实践,这个就不扯那么远了。
说了个大帽子,我其实只是想说说牙膏而已……
因为牙膏是生活质量的一个晴雨表。
因为它太普通了,你不会去注意它,如果你注意自己要选择什么牙膏的时候,那么起码说明你开始注意你的生活细节了,哈哈,而我,惭愧的说,从来就非常关注牙膏和牙刷。
嘻嘻……
注重生活质量的人如果遇到了体验经济很容易陷入,因为多愁善感的人比较注重细节,而那些多出来的感情正是体验经济捕捉用户的标靶。
满电视的牙膏广告的确能够吸你么?不管它如何,我来说一下我的体验:
牙膏最好有透明和鲜艳的膏体,最好有清新的香气,口味我偏好水果味,磨粒最好用摩擦力好的材质但不能伤牙。而牙刷我喜欢橡胶刷把的,刷毛要有两种以上的颜色,握把要有弹性适合手型,刷毛要软硬适中,能够适度摩擦牙釉质去除牙垢……
说说最近用过的牙膏:
1、佳洁士浩爽白:蓝色透明膏体,颜色很诡异,口味清新。我很喜欢这个牙膏,已经消费了两大管。洁白的效果算是出众了,而且磨料感觉真是不错。价格算比较贵的,用起来很满足。但是,不得不说,宣传中的增白效果没有……
2、高露洁那个什么白:较早用的。蓝白膏体好像,口味一般。就是感觉没什么特殊之处,所以就用了一管。挺贵的还,洁白效果依然没啥……下次不想要了。
3、佳洁士双效洁白:密云这边居然没有浩爽白。而且上次去家乐福居然没有佳洁士牙膏。所以凑合买了个双效洁白,蓝白膏体让我失望,我喜欢透明的。口味没啥感觉。价格一般。没有吸引力,用了一半不用了。
4、佳洁士茶洁:透明绿色膏体,看起来还不错,价格还便宜。偶然买的。但是感觉着牙膏磨料一般呀,我发现佳洁士的高低端产品的磨料就是不一样。那种绿茶口味有点怪,多挤一些偶然会被我本能的当做口臭的味道,用久了就没啥好感了,所以在剩下20%的时候被我抛弃。
5、高露洁冰爽-柠檬口味:我最近的最爱!透明黄色中间有小方块,样子很可人。味道香甜,而且是我最喜欢的柠檬口味,用起来很爽。不过缺点是泡沫也会发黄……但是这个东西并不冰也不爽,赶决时外观冰霜,而口干式绵软香甜型的。这种口味在冰霜系列中我认为是最好的。第一支是我买高露洁什么洁白牙膏(后来送给turtle)送的,用了感觉特好就又买了三只装的冰霜套装,不过结论是柠檬口味很好。
6、高露洁冰爽-薄荷和绿茶口味:薄荷口味感觉非常清淡,一点没有薄荷刺激的感觉,而绿茶味道也很淡,可以说比佳洁士的茶洁口味要好一点,但是这两种牙膏还是偏清淡了。它们的膏体是透明蓝色和绿色,所以泡沫也会发蓝和绿(当然只是一点点,基本还是白的,别误会)。我觉得这两个产品没意思,别选择了。
7、高露洁蜂胶:我考!这东西的口感和我用的口腔贴膜一样,有点麻嗖嗖的。味道不爽,感觉就是花露水的味道。我不喜欢牙膏给我的口腔这样不爽的刺激。这个牙膏我只使用了一次,在我女朋友家,她也同样讨厌这个口味。不过我觉得如果口腔溃疡的时候用一点会不会有药效?:D
8、动感装黑人牙膏:我用过两管小的。薄荷味,刺激适中,特点是用完后嘴里面有点凉爽,口气清新。感觉比较适合旅游的时候携带。膏体透明蓝色,泡沫特别洁白和丰富。后来没有购买过,不过总体回味起来还不错,有机会旅游再买。
9、LG竹盐:女朋友家主要用这个,存货很多。说实话者牙膏还挺贵,不知好在哪里。我没正式用过,就是拿他洗刷过污垢,就又一次用在嘴里,也不算很咸,也没什么香味。不知好在哪里,品味不出来。倒是包装简朴,有加分么?奇怪了。
10、高露洁全效:我老妈的最爱。我记得就是白色膏体,没啥特殊。磨料感觉像石膏……哎,这牙膏我大学的时候一直在用,保护牙齿算不错了,可是它和体验经济好像不沾边。
11、中华牙膏:这个牙膏味道很特殊,是一种水果香。我没牙膏的时候会用,因为我老爸偶尔会用。小的时候就用过黄色的中华,不过后来知道实际不是中国的品牌,失望。不过总体感觉就是经典,对于我没啥吸引力。
12、两面针中药:绿色的恶心的膏体,味道令人发指。我小时一直以为那是毛毛虫身体里面的东西,格外恶心,泡沫都是绿色的……体验经济的反面教材。现在的两面针号线也变白色那种了,没啥好玩。
13、高露洁若干普通型:特点就是普通,所以没有什么记忆。体验经济反面教材。
14、最近的计划:高露洁儿童用甜橙口味、高露洁夜用、所有包装特别花里胡哨的产品、佳洁士的高端牙齿保护产品、黑人牙膏?……其它的等我下次到超市视察回来再说……
希望:超市能够让我们试用牙膏。
窍门:如果你不喜欢新买的牙膏可以送给爸爸妈妈或者亲朋好友,他们会非常高兴的!

Webwork 2.2的Action是否使用Spring的prototype­获取的性能对比

本文在060216进行了修改,因为发现了测试中的错误!注意5.5和7的内容。

1、引子:
其实是ajoo的这篇“Nuts和Spring 1.2.6 效率对比”和“IoC容器的prototype性能测试 ”,他们在Javaeye上详细讨论了Spring的prototype的缺陷。
Spring的prototype指的就是singleton="false"的bean,具体可以看Spring参考手册“3.2.5. To singleton or not to singleton”介绍。

2、Webwork 2.2的Spring结合问题:
Webwork 2.2已经抛弃自己的IoC,默认使用Spring的IoC。
上在OpenSymphony的官方Wiki,和jscud后来的几篇文章中没有特别提出prototype的问题。但是托他们的福,我们已经顺利的使Spring和Webwork良好的协同工作起来了。
可是而后的一些问题却把prototype的问题搞得神秘起来……
ajoo的测试中指出Spring的prototype性能很差,参见后面参考中的一篇文章和Javaeye的讨论。
而后又发现robbin在Javaeye的Wiki上面的“集成webwork和spring”中的最后注到:
“注意:目前并不推荐使用Spring来管理Webwork Action,因为对于prototype类型的bean来说,Spring创建bean和调用bean的效率是很低的!更进一步信息请看IoC容器的prototype性能测试”
这就使我们常用的Spring+Webwork2.2的连接中使用的prototype的问题被摆出来了。
我现在的项目中使用了prototype的方式将Webwork Action使用Spring进行显示的装配,我担心这个性能的问题会很严重,所以今天花了半天时间具体测试了一下。

3、Prototype VS autowire的解释:
我不知道怎么命名两种方式好,所以这里先做个解释:
spring的配置中Action会有个id,如:

<bean id="someAction" class="com.tin.action.SomeAction" parent="basicActionWithAuthtication" singleton="false">
<property name="someDAO">
<ref bean="someDAO" />
</property>
</bean>

我指的prototype方式就是在xwork中这样配置:

<action name="someAction" class="someAction">

而autowire方式就是指在xwork中这样配置:

<action name="someAction" class="com.tin.action.SomeAction">

看起来相同,但其实不同(我以前发过帖子,其中说这几种方法都可,但是其实它们的机制是不同的。

4、Portotye和autowire在XWork的SpringObjectFactory中是如何运作的:
我们先看一下代码,就能明白两者的区别了:

public Object buildBean(String beanName, Map extraContext) throws Exception {
try {
return appContext.getBean(beanName);
        }
catch (NoSuchBeanDefinitionException e) {
            Class beanClazz 
= getClassInstance(beanName);
return buildBean(beanClazz, extraContext);
        }

    }


public Object buildBean(Class clazz, Map extraContext) throws Exception {
        Object bean;

try {
            bean 
= autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
        }
catch (UnsatisfiedDependencyException e) {
// Fall back
            bean = super.buildBean(clazz, extraContext);
        }


        bean 
= autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
// We don’t need to call the init-method since one won’t be registered.
        bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());

return autoWireBean(bean, autoWiringFactory);
    }


public Object autoWireBean(Object bean) {
return autoWireBean(bean, autoWiringFactory);
    }

如果按照autowire配置会使用第二个buildBean方法,而prototype会使用第一个buildBean方法。

5、我的测试,首先测试SpringObjectFactory的理论效率:

public class testSpringObjectFactory extends TestCase {
protected FileSystemXmlApplicationContext appContext;
protected SpringObjectFactory sof = null;
protected Map map = null;
final String[] paths = {
"WebRoot/WEB-INF/applicationContext.xml",
"WebRoot/WEB-INF/spring-daos.xml",
"WebRoot/WEB-INF/spring-actions.xml"
        }
;

protected void setUp() throws Exception {
super.setUp();
        appContext 
= new FileSystemXmlApplicationContext(paths);

        sof 
= new SpringObjectFactory();
        sof.setApplicationContext(appContext);
        sof.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);

        map 
= new HashMap();
    }


public void testSpringObjectFacotyWithAutowire() {
long begin = System.currentTimeMillis();

try {
for (int i = 0; i < 100000
; i++{
                sof.buildBean(
"com.wqh.action.XinfangNewsAction", map);
            }

        }
catch (Exception e) {
            e.printStackTrace();
        }


long end = System.currentTimeMillis();
        System.out.println(
"**************************Used time:" +
            (begin 
 end));
    }


public void testSpringObjectFacotyWithPrototype() {
long begin = System.currentTimeMillis();

try {
for (int i = 0; i < 100000; i++{
                sof.buildBean(
"xinfangNewsAction", map);
            }

        }
catch (Exception e) {
            e.printStackTrace();
        }


long end = System.currentTimeMillis();
        System.out.println(
"**************************Used time:" +
            (begin 
 end));
    }


public void testSpringObjectFacotyWithSpringProxyableObjectFactory() {
        sof 
= new SpringProxyableObjectFactory();
        sof.setApplicationContext(appContext);
        sof.setAutowireStrategy(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);

long begin = System.currentTimeMillis();

try {
for (int i = 0; i < 100000; i++{
                sof.buildBean(
"com.wqh.action.XinfangNewsAction", map);
            }

        }
catch (Exception e) {
            e.printStackTrace();
        }


long end = System.currentTimeMillis();
        System.out.println(
"**************************Used time:" +
            (begin 
 end));
    }

}

重要的是测试结果:
**************************Used time:-16875
**************************Used time:-80500
**************************Used time:-12703(使用SpringProxyableObjectFactory()这个实现)

prototype是autowire运行时间的4.77X倍,十分可观。

5.5 巨大的反差,原来是我搞错了配置,发现了幕后黑手:
第二天,我又重新运行了5里面的测试。但是结果令人吃惊,运行了十多次,结果于昨天反差巨大,prototype方式获得的bean反而性能最快!
摘要两次测量结果
**************************Autowire Used time:-17578
**************************Prototype Used time:-7609
**************************Proxy Used time:-13063
———————————————–
**************************Autowire Used time:-17047
**************************Prototype Used time:-7609
**************************Proxy Used time:-12797

这是为什么呢?我百思不得其解,问题出在哪里呢?后来经过跟踪svn里面的提交纪录。我发现,我在昨天测试以后,把spring配置文件中的<beans default-autowire="autodetect">变成了<beans>。也就是没有打开自动检测的autowire!
而后就真相大白了。我有配置上default-autowire="autodetect"进行测试,结果:
**************************Autowire Used time:-16937
**************************Prototype Used time:-79750
**************************Proxy Used time:-12578

这和昨天的测试结果完全相同。也就是说我昨天写的4.77x的结果其实没有实际意义。倒是说明了Spring和Webwork集成的文章上面说的default-autowire="autodetect"是很坏的实践,即失去了name的灵活性也带来了巨大的性能损失。
而如果使用默认的Spring autowire配置下,prototype的性能已经很好了,实际上它工作起来应该是最快的。

6、在实际的Web项目中的性能对比:
我使用了我的一个小项目,就是反复调用一个action获取一个页面,其中有一个DAO注入。使用了JMeter进行了一个测试:2个线程,间隔0.5秒,循环50次,对比“据和报告中的”Throughput,单位/sec。
使用autowire方式:Avg. 148.34(吞吐量越高越好)
使用prototype方式:Avg. 138.5

也就是说在实际应用中两者也是有性能差距的,后者大约是前者性能的93%。
具体代码我不放出了,因为意义不大,大家也可以自己动手试验一下。
补充说明:
首先注意这个测试是在default-autowire="autodetect"下进行的。
测试的这个Action其实是一个空Action,它没有调用service和DAO,只是直接return SUCCESS,然后dispatcher到一个静态内容的jsp页面。我的本意是为了能够在获取Action占据的时间比例比较高的情况下分析性能区别。但是实际上却间接的夸大了在真正的实际应用中的性能差距。实际应用中如果加上service、DAO等逻辑的执行时间、模板View的渲染时间还有广域网上的网络传输时间,那么获取Action实例的时间差距可能就微乎其微了。

7、后续:
经过今天的思考,可以说完全改变了想法,重新汇总一下:
a、在不使用default-autowire="autodetect"时,Webwork 2.2的xwork中的action class使用spring的bean id配置的理论性能最好。而且,我认为如果不是为了追求配置上的简单,严重推荐关闭spring的default-autowire。
b、在使用default-autowire="autodetect、name、class"时,需要考虑你的需求。如果不使用Spring AOP提供的功能则在Webwork 2.2的xwork中的action class使用class全名比较好。如果使用Spring AOP的功能,则还是使用bean id。
c、在Spring中是否使用default-autowire是个需要慎重考虑的问题。autowire如果打开,命名会受到限制(class则更不推荐,受限更大,参考相关文档),它所带来的配置简化我认为只算是小小的语法糖果,背后却是吃掉它所埋下的隐患。
d、6中的测试还是有些说明意义的。7%的性能差距是在使用了default-autowire的方式下得出的,其中测试的那个action其实没有执行什么逻辑,而是一个直接dispatcher到success view的action,如果有商业逻辑包装,则性能差据估计会更小。因为实际上Action的执行过程、service、DAO等逻辑的执行过程和模板View的渲染过程(网络延迟)才是耗时大户。所以,关于性能应该下的结论是,prototype与否,在实际应用中性能差距是很小的,基本可以忽略不计。我们讨论的更多是编码的更好的实践。
e、autowire不使用Spring AOP相对还是trade off,因为虽然配置简单一点,但是对于使用Spring的声明性事务等内容会带来麻烦。虽然XML不那么好,但是显示配置带来的好处还是很多的。
f、谢谢robbin的提示。关于事务我也是无奈,放弃Action事务后难道给DAO多封装一层事务?如何没有事务依然使用HibernateDAOSurpport?Acegi的确不适合Web,使用WW的Inteceptor可以实现更舒适的解决方案。
g、SpringProxyableObjectFactory的问题……使用上难道只能改代码?找了半天没有这个东西的介绍。看来还是需要看看代码。不过发现现在Webwork和Xwork的代码又变动了很多……
h、我的测试是在Webwork2.2+Spring 1.2.6环境下测试的

8、参考资源:
Nuts和Spring 1.2.6 效率对比
http://www.javaeye.com/pages/viewpage.action?pageId=786
IoC容器的prototype性能测试
http://forum.javaeye.com/viewtopic.php?t=17622&postdays=0&postorder=asc&start=0
JavaEye的Wiki:集成webwork和spring
http://www.javaeye.com/pages/viewpage.action?pageId=860
WebWork – Spring官方Wiki
http://www.opensymphony.com/webwork/wikidocs/Spring.html
webwork2 + spring 结合的几种方法的小结
http://forum.javaeye.com/viewtopic.php?t=9990
WebWork2.2中结合Spring:"新的方式"
http://www.blogjava.net/scud/archive/2005/09/21/13667.html
我为什么不推荐对Action进行事务控制
http://www.javaeye.com/pages/viewpage.action?pageId=1205
我为什么不推荐使用Acegi
http://www.javaeye.com/pages/viewpage.action?pageId=1199

关于存储过程和直接执行SQL对比

关于存储过程和直接执行SQL:
需要注意以下几点:
1、从数据库角度,存储过程总是要比它所对应的纯SQL要慢。
2、存储过程目的在于简化特别复杂的SQL复合应用的场景。
3、但是对于一个拥有多条SQL的存储过程来说,它可以提升效率。因为减少了SQL传输的网络延迟。所以说SQL复杂时,存储过程可以增加实际的运行效率。注意对比第1条,第一条是对应服务器调用,这条对应实际的网络环境。
4、还有就是存储过程很容易减少多条SQL之间数据传递的麻烦(有可能带来没有实际意义的中间变量),可以在服务器端把它们隐藏。
所以我想应该考虑这几点来选择存储过程:
1、拥有复杂的数据操作,需要SQL复合。
2、中间传递的临时数据过多或者过大的时候。
使用存储过程的Java代码:JDBC call Stored Procedure
CallableStatement cstmt = conn.prepareCall("{ ? = call md5( ? ) }");
// CallableStatement cstmt = conn.prepareCall("begin ? := md5( ? ); end;"); // oracle syntax
cstmt.registerOutParameter(1, Types.VARCHAR); // set out parameters
cstmt.setString(2, "idea"); // set in parameters
cstmt.execute();
String md5Str = cstmt.getString(1); // Getting OUT Parameter Values
cstmt.close();

对不起,我的Angel

工作到这么晚,真是没办法。
春节为了长假陪着你,多休息了两天,结果周三回来开始就加油工作。
我给我们小组配置了WebCalendar,我认真计算了一下。这几3天的一周我实际工作了34.5小时,自己都觉得可怕……
可是,我们每晚的电话却明显的减少了时间,这三天中只有一天电话时间比较长,其它两天只能匆匆聊上两句…………
今天白天,你给我婚纱网站的链接,我却忙得没时间仔细看,宝贝我对不起你,我知道你想做我的新娘。而我,我也迫不及待把我的一生托付给你,陪你。但是工作是对你好的基础,我真的是认真的工作拼命的在学习,虽然我不一定能赶上最好的,但我努力去追赶了,我想在工作上我是对得起你和我的父母的。
我是爱你的,你的照片就在我电脑的前面,我不时的看看你,看你冲我噘嘴,我想亲亲你!
我是爱你的,你的声音的一切影像都在我心里,我无时不刻不在想你,我想抱着你!
比起被时空分隔的其它恋人我们是幸福的,因为我们相互信任。
最后,叮嘱几句:
1、多喝水,按时吃饭。
2、多做眼睛保健操,用你的眼药。
3、看Angel和Buffy别太上瘾,注意休息。
4、多关心你爸爸妈妈。
5、如果有时间去浩沙健身吧:D

Mysql5的存储过程的权限Bug

晚上搞了半天procedure的问题,提示:
java.sql.SQLException: Driver requires declaration of procedure to either contain a ‘\nbegin’ or ‘\n’ to follow argument declaration, or SELECT privilege on mysql.proc to parse column types.
 at com.mysql.jdbc.DatabaseMetaData.getCallStmtParameterTypes(DatabaseMetaData.java:6953)
 at com.mysql.jdbc.DatabaseMetaData.getProcedureColumns(DatabaseMetaData.java:2721)
 at com.mysql.jdbc.CallableStatement.determineParameterTypes(CallableStatement.java:1048)
 at com.mysql.jdbc.CallableStatement.<init>(CallableStatement.java:83)
 at com.mysql.jdbc.Connection.prepareCall(Connection.java:1255)
 at com.mysql.jdbc.Connection.prepareCall(Connection.java:1232)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:585)
 at com.mchange.v2.c3p0.stmt.GooGooStatementCache$2.run(GooGooStatementCache.java:333)
 at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:354)
找了半天存储过程的问题,因为都着眼于’\nbegin’ or ‘\n’这个地方了,重建了procedure也不起作用,找不到问题所在。
仔细思考,换用了root登陆就可以了。感觉明显是权限privileges的问题。
所以其实应该是这个提示or SELECT privilege on mysql.proc to parse column types。
可是google不到这个问题所在,因为没有mysql.proc这个权限,但是有人说这是一个bug
后来我发现,其实它是指mysql这个库的proc表的SELECT权限,添加上就工作正常了……
其实原因是这样的,原先测试时使用的xinfangweb的用户有两个,一个是localhost,另一个是%,前者的权限是全的,后者被限制了,哈哈。

HQL的中文问题小议

遇到了HQL的中文问题:
hql的String中包括了中文以后就会出错。
经过多次查询,转码(多字节、单字节)还是不能解决问题,说明某些环节压根就对多字节支持不正常。
后来经过多次试验,发现是hibernate3的hql语法解释器org.hibernate.hql.ast.ASTQueryTranslatorFactory出的问题,看到有人推荐用org.hibernate.hql.classic.ClassicQueryTranslatorFactory代替。
那当然是不可以的,否则相当于退回Hibernate2。
解决方法是不要在hql中写中文。
而是去使用?占位符和setParameters。
比如在Spring HibernateTemplate里面可以:
String hql = "select count(*) from Xinfangnews xfnews where xfnews.shifoufabu = 0 and (xfnews.title like ? or xfnews.content like ?)";
Object[] parameters = { "%" + keyWord + "%", "%" + keyWord + "%" };
getHibernateTemplate().find(hql, parameters);
如此可以运行了。
不过我走了点弯路:
本来写成"like ‘%?%’",然后往里面插keyWord,告诉我找不到?占位符。后来才想到”中的?已经不是占位符了。
然后就解决了:
所以就是说不能在HQL进行语法解析之前插入中文,而需要在解析之后插入。
还要注意,我试验了一下,在Mysql5中已经指定了使用UTF-8语言的时候,不必须在Hibernate连接的地方指明使用jdbc的encoding:
如很多人说需要这样配置:<prop key="hibernate.connection.url">jdbc:mysql://10.206.21.171:3306/xinfangweb?useUnicode=true&amp;characterEncoding=GBK&amp;autoReconnect=true</prop>
但实际上是用这样的配置:<prop key="hibernate.connection.url">jdbc:mysql://10.206.21.171:3306/xinfangweb</prop>
也是可以工作的,当然还是写上比较稳妥。

Generic Hibernate DAO试验

我们项目升级Weblogic 9.1,所以可以用JDK5的东西了。
先试验一下GenericHibernateDAO,作为一个DAO的基类,实现了一些简单的方法,方便子DAO封装。

使用方法类似:
 public class HibernateTownDAO extends GenericHibernateDAO<Town>
implements ITownDAO{
 {
        public HibernateTownDAO() { super(Town.class); } }
 }

这个东西基本上是从EclipseWork里面来的……

IGenericDAO大家可以用Eclipse的抽出Interface……
代码如下:
package com.goldnet.dao.hibernateDAO;
import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.goldnet.dao.IDAO.IGenericDAO;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
/**
 * @author TIN
 * @comment
 * @param <E>
 *
 * 范型的抽象HibernateDAO,继承于Spring提供的HibernateDaoSupport,该DAO封装了大部分的Domain对象访问方法。
 * 使用范型的目的在于可以显示的规定传入/返回的类型。直接的目的在于减少使用DAO时的强行类型转换。
 *
 * TODO:getTopsByProperty(int, String)方法
 * TODO:execute(HibernateCallback callback)方法?
 * TODO:execute(HQL hql)方法?
 * TODO:execute(SQL sql)方法?
 * TODO:findByCriteria(Criterion… criterion)方法?
 * TODO:getAOfflineListCopy(List list)方法?获取一个离线list,可以在部分地方获取session无关的List,避免使用OpenSessionInView
 * TODO:String getPersistentObjectName()方法?
 * TODO:使用变长参数修改一些传入Properties和Value的地方,扩展一些方法
 * TODO:研究一下DetachedCreteria
 * 
 */
public class GenericHibernateDAO<E> extends HibernateDaoSupport implements IGenericDAO<E> {
 private static final long serialVersionUID = 1312483055446936306L;
 private final Class clazz;
 public GenericHibernateDAO(final Class<E> clazz) {
  this.clazz = clazz;
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#saveOrUpdate(java.lang.Object,
  *      java.io.Serializable)
  * @deprecated
  *
  * @param newObject 持久化的类
  * @param id 持久化类的标识id
  *
  * 存储一个Entity,由于它的实现方式与HibernateDAO的saveOrUpdate雷同,都检测了id是否存在,但却多一个参数
  * 所以暂时部推荐,但是该实现对saveOrUpdate机理表现比较清晰,所以没有删除
  */
 public void saveOrUpdate(E newObject, Serializable id) {
  if ((id != null) && !getHibernateTemplate().contains(id)) {
   E dbObject = this.loadById(id);
   try {
    BeanUtils.copyProperties(dbObject, newObject);
   } catch (Exception e) {
    logger.error(e, e);
   }
   newObject = dbObject;
  }
  if (logger.isDebugEnabled()) {
   logger.debug("[saveOrUpdate with id Parameter] Entity<"
     + this.clazz.getName() + ">.");
   logger
     .debug("this method is deprecated! Please use saveOrUpdate(Object) instead.");
  }
  getHibernateTemplate().saveOrUpdate(newObject);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#saveOrUpdate(java.lang.Object)
  *
  * @param newObject 持久化的类
  *
  * 持久化一个Entity,一般用在不知持久化的是VO或PO时
  */
 public void saveOrUpdate(E newObject) {
  if (logger.isDebugEnabled()) {
   logger
     .debug("[saveOrUpdate] Entity<" + this.clazz.getName()
       + ">.");
  }
  getHibernateTemplate().saveOrUpdate(newObject);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#save(java.lang.Object)
  *
  * @param object 持久化的类
  *
  * 持久化一个Entity,用在持久化PO时
  */
 public void save(E object) {
  if (logger.isDebugEnabled()) {
   logger.debug("[save] Entity<" + this.clazz.getName() + ">.");
  }
  getHibernateTemplate().save(object);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#update(java.lang.Object)
  *
  * @param object 持久化的类
  *
  * 持久化一个Entity,符合jsr-220规范,用在持久化一个VO的Entity(但是它已经在库中存在)时,持久化它并返回它的PO
  */
 @SuppressWarnings("unchecked")
 public E merge(E object) {
  if (logger.isDebugEnabled()) {
   logger.debug("[merge] Entity<" + this.clazz.getName() + ">.");
  }
  return (E) getHibernateTemplate().merge(object);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#isEntityWithPropertyOfValueExist(java.lang.String,
  *      java.lang.Object)
  *
  * @param propertyName Entity属性的名称
  * @param value Entity属性的值
  * @return
  *
  * 具有propertyName的Entity是否存在?如果true则存在,如果false则不存在。
  */
 public boolean isEntityWithPropertyOfValueExist(String propertyName,
   Object value) {
  if (logger.isDebugEnabled()) {
   logger.debug("[isEntityWithPropertyOfValueExist] Entity<"
     + this.clazz.getName() + ">, propName=" + propertyName
     + ", value=" + value + ".");
  }
  if (getHibernateTemplate().find(
    "from " + this.clazz.getName() + " table where table."
      + propertyName + "=?", value).size() > 0) {
   return true;
  }
  return false;
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#isEntityIdExist(java.io.Serializable)
  *
  * @param id Entity的标识id
  * @return
  *
  * 具有该id的Entity是否存在?如果true则存在,如果false则不存在。用来检测一个VO是否已经有对应的PO存在
  */
 public boolean isEntityIdExist(Serializable id) {
  if (logger.isDebugEnabled()) {
   logger.debug("[isEntityIdExist] Entity<" + this.clazz.getName()
     + "> with id=" + id + ".");
  }
  return getHibernateTemplate().contains(id);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#delete(java.lang.Object)
  *
  * @param object Entity对象
  *
  * 删除一个Entity
  */
 public void delete(E object) {
  if (logger.isDebugEnabled()) {
   logger.debug("[delete] Entity<" + this.clazz.getName() + ">.");
  }
  getHibernateTemplate().delete(object);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#deleteById(java.io.Serializable)
  *
  * @param id Entity的标识id
  *
  * 根据Entity的标识id删除一个Entity
  */
 public void deleteById(Serializable id) {
  if (logger.isDebugEnabled()) {
   logger.debug("[deleteById] Entity<" + this.clazz.getName()
     + "> with id=" + id + ".");
  }
  getHibernateTemplate().delete(
    getHibernateTemplate().get(this.clazz, id));
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#deleteByProperty(java.lang.String,
  *      java.lang.Object)
  *
  * @param propertyName Entity属性的名称
  * @param value Entity属性的值
  *
  * 删除一组具有相同propertyName属性值(value)的Entity,用来批量删除
  */
 @SuppressWarnings("unchecked")
 public void deleteByProperty(String propertyName, Object value) {
  Iterator<E> result = listByProperty(propertyName, value).iterator();
  while (result.hasNext()) {
   delete(result.next());
  }
  if (logger.isDebugEnabled()) {
   logger.debug("[deleteByProperty] Entity<" + this.clazz.getName()
     + ">, propName=" + propertyName + ", value=" + value + ".");
  }
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#loadById(java.io.Serializable)
  *
  * @param id Entity的标识id
  * @return
  *
  * 根据Entity的标识id获取一个Entity的PO
  */
 @SuppressWarnings("unchecked")
 public E loadById(Serializable id) {
  if (logger.isDebugEnabled()) {
   logger.debug("[loadById] Entity<" + this.clazz.getName() + ">, id="
     + id + ".");
  }
  return (E) getHibernateTemplate().get(this.clazz, id);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#loadByProperty(java.lang.String,
  *      java.lang.Object)
  *
  * @param propertyName Entity属性的名称
  * @param value Entity属性的值
  * @return
  *
  * TODO:判断结果是否是unique?
  * 获取一个具有相同propertyName属性值(value)的Entity,注意改属性必须是unique的,否则改方法将没有意义
  */
 @SuppressWarnings("unchecked")
 public E loadByProperty(String propertyName, Object value) {
  Collection result = getHibernateTemplate().find(
    "from " + this.clazz.getName() + " table where table."
      + propertyName + "=?", value);
  Iterator<E> it = result.iterator();
  if (logger.isDebugEnabled()) {
   logger.debug("[loadByProperty] Entity<" + this.clazz.getName()
     + ">, propName=" + propertyName + ", value=" + value + ".");
  }
  if (it.hasNext()) {
   return it.next();
  } else {
   return null;
  }
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#loadEntitySum()
  *
  * @return
  *
  * 获取一种Entity的总数
  */
 public int loadEntitySum() {
  if (logger.isDebugEnabled()) {
   logger.debug("[loadEntitySum] Entity<" + this.clazz.getName()
     + ">.");
  }
  return ((Integer) getHibernateTemplate().find(
    "select count(*) from " + this.clazz.getName()).iterator()
    .next()).intValue();
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#loadEntitySumByProperty(java.lang.String,
  *      java.lang.Object)
  *
  * @param propertyName Entity属性的名称
  * @param value Entity属性的值
  * @return
  *
  * 获取一组具有相同propertyName属性值(value)的Entity的个数
  */
 public int loadEntitySumByProperty(String propertyName, Object value) {
  if (logger.isDebugEnabled()) {
   logger.debug("[loadEntitySum] Entity<" + this.clazz.getName()
     + ">, propName=" + propertyName + ", value=" + value + ".");
  }
  return ((Integer) getHibernateTemplate().find(
    "select count(*) from " + this.clazz.getName()
      + " table where table." + propertyName + "=?", value)
    .iterator().next()).intValue();
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#loadLastEntity()
  *
  * @param orderByPropertyName 用来排序的属性的名称
  * @return
  *
  * TODO:和获取Tops是否重复?
  * 返回最后一个Entity,用来获取如最新加入的Entity这样的方法
  * 注意改方法实际上返回的是降序排列的list中的第一个!
  */
 @SuppressWarnings("unchecked")
 public E loadLastEntity(final String orderByPropertyName) {
  if (logger.isDebugEnabled()) {
   logger.debug("[listLastEntity] Entity<" + this.clazz.getName()
     + "> and orderByPropertyName=" + orderByPropertyName
     + ", Note:it’s DESC.");
  }
  return (E) getHibernateTemplate().execute(new HibernateCallback() {
   public Object doInHibernate(Session session) {
    Criteria criteria = session.createCriteria(clazz);
    criteria.addOrder(Order.desc(orderByPropertyName));
    releaseSession(session);
    try {
     return criteria.list().iterator().next();
    } catch (Exception e) {
     logger
       .warn("[listLastEntity] faild! the entity list is empty!");
     return null;
    }
   }
  });
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listAll()
  *
  * @return
  *
  * 获取所有的Entity的list
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listAll() {
  if (logger.isDebugEnabled()) {
   logger.debug("[listAll] Entity<" + this.clazz.getName() + ">.");
  }
  return getHibernateTemplate().find("from " + this.clazz.getName());
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listAllOrderBy(boolean, java.lang.String)
  *
  * @param isAsc 是否使用升序
  * @param orderByPropertyName 排序的属性
  * @return
  *
  * 获取所有的Entity的list,结果按照orderByPropertyName属性升序/降序(isAsc true则升序,否则降序)
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listAllOrderBy(final boolean isAsc,
   final String orderByPropertyName) {
  final Class clazztemp = this.clazz;
  if (logger.isDebugEnabled()) {
   logger.debug("[listAllOrderBy] Entity<" + this.clazz.getName()
     + ">, isAsc?=" + isAsc + ", orderByPropertyName="
     + orderByPropertyName + ".");
  }
  return (Collection<E>) getHibernateTemplate().execute(
    new HibernateCallback() {
     public Object doInHibernate(Session session) {
      Criteria criteria = session.createCriteria(clazztemp);
      criteria.addOrder(isAsc ? Order
        .asc(orderByPropertyName) : Order
        .desc(orderByPropertyName));
      releaseSession(session);
      return criteria.list();
     }
    });
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listByPage(int, int)
  *
  * @param pagesize 页的大小,每页最多Entity个数
  * @param pageno 页码
  * @return
  *
  * 获取一页的Entity的list,该页是所有Entity中的第pageno页,每页最多有pagesize个Entity,分页显示Entity时使用
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listByPage(final int pagesize, final int pageno) {
  final Class clazztemp = this.clazz;
  if (logger.isDebugEnabled()) {
   logger.debug("[listByPage] Entity<" + this.clazz.getName()
     + ">, pagesize=" + pagesize + ", pageno=" + pageno + ".");
  }
  return (Collection<E>) getHibernateTemplate().execute(
    new HibernateCallback() {
     public Object doInHibernate(Session session) {
      Criteria criteria = session.createCriteria(clazztemp);
      criteria.setFirstResult(pagesize * pageno);
      criteria.setMaxResults(pagesize);
      releaseSession(session);
      return criteria.list();
     }
    });
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listByPageOrderBy(int, int, boolean,
  *      java.lang.String)
  *     
  * @param pagesize 页的大小,每页最多Entity个数
  * @param pageno 页码
  * @param isAsc 是否使用升序
  * @param orderByPropertyName 排序的属性
  * @return
  *
  * 获取一页的Entity的list,该页内容按照orderByPropertyName属性升序/降序(isAsc true则升序,否则降序),该页是所有Entity中的第pageno页,每页最多有pagesize个Entity
  * 分页显示排序过的Entity时使用
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listByPageOrderBy(final int pagesize,
   final int pageno, final boolean isAsc,
   final String orderByPropertyName) {
  final Class clazztemp = this.clazz;
  if (logger.isDebugEnabled()) {
   logger.debug("[listByPageOrderBy] Entity<" + this.clazz.getName()
     + ">, pagesize=" + pagesize + ", isAsc?=" + isAsc
     + ", orderByPropertyName" + orderByPropertyName
     + ", pageno=" + pageno + ".");
  }
  return (Collection<E>) getHibernateTemplate().execute(
    new HibernateCallback() {
     public Object doInHibernate(Session session) {
      Criteria criteria = session.createCriteria(clazztemp);
      criteria
        .setFirstResult(pagesize * pageno)
        .setMaxResults(pagesize)
        .addOrder(
          isAsc ? Order.asc(orderByPropertyName)
            : Order
              .desc(orderByPropertyName));
      releaseSession(session);
      return criteria.list();
     }
    });
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listByProperty(java.lang.String,
  *      java.lang.Object)
  *     
  * @param propertyName Entity属性的名称
  * @param value Entity属性的值
  * @return
  *
  * 获取一组具有相同propertyName属性值(value)的Entity的列表list,返回一组符合条件的Entity
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listByProperty(String propertyName, Object value) {
  if (logger.isDebugEnabled()) {
   logger.debug("[listByProperty] Entity<" + this.clazz.getName()
     + ">, propertyName" + propertyName + ", value=" + value
     + ".");
  }
  return getHibernateTemplate().find(
    "from " + this.clazz.getName() + " table where table."
      + propertyName + "=?", value);
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listByPageByProperty(java.lang.String,
  *      java.lang.Object, boolean, java.lang.String)
  *     
  * @param pagesize 页的大小,每页最多Entity个数
  * @param pageno 页码
  * @param listByPropertyName Entity属性的名称
  * @param value Entity属性的值
  * @return
  *
  * 获取一页的Entity的list,该页内容为具有相同propertyName属性值(value)的Entity,该页是所有符合条件Entity中的第pageno页,每页最多有pagesize个Entity
  * 分页显示匹配某一属性值的Entity时使用
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listByPageByProperty(final int pageno,
   final int pagesize, final String listByPropertyName,
   final Object value) {
  final Class clazztemp = this.clazz;
  if (logger.isDebugEnabled()) {
   logger.debug("[listByPageByProperty] Entity<"
     + this.clazz.getName() + ">, pageno=" + pageno
     + ", pagesize=" + pagesize + ", listByPropertyName="
     + listByPropertyName + ", value" + value + ".");
  }
  return (Collection<E>) getHibernateTemplate().execute(
    new HibernateCallback() {
     public Object doInHibernate(Session session) {
      Criteria criteria = session.createCriteria(clazztemp);
      criteria.setFirstResult(pagesize * pageno)
        .setMaxResults(pagesize).add(
          Restrictions.eq(listByPropertyName,
            value));
      releaseSession(session);
      return criteria.list();
     }
    });
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listByPropertyOrderBy(java.lang.String,
  *      java.lang.Object, boolean, java.lang.String)
  *     
  * @param listByPropertyName Entity属性的名称
  * @param value Entity属性的值
  * @param isAsc 是否使用升序
  * @param orderByPropertyName 排序的属性
  * @return
  *
  * 获取所有的具有匹配propertyName属性值(value)的Entity的list,按照orderByPropertyName属性升序/降序(isAsc true则升序,否则降序)
  * 显示匹配属性值的列表且要求排序时使用
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listByPropertyOrderBy(final String listByPropertyName,
   final Object value, final boolean isAsc,
   final String orderByPropertyName) {
  final Class clazztemp = this.clazz;
  if (logger.isDebugEnabled()) {
   logger.debug("[listByPropertyOrderBy] Entity<"
     + this.clazz.getName() + ">, listByPropertyName="
     + listByPropertyName + ", value" + value + ", isAsc?="
     + isAsc + ", orderByPropertyName=" + orderByPropertyName
     + ".");
  }
  return (Collection<E>) getHibernateTemplate().execute(
    new HibernateCallback() {
     public Object doInHibernate(Session session) {
      Criteria criteria = session.createCriteria(clazztemp);
      criteria
        .add(Restrictions.eq(listByPropertyName, value))
        .addOrder(
          isAsc ? Order.asc(orderByPropertyName)
            : Order
              .desc(orderByPropertyName));
      releaseSession(session);
      return criteria.list();
     }
    });
 }
 /**
  * @see com.goldnet.dao.IDAO.IGenericDAO#listByPageByPropertyOrderBy(int, int,
  *      java.lang.String, java.lang.Object, boolean, java.lang.String)
  *     
  * @param pagesize 页的大小,每页最多Entity个数
  * @param pageno 页码
  * @param listByPropertyName Entity属性的名称
  * @param value Entity属性的值
  * @param isAsc 是否使用升序
  * @param orderByPropertyName 排序的属性
  * @return
  *
  * 获取一页的Entity的list,该页内容为具有相同propertyName属性值(value)的Entity,该页内容按照orderByPropertyName属性升序/降序(isAsc true则升序,否则降序),
  * 该页是所有符合条件Entity中的第pageno页,每页最多有pagesize个Entity
  * 分页显示匹配某一属性值的Entity时使用
  */
 @SuppressWarnings("unchecked")
 public Collection<E> listByPageByPropertyOrderBy(final int pagesize,
   final int pageno, final String listByPropertyName,
   final Object value, final boolean isAsc,
   final String orderByPropertyName) {
  final Class clazztemp = this.clazz;
  if (logger.isDebugEnabled()) {
   logger.debug("[listByPageByPropertyOrderBy] Entity<"
     + this.clazz.getName() + ">, pageno=" + pageno
     + ", pagesize=" + pagesize + ", listByPropertyName="
     + listByPropertyName + ", value" + value + ", isAsc?="
     + isAsc + ", orderByPropertyName=" + orderByPropertyName
     + ".");
  }
  return (Collection<E>) getHibernateTemplate().execute(
    new HibernateCallback() {
     public Object doInHibernate(Session session) {
      Criteria criteria = session.createCriteria(clazztemp);
      criteria
        .setFirstResult(pagesize * pageno)
        .setMaxResults(pagesize)
        .add(Restrictions.eq(listByPropertyName, value))
        .addOrder(
          isAsc ? Order.asc(orderByPropertyName)
            : Order
              .desc(orderByPropertyName));
      releaseSession(session);
      return criteria.list();
     }
    });
 }
}

生活的效率,累的是谁?

发现MSN Spaces升级了,满好玩的,不过好像是有Bug的,目前带着自己的信息给别人留言还有问题。
如何提高效率呢?
1、最好要有一个可以移动的工作平台。或者自己总结一个回复工作平台的步骤。自己经常对自己所做的事情做记录,并分享。
2、关于信息的获取,除了使用搜索引擎,还可以考虑订阅RSS。通过FeedDeamon这样的Rss阅读器,可以定义自己的频道,在里面收藏关注的站点(尤其是技术Blog),如此通过适时的跟踪,可以保证信息获取的及时。
3、使用一个文章收藏工具,如CyberArticle(网文快捕)。通过它的分类收藏功能,可以将自己关注或者有用的技术文章保存下来,并且可以方便的制作电子书。它的最大优点是可以将你所有的技术文章(网页)保存为一个统一的book文件,方便管理和备份。
4、掌握一个拿手的编辑器。用Windows的朋友可能习惯使用NotePad(记事本),但是它并不怎么好用。推荐习惯UltraEdit或者EditPlus这样的强大的编辑器的功能,使用批量替换,字符显示,文件编码等问题的解决(如果感兴趣,用UltraEdit的16进制编辑功能可以帮你发现更多的隐藏的编码问题)。我偏好EditPlus……
5、这个年代要掌握eMule和BT的基本使用方法。BT对于获得流行的资源非常有帮助,而eMule则对获得非流行的资源(如eBook、冷门mp3等)非常重要。注意不少eMule Client有积分,注意积攒,对排队有效。
6、如果你还没有Blog,最好有一个,可以催促你积累和共享你的收获。
祝大家新春快乐,狗年吉祥!

Webwork 2.2的form tag在使用jsp view时onsubmit不工作

起因是使用Michael Chen的JSValidation,需要手动写onsubmit,结果居然发现webwork 2.2的正式版本里面的form tag里面的onsubmit在使用jsp view的时候没法输出,苦恼于此。暂时通过修改模板修改了一下,但是如此很不爽。所以就想测试一下到底是不是我们项目的问题还是ww 2.2的问题。但一直太忙,今天才抽出功夫测。以下是测试纪录,希望知道缘由的朋友帮我指点一下。

测试使用Webwork发行包中演示的starter项目,其中使用了ftl模板作为表现层,我按它的方式制造了newPerson.jsp。
下面是两个页面的源文件:
newPerson.jsp

java代码: 
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ taglib uri="webwork" prefix="ww"%>
<html>
<head>
    <title>New Person</title>
    <link href="<ww:url value="/webwork/jscalendar/calendar-blue.css" />" rel="stylesheet" type="text/css" media="all"/>
</head>

<body>
<ww:form action="newPerson" onsubmit="dosomething" validate="false">
    <ww:textfield label="Name" name="person.name"/>
    <ww:datepicker  name="date" id="ecoInf.turninDate" template="datepicker.ftl" language="en" format="%Y-%m-%d" label="Time
"/>
    <ww:submit value="Create person"/>
</ww:form>
</body>
</html>

newPerson.ftl

java代码: 
<html>
<head>
    <title>New Person</title>
    <link href="<@ww.url value="/webwork/jscalendar/calendar-blue.css" />" rel="stylesheet" type="text/css" media="all"/>
</head>

<body>
<@ww.form action="newPerson" onsubmit="dosomething">
    <@ww.textfield label="Name" name="person.name"/>
    <@ww.datepicker  name="date" id="ecoInf.turninDate" template="datepicker.ftl" language="en" format="%Y-%m-%d" label="Time"/>
    <@ww.submit value="Create person"/>
</@ww.form
>
</body>
</html>


程序逻辑继续使用starter项目里面的内容。
xwork中配置如下:

java代码: 
<action name="newPerson" class="com.acme.CreatePerson">
            <result name="success" type="redirect">listPeople.action</result>
            <!–
            <result name="input" type="dispatcher">newPerson.jsp</result>
            –>
            <result name="input" type="freemarker">newPerson.ftl
</result>
        </action>

两个"success"的result轮流切换。

试验结果如下:
1、webwork.properties配置如下,使用vm的template,此时使用两种result的结果是相同的:

java代码: 
webwork.ui.theme=simple
webwork.ui.templateDir=template/archive
webwork.ui.templateSuffix
=vm

显示结果,显然onsubmit没有输出,此时datapicker标签没有输出(因为老的vm模板没有提供datapicker标签):

java代码: 
<form namespace="/" id="newPerson" name="newPerson" action="/starter/newPerson.action">

2、webwork.properties配置如下,此时使用ftl的result和ftl的template:

java代码: 
webwork.ui.theme=simple
webwork.ui.templateDir=template
webwork.ui.templateSuffix
=ftl

显示结果,onsubmit输出正常:

java代码: 
<form id="newPerson" name="newPerson" onsubmit="dosomething" action="/starter/newPerson.action">


3、webwork.properties配置如下,此时使用jsp的result和ftl的template:

java代码: 
webwork.ui.theme=simple
webwork.ui.templateDir=template
webwork.ui.templateSuffix
=ftl


显示结果,onsubmit没有输出:

java代码: 
<form namespace="/" id="newPerson" name="newPerson" action="/starter/newPerson.action">


此时尝试使用theme="xhtml"还是没有输出。

从上面的测试来看,似乎是个bug,可是在webwork网站却没有查到相关issue,怀疑是我这里的环境问题。以上测试在tomcat 5.5.12 or Weblogic 9.1 @ SUN JDK 1.5b6上面进行。希望哪位解决了上面问题指导一下我,谢谢。

Spring+Hibernate中使用Mysql存储过程初步

Hibernate中使用Mysql存储过程
1、我使用了mysql-connector-java-5.0.0-beta-bin.jar(其实用老的mysql-connector-java-3.1.8-bin.jar也可以调用存储过程的)这个最新的mysql驱动。
2、数据库我使用了mysql-5.0.18-win32,安装后建立了一个简单的数据表。
sql如下:

java代码: 
create database testprocedure;
use testprocedure;
create table testtable (id int(11) AUTO_INCREMENT, content varchar(255), readcount int(11) DEFAULT 0,primary key (id));
desc testtable;(查看是否建立正确)


3、建立一个专用的用户(可选,建立时请使用具有grant权限的用户如root):

java代码: 
grant select,delete,update,create,alter,execute on testtable.* to testprocedure@"localhost" identified by "test";

用户名为testprocedure,密码test。注意权限中的execute,它就是执行call procedure的权限。在你的Hibernate配置中使用该帐户。
4、建立一个存储过程:
sql如下:

java代码: 
delimiter //
(注意//是新的命令结束符,方便我们建立procedure)
create procedure readcountplusone (inputid int)
begin
update testtable set readcount = readcount + 1 where id = inputid;
end//

(建立存储过程完毕)
delimiter ;
(恢复命令结束符为;)

5、测试一下存储过程:

java代码: 
insert into testtable values (null,‘test’,0);
select * from testtable;
call readcountplusone(1)
;
select * from testtable;

应该看到原先readcount为0,call以后变成1,而且每次call都加1。
如果执行有错,可以删除procedure重新建立。
删除的命令为drop procedure readcountplusone;
6、开始在我们的Hibernate+Spring support项目中使用procedure:
HBM映射我们不说了,这里没有使用named query。Hibernate+Spring的配置这里也不多说了,应该可以搜寻到很多文章。
我的DAO是extends HibernateDAO,具体的使用方法可以参照其他很多讲Spring hibernate support的文章。
我们建立一个方法,比较丑陋(只是测试,大家有好方法可以提),假设对应testtable的pojo为TestPojo,它的getId()返回id对应的值:

java代码: 
public void readCountPlusOne(final TestPojo pojo) {
        getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) {
try {
Connection conn = session.connection();

String sql = "{call readcountplusone(?)}";
CallableStatement stmt = conn.prepareCall(sql);
                        stmt.setLong(1, pojo.getId().longValue());
                        stmt.execute();
} catch (Exception e) {
if(log.isDebugEnable){
                                log.debug("call DAO
‘s readCountPlusOne() faild, with Exception:");
                                e.printStackTrace();
                        }
                    }

                    return null;
                }
            });
    }

7、然后我们在我们的bussiness中调用readCountPlusOne方法既可实现通过Hibernate调用一个简单的Mysql存储过程。

有点走马观花,主要是把口舌都放在mysql部分,hibernate部分则用的比较简单,我想把调用方法改为Named Query,这样不会这么丑陋。
抛砖引玉,谢谢。