iBatis 使用 OSCache 作缓存与 java.io.FileNotFoundException 异常

这种异常只会发生在 Windows 平台上,可以说是 iBatis 的一个 Bug,大约 iBatis 的开发测试人员都比较热衷于 Unix 族系的平台,而忽略了 Windows 下的兼容性测试。

出现 FileNotFoundException 异常的情形是这样的:Windows7 + iBatis2.3.0.677 + OSCache2.4.1。

iBatis 中配置使用 OSCache 缓存,在映射文件 Product.xml 中有如下声明片断:

OSCache 的 oscache.properties 中指明用磁盘文件缓存数据,注意以下几个配置:

Java 测试代码:

执行上面那段代码,你将会得到异常:

2009-8-19 10:45:52 com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache persistStore
严重: [oscache] Exception persisting 1991729862|2162672802|5|Product.getById|62996507|         select id, name,description as desc1  from test_product where id = ?     |executeQueryForObject
com.opensymphony.oscache.base.persistence.CachePersistenceException: Unable to write 'e:\OSCache\cache\application\1991729862|2162672802|5|Product_31getById|62996507|_49_50_51_52_53_54_55_56_57select_64id,_68name,description_85as_88desc1_94_95from_100test_105product_113where_119id_122=_124_125_126_127_128_129_130|executeQueryForObject.cache' in the cache. Exception: java.io.FileNotFoundException, Message: e:\OSCache\cache\application\1991729862|2162672802|5|Product_31getById|62996507|_49_50_51_52_53_54_55_56_57select_64id,_68name,description_85as_88desc1_94_95from_100test_105product_113where_119id_122=_124_125_126_127_128_129_130|executeQueryForObject.cache (文件名、目录名或卷标语法不正确。)
 at com.opensymphony.oscache.plugins.diskpersistence.AbstractDiskPersistenceListener.store(AbstractDiskPersistenceListener.java:376)
 at com.opensymphony.oscache.plugins.diskpersistence.AbstractDiskPersistenceListener.store(AbstractDiskPersistenceListener.java:238)
 at com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache.persistStore(AbstractConcurrentReadCache.java:1113)
 at com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache.put(AbstractConcurrentReadCache.java:1623)
 at com.opensymphony.oscache.base.algorithm.AbstractConcurrentReadCache.put(AbstractConcurrentReadCache.java:864)
 at com.opensymphony.oscache.base.Cache.putInCache(Cache.java:637)
 at com.opensymphony.oscache.base.Cache.putInCache(Cache.java:614)
 at com.opensymphony.oscache.general.GeneralCacheAdministrator.putInCache(GeneralCacheAdministrator.java:270)
 at com.ibatis.sqlmap.engine.cache.oscache.OSCacheController.putObject(OSCacheController.java:70)
 at com.ibatis.sqlmap.engine.cache.CacheModel.putObject(CacheModel.java:318)
 at com.ibatis.sqlmap.engine.mapping.statement.CachingStatement.executeQueryForObject(CachingStatement.java:80)
 at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.queryForObject(SqlMapExecutorDelegate.java:566)
 at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.queryForObject(SqlMapExecutorDelegate.java:541)
 at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.queryForObject(SqlMapSessionImpl.java:106)
 at com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.queryForObject(SqlMapClientImpl.java:83)
 at com.unmi.TestClient.main(TestClient.java:25)

无法写某个文件,文件找不到,一开始我也以为是 OSCache 那方出了什么差错,所以 Google 的关键词都偏重于 OSCache FileNotFoundExcption。后来结合 iBatis 才艰难的 Google 到了这个:http://osdir.com/ml/java.ibatisdb.devel/2004-06/msg00079.html,也有同问的:http://forums.opensymphony.com/thread.jspa?messageID=673372&。说句外话,搜索这方面内容时,能时刻为党说话的 Baidu 就不太擅长了,基本上用它来搜索 ‘儿子’ 和药品是无敌的了。

看了前面那个贴子,拍了下脑袋,其实早该醒悟的,不就是说的:'e:\OSCache\cache\application\1991729862|2162672802|5|Product_31getById|62996507|_49_50_51_52_53_54_55_56_57select_64id,_68name,description_85as_88desc1_94_95from_100test_105product_113where_119id_122=_124_125_126_127_128_129_130 文件无法写吗。去看看 e:\OSCache\cache\application 目录中,没有这里提到的文件。如果你试图去手工建立这么一个文件,文件名有多长且不说,但 Windows 肯定要提示你:文件名不能包含下面任何字符: \ / : * ? " < > | 。对啦,问题就出在 iBatis 为 OSCache 生成的文件名上。

开源的东西对我们使用者有什么好处,就是能完全窥探到源代码呀。我的习惯是开源的组件,总会下载到相应的源代码摆在它的身边。于是 Debug/Step Into 吧,发现前面那个长长的文件名其实就是 OSCache 用来缓存 iBatis 查询得到结果(注:iBatis 被缓存的粒度没 Hibernate 的缓存细,不是以实体对象为单位,而是以查询结果为单位,所以会出现第一次查询的 id=2 or id=3 的结果被缓存了,第二次查询 id=2 时还是从数据库中取得情况) 的 Key,这个 Key 是基于查询语句(含查询条件) 生成的。实现类是 iBatis 的 com.ibatis.sqlmap.engine.cache.CacheKey,它的 toString() 方法返回值就是文件名:

上面代码生成的文件名就可能包含竖线符号("|"),这在 Windows 平台是不允许的,在 Linux 下是不受此约束的。为了能让 iBatis 在使用文件缓存时保持平台独立性,我们的 Fix 办法可使在该 toString() 方法最后返回时把 | 换成都能够接受的符号,比如 -,即把 return 语句改为:

return returnValue.toString().replace('|', '-');

可以把修正编译好的 CacheKey.class 替换掉 iBatis jar 包中的相应位置的 CacheKey.class,或者若是 Web 应用,可直接放在 classes 目录中正确位置上,因为 classes 中散装的类文件要优先于 jar 包中的类得到加载。

问题需解决了,但还是得注意几点:

1. 即使是使用其它缓存实现,也都是由这个 CacheKey 来生成 Key 的,所以若有可能使用其它缓存组件,缓存文件时同样有可能出现类似的问题。
2. 如果 CacheKey 在结合 SQL 和 参数生成的文件名过长可能也会是个问题,受限于不同的文件系统。设置的缓存目录不要过深,适时完全自定 toString() 方法。
3. 还有,关于是从缓存中取数据还是从数据库取得问题,这个 CacheKey 的 equals() 方法值得细究,这是我的后话了。

本文链接 https://yanbin.blog/ibatis-oscache-java-io-filenotfoundexception/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

1 Comment
Inline Feedbacks
View all comments
找个美女做老婆
15 years ago

Java乐园学习网站: http://www.javaly.cn

有大量的学习文章和视频教程,以及一些项目源码

Java乐园学习群: 81107233