访问数据库时遇到NullPointer Exception

06-05-02 kyle

大家好,我的webapp在运行时会不定期的遭遇"http 500"的错误信息,报告NullPointer异常,详细情况如下

0. 我采用jdk1.5.0, struts1.2.8开发,数据库为MySQL, 容器为Tomcat5.5.10。数据库连接采用tomcat的连接池,配置在web应用的/META-INF/context.xml里

1. 在开发期间,我碰到过NullPointer异常,它来自于访问数据库,试图从某个表中获得ResultSet的语句。经过检查,我发现是在访问后忘记调用conn.close(),经过改正后,经过测试,再也没有遇到NullPointer的问题。

2. 我将应用部署到客户的服务器上。这台服务器性能一般,512M的内存,上面还跑着oracle作服务。我将jdk, MySQL和tomcat安装,一开始一切运行良好。试运行开始。

3. 在试运行开始后的72个小时内,客户便遇到了两次的NullPointer异常报告。错误信息显示,均是来自访问数据库表,试图获取ResultSet时抛出的。当NullPointer抛出后,web应用无法使用,即任何试图连接数据库的操作都会抛出相同的NullPointer异常,无论访问哪个表。

4. 由于业务代码经过测试,我相信没有问题,因此,我判断问题在于当NullPointer异常抛出后,web应用无法从tomcat的连接池中获取连接。我又查阅了tomcat的文档,发现应该在/META-INF/context.xml中的<Resource>标记内添加属性,防止连接池泄漏。

====== /META-INF/context.xml ====
<Context reloadable="false">
 <Resource name="jdbc/ArmyDB"
     auth="Container"
     type="javax.sql.DataSource"
     username="reaver"
     password="123456"
     driverClassName="com.mysql.jdbc.Driver"
     url="jdbc:mysql://localhost/army?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=GBK" 
     maxActive="200"
     maxIdle="30"
     maxWait="10000"
     removeAbandoned="true"
     removeAbandonedTimeout="60"
     logAbandoned="true"/>
</Context>
========================================
<p class="indent">


即添加了

removeAbandoned="true"
removeAbandonedTimeout="60"
logAbandoned="true"
<p class="indent">


5. 为了验证我的判断,我编写了一个简单的测试。首先,我写了一个servlet叫做dbtest, 访问数据库中的某个表,获取全部的数据。其次,我用apache jemter作压力测试,设置100个线程并发访问,连续运行。经过6个小时的测试,tomcat和我的webapp运行良好,没有遇到NullPointer问题。但这不能肯定我之前的判断,因此,我对最初的配置进行同样的测试,即/META-INF/contex.xml中去掉[removeAbandoned="true"
removeAbandonedTimeout="60"
logAbandoned="true"]属性。

6. 问题来了:测试同样运行的良好,没有遇到NullPointer异常!这就是说,我的判断不是正确的,客户仍会遇到NullPointer的异常报告。

请板桥大哥,请各位达人指教,问题出在哪里呢?我该从什么地方入手解决这个问题呢?

另外,我怀疑是否是由于客户的服务器负载太大索引起,所以将/WEB-INF/context.xml中的最大活跃连接数改为50,即

maxActive="50"
<p class="indent">


目前客户仍在试运行,我相信他们不久之后还会遇到NullPointer...

7. 其他的信息
这个应用比较简单,业务流程不算复杂,系统同时在线大约在50-70人。我自己写了一个简单(丑陋:)的CommonDAO用以提供基本的db访问。然后每张表/视图的访问对应一个DAO,它继承自CommonDAO。CommonDAO代码如下:

import ...  // import stuff
 
public class CommonDAO {
    static Logger log = Logger.getLogger(CommonDAO.class.getName());
 
 // jndi name
 static final String jndi = "jdbc/ArmyDB";
 private DataSource ds = null;
 private Connection conn = null;
 
 protected void init() {
   // use container datasource
  try {
   Context initCtx = new InitialContext();
   Context envCtx = (Context) initCtx.lookup("java:comp/env");
   this.ds = (DataSource) envCtx.lookup(jndi);
   this.conn = ds.getConnection();   
  } catch (NamingException ne) {
   log.debug(ne);
  } catch (SQLException sqle) {
   log.debug(sqle);
  }
 }

 public CommonDAO() {
  init();
 }
 
 public Connection getConnection() {
  return this.conn;
 } 
 
 protected void checkConn() throws SQLException {
  if (this.conn == null || this.conn.isClosed()) {
      init();
  }
 }
 
 public PreparedStatement getPreparedStatement(String sql) {
     PreparedStatement pstmt = null;     
     if (this.conn != null) {
         try {
          checkConn();
                pstmt = conn.prepareStatement(sql);                
            } catch (SQLException e) {
                log.info(e);
            }         
     } else {
         // this seems will not happened...
         log.debug("null");        
     }
        return pstmt;
 }
 
 // for query, select 
 public ResultSet executeQueryPSTMT(PreparedStatement pstmt) {
     ResultSet rs = null;     
     try{
        checkConn();
        rs = pstmt.executeQuery(); 
     } catch(SQLException sqle) {
         log.info(sqle);
     }     
     return rs;
 }
 
 // for insert, update, delete, DDLs..
 public int executeUpdatePSTMT(PreparedStatement pstmt) {
     int rowsAffected = -1;
     try{
      checkConn();
      rowsAffected = pstmt.executeUpdate(); 
  } catch(SQLException sqle) {
          
   log.info(sqle);    
  }
     return rowsAffected;
 }
 
 // select [fields...] from [tablename] where [conditions...]
 public ResultSet executeQuery(String sql){
  ResultSet rs = null;
  try{
   checkConn();
   Statement stmt = this.conn.createStatement();
   rs = stmt.executeQuery(sql);
  } catch(SQLException sqle){   
   log.info(sqle);
  }
  return rs;
 }
 public int executeUpdate(String sql) {
  int rowsAffected = -1;
  try {
   checkConn();
   Statement stmt = this.conn.createStatement();
   rowsAffected = stmt.executeUpdate(sql);
  } catch (SQLException sqle) {
    log.info(sqle);
  }
  return rowsAffected;
 }
 // insert: insert into [tablename][(col1, col2, ...)] values(v1, v2, ...)
 public int executeInsert(String sql) {
  return this.executeUpdate(sql);
 }
 // delete: delete from [tablename] where[condition]
 public int executeDelete(String sql) {
  return this.executeUpdate(sql);
 }
 // create, drop, alter...
 public int executeDDL(String sql) {
  return this.executeUpdate(sql);
 }
 
 public void destory() {
  if (conn != null) {
   try {
    //conn.commit();
    conn.close();
    conn = null;
   } catch (SQLException sqle) {
    log.debug(sqle);
   }
  }
 }
  
 public List getAllTableNames() {
  List list = new ArrayList();
  
  try {
   checkConn();
   
   DatabaseMetaData dbmd = conn.getMetaData();
   String[] types = {"TABLE"};
   ResultSet rs = dbmd.getTables(null, null, "%", types);
   
   while (rs.next()) {
    String tableName = rs.getString(3);
    list.add(tableName);
   }
   
  } catch (SQLException e) {
   // TODO: handle exception
   log.info(e);
  }
  
  return list;
 }
  
 public boolean supportsBatchUpdates() {
  boolean bool = false;
  
  try {
   checkConn();
   
   DatabaseMetaData dmd = conn.getMetaData();
   if (dmd.supportsBatchUpdates()) {
    bool = true;
   }
  } catch (SQLException e) {
   // TODO: handle exception
  }
  
  return bool;
 }
 
 protected void close(ResultSet rs) {
  if (rs != null) {
   try {
    rs.close();
   } catch (SQLException e) {
   }
   rs = null;
  }
 }

 protected void close(PreparedStatement pstmt) {
  if (pstmt != null) {
   try {
    pstmt.close();
   } catch (SQLException e) {
   }
   pstmt = null;
  }
 }
 protected void close(Connection conn) {
  if (conn != null) {
   try {
    conn.close();
   } catch (SQLException e) {
    e.printStackTrace();
   }
   conn = null;
  }
 }
 protected void rollback(Connection conn) {
  if (conn != null) {
   try {
    conn.rollback();
   } catch (SQLException e) {
    e.printStackTrace();
   }
   conn = null;
  }
 }
}

<p class="indent">


如果访问user表,则编写UserDAO类,继承自CommonDAO,提供CRUD方法

请大家指点!谢谢了

kyle
2006-05-02 15:18

另外,板桥大哥,貌似没法修改自己发表的文章?我没找到“修改”的链接....