halo-dal原帖 地址 http://www.iteye.com/topic/1123284
近期刚刚完成halo-dal的代码,一些iteye的朋友通过qq问我设计思路。由于在qq上写的语言组织不是很完整,特此开一帖,详细写一下halo-dal的一些心得与技巧,希望大家能从中获得灵感,或者能指出我设计的不足之处。
halo-dal的雏形在2009年中旬的时候就已经存在了,当时看了手机之家 许超前 设计的 dal 相关的ppt,那时对此很好奇,工作之余设计了 hkframe。这个是建立在jdbc基础之上的一个分布式访问框架以及一个快速开发的web框架,当时没有开源的原因在于自认为没有做到很好,不是我理想的代码。后来一直在我自己的项目上使用。没有再进行更新。
到2011年时,又由于工作需要,再次捡起以前的代码翻看了2天,决定重新设计hkframe,用了2个星期完成了halo 框架,使用全新的思想进行设计。为什么叫halo,由于当时在玩一个游戏 halo,非常喜欢,在玩完游戏之余,非常兴奋的状态下完成的代码,所以起名的时候就写了halo。halo中还是包括一个jdbc访问框架和web框架。当时的框架是一个分布式访问和轻量级orm的结合体,进行开发,效率非常高。由于使用了asm来解决使用反射的问题,所以性能也很不错。唯一缺憾的地方就是只能用jdbc来写,而且必须是我提供的api。
设计halo-dal的原因为公司开发需要,发现没有合适的解决方案,公司内部jdbc hibernate ibatis 都在使用,而且很多设计方案代码入侵性较强,耦合度高。搜索发现hibernate share 只解决了分表问题,无法解决分库问题。ibatis没有多加搜索。后来就有一个想法,能不能设计一种简单的框架能支持各种现有的数据库访问框架呢。由此出现了halo-dal的设计。主要利用五一假期时间完成了代码。
进入正题:::
halo-dal 分布式数据访问设计
功能主要解决的问题是 :
如何能方便的选择 datasource 和真正的 tablename
顺便要做到:
尽量使用jdbc原有接口,做到最小化的代码入侵。(旨在能使用大家各自喜欢的hibernate ibatis mybatis 也能解决分布式访问问题)
那么所有设计的入口就只能在Connection以下。
为了简化第一个版本的开发,我只想支持PreparedStatement方式,不支持普通的Statement 以及批处理使用的BatchPreparedStatement
什么是我需要的信息?
我的关注点放到了sql上,因为根据jdbc使用经验来说,操作数据库离不开2个最主要的参数sql ,以及sql中赋值的参数 Object[] values。也就是说我只需要拦截到sql以及alues就可以进行分析。sql中包含最显而易见的一个信息,那就是tablename,那么datasource从哪里获得?没有别的东西,只能是靠sql 与values。
每个模块完全独立设计,只靠接口传递信息:
第一个要解决的问题就是解析sql与values获得我需要的信息,对于sql解析,我使用的就是最普通的字符串indexof substring这些api。解析后,我需要的信息就清楚了tablename、条件表达式、赋值表达式。
以下解释用到的例子
sql: select * from user where level=?
Object[] values =new Object[]{1};
tablename是什么?
在此的table name 不是真正的表名称。而是一个逻辑表明称:我称他为logicTableName,这个逻辑表名。最终会把sql中的tablename 替换成真正的表名。
条件表达式、赋值表达式是什么:
level=? 真正的值是1 那么表达式 就是 level、=、1,组合就成为了SQLExpression。
分表分库的路由解析器:
分析完sql,那就该进行分表分库信息的获取了,这个信息的解析,就在PartitionParser中,在此类的方法中传入tablename 以及表达式等有效信息。
例如level,这个是对user表进行分区的条件,那么我们只需要对level以及他的值进行相关运算即可。这个时候就用到了SQLExpression的数据.
通过解析,最终会获得 dsKey(datasource对应的key) 和 真正的表名称 realTableName,返回的对象PartitionTableInfo包含了这些信息。
如何使用解析的结果?
上面的步骤完成了sql解析以及获得真正datasource key和真正的表名,那么接下来就是如何组织上面2段程序。
既然要解析sql,那么就要找到能获得sql的位置。在Connection中与sql有关的几个主要接口就是
public PreparedStatement prepareStatement(String sql) throws SQLException;
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException;
......
因此,我只能通过自定义DALConnection implements Connection 来实现一个虚拟的Connection,这样就可以截取sql信息。
DALDataSource DALConnection DALPreparedStatement
DataSource.getConnection方法是获得链接的入口,为了获得DALConnection,我就需要自定义DALDataSource implements DataSource.所有真实的DataSource全部由DALDataSource来掌管,这些真实的DataSource全部都通过一个map来保存key为 dsKey(程序中会用到),value就是DataSource。
使用了DALDataSource DALConnection的好处就是能延迟开启真正的DataSource,这样才能保证先解析,再定位。例如,在使用spring 进行事务管理时,
假设 service.methodTX这个方法是需要开启事务,通常只有获得了Connection,才能setAutoCommit(false);,但是我们在这个步骤还没有获得sql,就无法进行解析,只有通过DALDataSource来获得一个DALConnection,这个DALConnection在此时是与任何真实的Connection无关的。因此并不会开启真正的Connection,以及真正的执行setAutoCommit(false);,所有对Connection进行的设置,都会通过DALConnection进行记录,直到获取真正的Connection时,才全部设置到真身。
通过DALConnection.prepareStatement,我们也无法获取真正的PreparedStatement,只有继续造假,创建了DALPreparedStatement.代码的重点就是DALPreparedStatement,因为通过PreparedSatement才可以设置sql需要的参数。DALPreparedStatement,会记录所有对本身的操作,直到获得了真正的PreparedStatement,才把记录的操作设置到真身。
在执行PreparedStatement.execute*()系列方法之前,我们都已经传递了sql,并进行参数赋值,那么我们就可以进行真正的分析了。通过调用prepare()方法就可以进行进行数据访问路由分析。
public boolean execute() throws SQLException {
this.prepare();
return ps.execute();
}
prepare这个方法会调用上面讲述的sql分析器 访问路由器,获得dsKey 、realTableName接下来 再次通过sql解析器替换sql中对应的表名。dsKey会存储到一个ThreadLocal类型的对象中,通过DALConnection.getCurrentConnection就可以获得真正的Connection,并开启PreparedStatement,最终执行的execute就落到了指定的数据库上。
整个halo-dal的设计思想就是以上这些了,有不妥之处请大家指出。
希望大家能做出更好的框架供我参考。