访问数据库时遇到NullPointer Exception
大家好,我的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&useUnicode=true&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方法
请大家指点!谢谢了
另外,板桥大哥,貌似没法修改自己发表的文章?我没找到“修改”的链接....