作家:京东保障 王奕龙人妖 射精
物流的分拣业务在某些分拣花样惟有一个数据源,因为数据量相比大,将所额外据存在一张表内查询速率慢,也为了作念不同建造数据的分库处治,便在这个数据源内创建了多个不同库名但表满盈疏通的数据库
当今需要上线报表事业来查询所额外据库中的数据进行统计,那么当今的问题来了,该何如 满足在建树一个数据源的情况下来查询该数据源下不同数据库的数据 呢,借助搜索引擎查到的分库已矣大多是借助 Sharding-JDBC 框架,建树多个数据源字据分库算法已矣数据源的切换,可是关于惟有一个数据源的系统来说,我以为引入框架再将单个数据源字据不同的库名建树成多个不同的数据源来已矣分库查询的逻辑我以为并不好。
含羞草研究院在线看如果咱们能在 SQL 实行前将 SQL 中扫数的表名前拼接上对应的库名的话,那么就概况已矣数据源的切换了,底下咱们讲一下使用 JSqlParser 和 Mybatis遏止器 已矣该逻辑,借助 JSqlParser 主如若为了深刻SQL,找到其中扫数的表名进行拼接,如果各人有更好的已矣方式,该组件并不是必须的。
已矣逻辑
SqlSource 是读取 XML 中 SQL 现实并将其发送给数据库实行的对象人妖 射精,如果咱们在实行前能遏止到该对象,并将其中的 SQL 替换掉便达成了咱们的策画。 SqlSource 有多种已矣,包括常见的DynamicSqlSource。其中包含着必要的实行逻辑,咱们需要作念的责任即是在这些逻辑实行完之后,对 SQL 进行改良,是以此次已矣咱们使用了 消散器模式,在原来的 SqlSource 上套一层,实行完 SqlSource 自己的法子之后对其进行增强,代码如下:
public abstract class AbstractDBNameInterceptor { /** * SqlSource 的消散器,作用是增强了 getBoundSql 法子,在基础上增多了动态分库的逻辑 */ static class SqlSourceDecorator implements SqlSource { /** * SQL 字段称呼 */ private static final String SQL_FIELD_NAME = "sql"; /** * 正本的 sql source */ private final SqlSource sqlSource; /** * 消散器进行封装 */ public SqlSourceDecorator(SqlSource sqlSource) { this.sqlSource = sqlSource; } @Override public BoundSql getBoundSql(Object parameterObject) { try { // 先生成出未修改前的 SQL BoundSql boundSql = sqlSource.getBoundSql(parameterObject); // 赢得数据库名 String dbName = getSpecificDBName(parameterObject); // 灵验才修改 if (isValid(dbName)) { // 生成需要修改完库名的 SQL String targetSQL = getRequiredSqlWithSpecificDBName(boundSql, dbName); // 更新 SQL updateSql(boundSql, targetSQL); } return boundSql; } catch (Exception e) { throw new RuntimeException(e); } } /** * 校验是否为灵验库名 */ private boolean isValid(String dbName) { return StringUtils.isNotEmpty(dbName) && !"null".equals(dbName); } /** * 赢得到咱们念念要的库名的 SQL */ private String getRequiredSqlWithSpecificDBName(BoundSql boundSql, String dbName) throws JSQLParserException { String originSql = boundSql.getSql(); // 赢得扫数的表名 Set tables = TablesNamesFinder.findTables(originSql); for (String table : tables) { originSql = originSql.replaceAll(table, dbName + "." + table); } return originSql; } /** * 修改 SQL */ private void updateSql(BoundSql boundSql, String sql) throws NoSuchFieldException, IllegalAccessException { // 通过反射修改sql语句 Field field = boundSql.getClass().getDeclaredField(SQL_FIELD_NAME); field.setAccessible(true); field.set(boundSql, sql); } } // ... }
界说了 AbstractDBNameInterceptor 综合类是为了已矣复用,并将 SqlSourceDecorator 消散器界说为静态里面类,这么的话,将扫数逻辑皆封装在综合类里面,之后这部分已矣好后研发径直已矣综合类的通用法子即可,无须热情它的里面已矣。
聚拢持重咱们证实注解一下 SqlSourceDecorator 的逻辑,其顶用到了 Java 反射斟酌的操作。率先通过反射赢得到 SQL,getSpecificDBName 法子是需要自界说已矣的,其中 parameterObject 对象是传到 DAO 层实行查询时的参数,在咱们的业务中是概况字据其中的建造斟酌参数拿到对应的处所库名的,而建造和具体库名的映射联系需要提前开动化好。在赢得到具体的库名后实行 getRequiredSqlWithSpecificDBName 法子来将其拼接到表名前,在这里咱们使用到了 JSqlParser 的器具类,深刻出来扫数的表名,实行字符串的替换,终末一步雷同是使用反射操作将该参数值再写且归,这么便完成了指定库名的任务。
接下来咱们需要看下综合遏止器中供遏止器复用的法子,如下:
public abstract class AbstractDBNameInterceptor { /** * SqlSource 字段称呼 */ private static final String SQL_SOURCE_FIELD_NAME = "sqlSource"; /** * 实行修改数据库名的逻辑 */ protected Object updateDBName(Invocation invocation) throws Throwable { // 消散器消散 SqlSource decorateSqlSource((MappedStatement) invocation.getArgs()[0]); return invocation.proceed(); } /** * 消散 SqlSource */ private void decorateSqlSource(MappedStatement statement) throws NoSuchFieldException, IllegalAccessException { if (!(statement.getSqlSource() instanceof SqlSourceDecorator)) { Field sqlSource = statement.getClass().getDeclaredField(SQL_SOURCE_FIELD_NAME); sqlSource.setAccessible(true); sqlSource.set(statement, new SqlSourceDecorator(statement.getSqlSource())); } } }
这个已经相比简便的,仅仅借助反射机制作念了一层“消散”,查询遏止器已矣如下:
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) }) public class SelectDBNameInterceptor extends AbstractDBNameInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { return updateDBName(invocation); } }
将其建树到 Mybatis 遏止器中人妖 射精,便能已矣数据库动态切换了。 审核剪辑 黄宇