相比于Spring基于AbstractRoutingDataSource实现的分库分表功能,Sharding jdbc在单库单表扩展到多库多表时,兼容性方面表现的更好一点。例如,spring实现的分库分表sql写法如下:
select id, name, price, publish, intro
from book${tableIndex}
where id = #{id,jdbcType=INTEGER}
sql中的表名book需要加一个分表的后缀tableIndex,也就是需要在sql注入的参数中指定插入哪个表。相比,Sharding jdbc在这一块封装的更好一点。其sql中,根本不需要指定tableIndex,而是根据分库分表策略自动路由。
select id, name, price, publish, intro
from book
where id = #{id,jdbcType=INTEGER}
Sharding jdbc的这种特性,在水平扩展的时候无疑更具有吸引力。试想一下,一个项目开发一段时间后,单库单表数据量急剧上升,需要分库分表解决数据库的访问压力。而现有sql配置都是基于单库单表实现的,如果基于spring的AbstractRoutingDataSource实现,需要修改每一个相关表的sql,修改涉及较多地方,出错概率较大。而基于Sharding jdbc实现时,sql无需修改,只需要在spring中添加Sharding jdbc的相关配置即可,减少了修改面,大大简化分库分表的实现难度。
那么,Sharding jdbc是如何实现这种分库分表的逻辑呢?下面我们用一段简单、易懂的代码描述Sharding jdbc的原理。
通常我们在写一段访问数据库的数据时,逻辑是这样的:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
DataSource dataSource = ctx.getBean("dataSource", DataSource.class);
Connection connection = dataSource.getConnection();
String sql = "select id, name, price, publish, intro from book where id = 111";
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
// handle ResultSet...
Sharding jdbc是基于JDBC协议实现的,当我们获得dataSource时,这个dataSource是Sharding jdbc自己定义的一个SpringShardingDataSource类型的数据源,该数据源在返回getConnection()及prepareStatement()时,分别返回ShardingConnection和ShardingPreparedStatement的实例对象。然后在executeQuery()时,ShardingPreparedStatement做了这样的一件事:
- 根据逻辑sql,经过分库分表策略逻辑计算,获得分库分表的路由结果SQLRouteResult;
- SQLRouteResult中包含真实的数据源以及转换后的真正sql,利用真实的数据源去执行获得ResultSet;
- 将ResultSet列表封装成一个可以顺序读的ResultSet对象IteratorReducerResultSet。
class ShardingPreparedStatement implements PreparedStatement {
@Override
public ResultSet executeQuery() throws SQLException {
List routeResults = routeSql(logicSql);
List resultSets = new ArrayList<>(routeResults.size());
for (SQLRouteResult routeResult : routeResults) {
PreparedStatement ps = routeResult.getDataSource().getConnection.prepareStatement(routeResult.getParsedSql());
ResultSet rs = ps.executeQuery();
resultSets.add(rs);
}
return new IteratorReducerResultSet(resultSets);
}
.....
}
其中,分库分表策略的sql路由过程,我们将Sharding jdbc中的相关代码全部抽出来,放到一起来观看这个过程的实现:
// 环境准备
@SuppressWarnings("resource")
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
SpringShardingDataSource dataSource = ctx.getBean(SpringShardingDataSource.class);
Field field = SpringShardingDataSource.class.getSuperclass().getDeclaredField("shardingContext");
field.setAccessible(true);
ShardingContext sctx = (ShardingContext)field.get(dataSource);
ShardingRule shardingRule = sctx.getShardingRule();
String logicSql = "select id, name, price, publish, intro from book where id = ?";
List