阳光VIP

少壮不努力,老大徒伤悲。平日弗用功,自到临期悔。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

发现Hibernate的bug与对Properties的深入认识

Posted on 2006-09-24 19:27  阳光VIP  阅读(131)  评论(0编辑  收藏  举报

下午接着检查王泽佑完成的作业情况,王泽佑的任务是使用Hibernate中的DriverManagerConnectionProvider来获得数据库连接,由于DriverManagerConnectionProvider没有提供构造方法或setter方法来接收配置信息,而是只能调用configure(Properties)方法来设置其配置信息,王泽佑为了能够利用起Spring,他将confiure方法接受的参数Properties对象作为Spring的一个bean对象(这有点过度使用Spring了,他们的理由是好玩和加深对spring的应用),没想到这一用却用出了一个很怪的问题:
下面是spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
 <bean id="providerProps" class="java.util.Properties">
  <constructor-arg>
   <props>
    <prop key="hibernate.connection.driver_class">com.mysql.jdbc.Driver</prop>
    <prop key="hibernate.connection.url">jdbc:mysql:///itcast</prop>
    <prop key="hibernate.connection.username">root</prop>
    <prop key="hibernate.connection.password">1234</prop>
   </props>
  </constructor-arg>
 </bean>
</beans>
上面的配置相当于如下的一段源程序代码:
  Properties providerProps = new Properties();
  providerProps.setProperty("hibernate.connection.driver_class","com.mysql.jdbc.Driver");
  providerProps.setProperty("hibernate.connection.url","jdbc:mysql:///itcast");
  providerProps.setProperty("hibernate.connection.username","root");
  providerProps.setProperty("hibernate.connection.password","1234");
  Properties props = new Properties(providerProps);
即先创建出一个Properties对象,然后将这个Properties对象作为另外一个Properties对象的构造方法的参数去初试化另外那个Properties对象。
源程序中的调用代码片段如下:
 DriverManagerConnectionProvider dmcp = new DriverManagerConnectionProvider(); 
 ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
 props = (Properties)context.getBean("providerProps");
 dmcp.configure(props); 
 Connection cn = dmcp.getConnection();
        ....
程序运行时,总是报告连接数据库失败,MySQl报告的失败原因是登陆帐户的密码为空。他们将上面的程序代码改成如下形式:
 DriverManagerConnectionProvider dmcp = new DriverManagerConnectionProvider(); 
 ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
 props = (Properties)context.getBean("providerProps");
 dmcp.configure(props);
 //增加了下面这条语句 
 props.setProperty("hibernate.connection.password",props.getProperty("hibernate.connection.password"));
 Connection cn = dmcp.getConnection();
        ....
再次运行,结果就正常了。修改后的代码仅仅是再次设置了password属性,并且设置值就是从原来的props对象中提取出来的,加不加这条语句,似乎没有什么不同啊?在新增的这条语句前后分别打印出props对象中的内容,看到的结果也完全一样。
 DriverManagerConnectionProvider dmcp = new DriverManagerConnectionProvider(); 
 ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
 props = (Properties)context.getBean("providerProps");
 dmcp.configure(props);
 props.list(System.out);  //与下面的list显示结果完全一样
 //增加了下面这条语句 
 props.setProperty("hibernate.connection.password",props.getProperty("hibernate.connection.password"));
 props.list(System.out);  //与上面的list显示结果完全一样
 Connection cn = dmcp.getConnection();
        ....
这个问题实在太怪异了!!!我怎么也想不明白!
于是,我打开JDK帮助文档,仔细阅读Properties类的帮助信息,注意到了如下一段描述:
A property list can contain another property list as its "defaults"; this second property list is searched if the property key is not found in the original property list.
其大概意思是说:一个Properties对象内部可以包含另外一个Properties对象作为其缺省Properties对象,当我们从Properties对象中检索一个属性时,先从这个Properties对象自身中查找,当找不着时,再从它内部包含的另外那个缺省Properties对象中查找。这是我以前不曾注意的一个细节,而我们上面的应用看起来正好与这个细节有些关系,我们也是先构造出一个Properties对象,然后将它设置成了另外一个Properties对象的内部缺省Properties对象,尽管内部的缺省Properties对象中包含了一个名为“hibernate.connection.password”的属性,但它毕竟不是外面的Properties对象自己的属性,我们对外面的Properties对象调用setProperty("hibernate.connection.password",...)方法,导致外面的Properties对象与它内部的缺省Properties对象中都有了一个名为“hibernate.connection.password”的属性,显然,我们调用了setProperty方法后确实改变了外面的Properties对象的内部数据,而不是我们以前认为的没有变化,看来问题可能就出现在这个细节上了。于是,我在eclipse下开始跟踪调试这个程序,跟踪到ConnectionProviderFacatory.getConnectionProperties方法时,看到该方法的代码如下:
 public static Properties getConnectionProperties(Properties properties) {

  Iterator iter = properties.keySet().iterator();
  Properties result = new Properties();
  while ( iter.hasNext() ) {
   String prop = (String) iter.next();
   if ( prop.indexOf(Environment.CONNECTION_PREFIX) > -1 && !SPECIAL_PROPERTIES.contains(prop) ) {
    result.setProperty(
     prop.substring( Environment.CONNECTION_PREFIX.length()+1 ),
     properties.getProperty(prop)
    );
   }
  }
  String userName = properties.getProperty(Environment.USER);
  if (userName!=null) result.setProperty( "user", userName );
  return result;
 }
该方法的作用是从一个Properties对象中提取与数据库连接信息相关的属性,并将这些连接属性生成一个新的Properties对象返回,这个新生成的properties对象中确实没有包含“password”属性,问题就出现在这个方法上。结合前面的Properties对象的重新认识,分析上面的代码,猜想是Properties.keySet()方法返回的关键字集合中只包含了它自己的关键字,而没有包含它内部的缺省Properties对象的关键字,为此,我写了如下一段代码来证实自己的猜想:
 Properties props1 = new Properties();
 props1.setProperty("hibernate.connection.username","root");
 props1.setProperty("hibernate.connection.password","1234");
 Properties props = new Properties(props1);
 props.setProperty("hibernate.connection.username","root");
 Enumeration e = props.keys();
 while(e.hasMoreElements())
 {
  System.out.println((String)e.nextElement());
 }
运行的结果,果然是只打印出了props.setProperty方法设置的关键字,而没有打印出props1中包含的关键字。至此,我完全确定了问题的原因,看来,Hibernate的开发者可能也没有注意Properties类的这个细节,因而给Hibernate留下了这样的一个Bug。