Mybatis 拦截器完成单数据源内多数据库切换
作者:京东稳妥 王奕龙。
物流的分拣事务在某些分拣场所只要一个数据源,由于数据量比较大,将一切数据存在一张表内查询速度慢,也为了做不同设备数据的分库办理,便在这个数据源内创建了多个不同库名但表完全相同的数据库。
现在需求上线报表服务来查询一切数据库中的数据进行计算,那么现在的问题来了,该怎么。 满意在装备一个数据源的状况下来查询该数据源下不同数据库的数据。呢,凭借搜。索引。擎查到的分库完成大多是凭借 Sharding-JDBC 结构,装备多个数据源依据分库。算法。完成数据源的切换,可是关于只要一个数据源的体系来说,我觉得引进结构再将单个数据源依据不同的库名装备成多个不同的数据源来完成分库查询的逻辑我觉得并不好。
假如咱们能在 SQL 履行前将 SQL 中一切的表名前拼接上对应的库名的话,那么就可以完成数据源的切换了,下面咱们讲一下运用 JSqlParser 和 Mybatis阻拦器 完成该逻辑,凭借 JSqlParser 首要是为了解析SQL,找到其间一切的表名进行拼接,假如咱们有更好的完成办法,该组件并不是有必要的。
完成逻辑。
SqlSource 是读取 XML 中 SQL 内容并将其发送给数据库履行的目标,假如咱们在履行前能阻拦到该目标,并将其间的 SQL 替换掉便达成了咱们的意图。 SqlSource 有多种完成,包含常见的DynamicSqlSource。其间包含着必要的履行逻辑,咱们需求做的作业就是在这些逻辑履行完之后,对 SQL 进行改造,所以这次完成咱们运用了。 装修器形式。,在本来的 SqlSource 上套一层,履行完 SqlSource 自身的办法之后对其进行增强,代码如下:
publ。ic。abstract class AbstractDBNameIntercept。or。{ /** * SqlSource 的装修器,作用是增强了 getBoundSql 办法,在基础上增加了动态分库的逻辑 */ sta。ti。c 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 pa。ram。eterObject) { 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.isNo。tE。mpty(dbName) && !"null".equals(dbName); } /** * 获取到咱们想要的库名的 SQL */ private String getRequiredSqlWithSpecificDBName(BoundSql boundSql, String dbName) throws JSQLParserException { String originSql = boundSql.getSql(); // 获取一切的表名 Set。< String >tables = TablesNamesFinder.findTables(originSql); for (String table : tables) { originSql = originSql.repl。ac。eAll(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 阻拦器中,便能完成数据库动态切换了。
审阅修改 黄宇。
内容来源:https://nlsngoisaoviet.com/app-1/xoi lac vn,http://chatbotjud.saude.mg.gov.br/app-1/bet365-games
(责任编辑:经济)