从前面的《网店版重生系列:都是Spring配置中自动注入惹的祸》中我们可以看出一些有关datasource、sqlMapClientTemplate、sqlMapClient的相关配置信息;整体而言,采取的方式为:
其一,单实例sqlMapClient,只配置configLocation属性,不配置datasource;
其二,sqlMapClientTemplate中配置动态datasource、sqlMapClient,然后在Dao中注入sqlMapClientTemplate;
其三,由于获取用户与Mysql数据库之间的对应关系是保存在Oracle主库中,所以不适用动态数据源,而是直接通过配置sqlMapClient、JNDI datasource来完成注入;
在这种配置方式下,应用初期偶尔会出现报NullPointerException异常信息,堆栈信息如下:
开始的时候,我们还以为是Mysql数据库首次连接出现Time Out所致,但细想下不对啊!如果是这样堆栈信息中应该会包括TimeOut相关的信息才对啊,但是没有这个信息,再看看堆栈信息,是从SqlMapSessionImpl.setUserConnection中抛出来的,跟踪下源码,问题很快就被定位了,原因是SqlMapSession被关闭所致;来看看最为关键的这段代码先:
跟踪代码时发现,其实这段代码被调用了多次,第一次执行时没有任何问题,第二次执行时也没问题,异常信息其实是第三次执行时抛出,直接原因是此时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实例即可;