您的位置:首页 > 数据库

网店版重生系列:多数据源单sqlMapClient导致NullPointerException问题跟踪

2009-06-20 17:00 453 查看
从前面的《网店版重生系列:都是Spring配置中自动注入惹的祸》中我们可以看出一些有关datasource、sqlMapClientTemplate、sqlMapClient的相关配置信息;整体而言,采取的方式为:
其一,单实例sqlMapClient,只配置configLocation属性,不配置datasource;
其二,sqlMapClientTemplate中配置动态datasource、sqlMapClient,然后在Dao中注入sqlMapClientTemplate;
其三,由于获取用户与Mysql数据库之间的对应关系是保存在Oracle主库中,所以不适用动态数据源,而是直接通过配置sqlMapClient、JNDI datasource来完成注入;

在这种配置方式下,应用初期偶尔会出现报NullPointerException异常信息,堆栈信息如下:
java.lang.NullPointerException
at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.setUserConnection(SqlMapSessionImpl.java:149)
at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:179)
at com.alisoft.eshop.ddb.dao.support.DdbSqlMapClientTemplate.execute(DdbSqlMapClientTemplate.java:86)
at org.springframework.orm.ibatis.SqlMapClientTemplate.delete(SqlMapClientTemplate.java:396)
at com.alisoft.eshop.ddb.dao.support.DdbSqlMapClientTemplate.delete(DdbSqlMapClientTemplate.java:53)
at com.alisoft.c2c.biz.dal.dao.ibatis.IbatisMigrateDataDao.deleteDBFromMysql(IbatisMigrateDataDao.java:21)
at com.alisoft.c2c.biz.manager.impl.MigrationDataImpl.doDeleteFromMysql(MigrationDataImpl.java:170)
at com.alisoft.c2c.biz.manager.impl.MigrationDataImpl.migrationDate(MigrationDataImpl.java:69)


开始的时候,我们还以为是Mysql数据库首次连接出现Time Out所致,但细想下不对啊!如果是这样堆栈信息中应该会包括TimeOut相关的信息才对啊,但是没有这个信息,再看看堆栈信息,是从SqlMapSessionImpl.setUserConnection中抛出来的,跟踪下源码,问题很快就被定位了,原因是SqlMapSession被关闭所致;来看看最为关键的这段代码先:
SqlMapSession session = this.sqlMapClient.openSession();
Connection ibatisCon = null;
try {
if (logger.isDebugEnabled()) {
logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
}
Connection springCon = null;
try {
ibatisCon = session.getCurrentConnection();
if (ibatisCon == null) {
springCon = DataSourceUtils.getConnection(getDataSource());
session.setUserConnection(springCon);
if (logger.isDebugEnabled()) {
logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation");
}
}
return action.doInSqlMapClient(session);
}
catch (SQLException ex) {
throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
}
finally {
DataSourceUtils.releaseConnection(springCon, getDataSource());
}
}
finally {
// Only close SqlMapSession if we know we've actually opened it
// at the present level.
if (ibatisCon == null) {
session.close();
}
}

跟踪代码时发现,其实这段代码被调用了多次,第一次执行时没有任何问题,第二次执行时也没问题,异常信息其实是第三次执行时抛出,直接原因是此时SqlMapSession的实例已被close;根本原因呢还是因为单sqlMapClient实例所致!

在分布式改造过程中,为了避免对用户、Mysql分库信息对应关系的强耦合,没有采取固定的hash取模或者是一致hash算法,而是将用户、Mysql分库信息进行了持久化存储,保存于主库Oracle中,然后再配合Cache优化性能;这样就出现了一种应用场景:
当我们在通过Ibatis在Mysql库中执行SQL语句时,因为要通过datasource获取connection,所以可能需要先访问Oracle主库以便获取当前用户所对应的Mysql数据库信息,而这两者的SQL操作都是通过Ibatis进行的,所以我们上面提到的那段代码就会被多次执行了,而每次SQL执行完毕之后都会到finally部分调用session.close();session的实例是和当前线程绑定的,所以其实那几次代码段的执行都是使用的同一个session;

问题根源都找到了,解决也很简单:针对JNDI datasource、动态datasource区分不同的sqlMapClient,也即配置两个单独的sqlMapClient实例即可;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: