解决Mac环境下的Java Version问题

前一段时间遇到了一个奇怪的问题:我的Mac下面的Lotus Notes 8.5和FreeMind 0.9rc4都不能工作了,它们的共同特点就是都基于Java。我试验了在Terminal里面启动/Applications/Notes/Contents/MacOS/Notes的话是可以启动的,但是会启动以前的Basic界面(后来我才知道可能它是个Cabon应用,而不是Java版本)。所以就一直凑合着用。
可是昨天我尝试运行/Applications/Utilities/Java Preference的时候它也无法启动了,我这才觉得这不是个简单问题,必须要彻底解决。

我回忆了一下,我最开始的时候尝试用Java Preference的时候修改了JDK的顺序,将JDK6放在最上方,可是在Terminal里面(java -version)依然只可以访问1.5。所以当时就手动修改了系统的指向:


/System/Library/Frameworks/JavaVM.frameworks/Versions/Current -> /System/Library/Frameworks/JavaVM.frameworks/Versions/1.6
/System/Library/Frameworks/JavaVM.frameworks/Versions/CurrentJDK -> /System/Library/Frameworks/JavaVM.frameworks/Versions/1.6

这样修改以后就工作了,命令行里面执行java -version显示:

java version "1.6.0_13"
Java(TM) SE Runtime Environment (build 1.6.0_13-b03-211)
Java HotSpot(TM) 64-Bit Server VM (build 11.3-b02-83, mixed mode)

但是大家千万不要这样做,这个其实是后面很多问题的一个重要原因。

如果你修改了上面的指向,我推荐你unlink它们,然后下载Java for Mac OSX 10.5 Update 4 (http://www.apple.com/downloads/macosx/apple/application_updates/javaformacosx105update4.html)重新安装一下就可以重新生成这些link了。不过记得要unlink自己手工建立的符号链接(比如我的是Current, CurrentJDK, 1.6),否则升级包无法替换你建立的那些。

但是我这样做了以后还是没有解决所有问题,Java Preference可以正常启动了,不过它设置的顺序依然不管用,java -version总是返回1.5。我突然想到估计是运行我们的Ant脚本曾经提醒过JAVA_HOME的问题,我手动设置过一个,忘记去掉了。所以去查我的.profile,发现了罪魁祸首:

export JAVA_HOME='/Library/Java/Home'

这个设置是错误的,虽然它存在,可是它不会根据Java Preference修改的结果去寻找你的Java的bin和lib,也就造成了后面的一系列错误。

修改为(或者删掉JAVA_HOME声明也是安全的):
export JAVA_HOME='/System/Library/Frameworks/JavaVM.framework/Current/Home'

问题解决,教训是如果你手头有工具,应该学会使用它,如果没有耐心学习就错误的使用工具,一开始感觉能解决问题,可是后来它会造成越来越多的错误。那么决绝方案就是回到原点,恢复,学习,正确的使用工具。

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

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();
     }
    });
 }
}

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,这样不会这么丑陋。
抛砖引玉,谢谢。