本文首发在我的博客:https://blog.liuzijian.com/post/mybatis-plus-source-multi-table-inner-interceptor.html
一、概述
BaseMultiTableInnerInterceptor是MyBatis-Plus中的一个抽象类,位于mybatis-plus-jsqlparser-4.9模块中com.baomidou.mybatisplus.extension.plugins.inner包下,提供解析和重写SQL功能,MyBatis-Plus的数据权限(TenantLineInnerInterceptor)插件和多租户(DataPermissionInterceptor)插件均继承了BaseMultiTableInnerInterceptor类来实现对应的功能。
本文基于MyBatis-Plus的3.5.9版本的源码,并fork了代码: https://github.com/changelzj/mybatis-plus/tree/lzj-3.5.9- public abstract class BaseMultiTableInnerInterceptor
- extends JsqlParserSupport
- implements InnerInterceptor {
- protected void processSelectBody(Select selectBody, final String whereSegment) {...}
- protected Expression andExpression(Table table, Expression where, final String whereSegment) {...}
- protected void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {...}
- private List<Table> processFromItem(FromItem fromItem, final String whereSegment) {...}
- protected void processWhereSubSelect(Expression where, final String whereSegment) {...}
- protected void processSelectItem(SelectItem selectItem, final String whereSegment) {...}
- protected void processFunction(Function function, final String whereSegment) {...}
- protected void processOtherFromItem(FromItem fromItem, final String whereSegment) {...}
- private List<Table> processSubJoin(ParenthesedFromItem subJoin, final String whereSegment) {...}
- private List<Table> processJoins(List<Table> mainTables, List<Join> joins, final String whereSegment) {...}
- protected Expression builderExpression(Expression currentExpression, List<Table> tables, final String whereSegment) {...}
- public abstract Expression buildTableExpression(final Table table, final Expression where, final String whereSegment);
- }
复制代码 二、执行流程
BaseMultiTableInnerInterceptor实现了InnerInterceptor接口中的beforeQuery(),beforePrepare()方法,实际上是子类去间接实现的,MyBatis-Plus就是对实现这个接口的类进行回调,在查询SQL即将执行时调用beforeQuery(),在增删改SQL即将执行前调用beforePrepare(),beforeQuery()中再去调用parserSingle(),beforePrepare()再去调用parserMulti()
查询语句只能一次执行一条,增删改语句可以用分号隔断一次执行多条。故beforeQuery()调用parserSingle(),beforePrepare()调用parserMulti()
- @Override
- public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
- if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
- return;
- }
- PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
- mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
- }
- @Override
- public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
- PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
- MappedStatement ms = mpSh.mappedStatement();
- SqlCommandType sct = ms.getSqlCommandType();
- if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
- if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
- return;
- }
- PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
- mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
- }
- }
复制代码 parserSingle(),parserMulti()是BaseMultiTableInnerInterceptor从JsqlParserSupport抽象类继承而来的,JsqlParserSupport是MyBatis-Plus基于JsqlParser(JSQLParser详见:SQL解析工具JSQLParser)封装的一个工具类,这个类的功能非常简单,作用是判断SQL是增删改查的哪一种类型,然后分别调用对应的方法开始解析。- public abstract class JsqlParserSupport {
- /**
- * 日志
- */
- protected final Log logger = LogFactory.getLog(this.getClass());
- public String parserSingle(String sql, Object obj) {
- if (logger.isDebugEnabled()) {
- logger.debug("original SQL: " + sql);
- }
- try {
- Statement statement = JsqlParserGlobal.parse(sql);
- return processParser(statement, 0, sql, obj);
- } catch (JSQLParserException e) {
- throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e.getCause(), sql);
- }
- }
- public String parserMulti(String sql, Object obj) {
- if (logger.isDebugEnabled()) {
- logger.debug("original SQL: " + sql);
- }
- try {
- // fixed github pull/295
- StringBuilder sb = new StringBuilder();
- Statements statements = JsqlParserGlobal.parseStatements(sql);
- int i = 0;
- for (Statement statement : statements) {
- if (i > 0) {
- sb.append(StringPool.SEMICOLON);
- }
- sb.append(processParser(statement, i, sql, obj));
- i++;
- }
- return sb.toString();
- } catch (JSQLParserException e) {
- throw ExceptionUtils.mpe("Failed to process, Error SQL: %s", e.getCause(), sql);
- }
- }
- /**
- * 执行 SQL 解析
- *
- * @param statement JsqlParser Statement
- * @return sql
- */
- protected String processParser(Statement statement, int index, String sql, Object obj) {
- if (logger.isDebugEnabled()) {
- logger.debug("SQL to parse, SQL: " + sql);
- }
- if (statement instanceof Insert) {
- this.processInsert((Insert) statement, index, sql, obj);
- } else if (statement instanceof Select) {
- this.processSelect((Select) statement, index, sql, obj);
- } else if (statement instanceof Update) {
- this.processUpdate((Update) statement, index, sql, obj);
- } else if (statement instanceof Delete) {
- this.processDelete((Delete) statement, index, sql, obj);
- }
- sql = statement.toString();
- if (logger.isDebugEnabled()) {
- logger.debug("parse the finished SQL: " + sql);
- }
- return sql;
- }
- /**
- * 新增
- */
- protected void processInsert(Insert insert, int index, String sql, Object obj) {
- throw new UnsupportedOperationException();
- }
- /**
- * 删除
- */
- protected void processDelete(Delete delete, int index, String sql, Object obj) {
- throw new UnsupportedOperationException();
- }
- /**
- * 更新
- */
- protected void processUpdate(Update update, int index, String sql, Object obj) {
- throw new UnsupportedOperationException();
- }
- /**
- * 查询
- */
- protected void processSelect(Select select, int index, String sql, Object obj) {
- throw new UnsupportedOperationException();
- }
- }
复制代码 当调用parserSingle()或parserMulti()并传入SQL时,会在processParser()方法中先判断是哪一种Statement,然后分别强转为详细的Select、Update、Delete、Insert对象,再调用子类(例如:DataPermissionInterceptor)间接继承并重写的processSelect()、processDelete()、processUpdate()方法。
子类中的processSelect()方法会再调用父类BaseMultiTableInnerInterceptor中的processSelectBody()对查询进行解析,processUpdate()和processDelete()同理。如许计划的原因可能是由详细的子类根据功能来最终确定解析和重写逻辑,而BaseMultiTableInnerInterceptor只提供解析和重写能力不负责不同场景下的详细逻辑实现。- @Override
- protected void processSelect(Select select, int index, String sql, Object obj) {
- if (dataPermissionHandler == null) {
- return;
- }
- if (dataPermissionHandler instanceof MultiDataPermissionHandler) {
- // 参照 com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.processSelect 做的修改
- final String whereSegment = (String) obj;
- processSelectBody(select, whereSegment);
- List<WithItem> withItemsList = select.getWithItemsList();
- if (!CollectionUtils.isEmpty(withItemsList)) {
- withItemsList.forEach(withItem -> processSelectBody(withItem, whereSegment));
- }
- } else {
- // 兼容原来的旧版 DataPermissionHandler 场景
- if (select instanceof PlainSelect) {
- this.setWhere((PlainSelect) select, (String) obj);
- } else if (select instanceof SetOperationList) {
- SetOperationList setOperationList = (SetOperationList) select;
- List<Select> selectBodyList = setOperationList.getSelects();
- selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
- }
- }
- }
- /**
- * update 语句处理
- */
- @Override
- protected void processUpdate(Update update, int index, String sql, Object obj) {
- final Expression sqlSegment = getUpdateOrDeleteExpression(update.getTable(), update.getWhere(), (String) obj);
- if (null != sqlSegment) {
- update.setWhere(sqlSegment);
- }
- }
- /**
- * delete 语句处理
- */
- @Override
- protected void processDelete(Delete delete, int index, String sql, Object obj) {
- final Expression sqlSegment = getUpdateOrDeleteExpression(delete.getTable(), delete.getWhere(), (String) obj);
- if (null != sqlSegment) {
- delete.setWhere(sqlSegment);
- }
- }
- protected Expression getUpdateOrDeleteExpression(final Table table, final Expression where, final String whereSegment) {
- if (dataPermissionHandler == null) {
- return null;
- }
- if (dataPermissionHandler instanceof MultiDataPermissionHandler) {
- return andExpression(table, where, whereSegment);
- } else {
- // 兼容旧版的数据权限处理
- return dataPermissionHandler.getSqlSegment(where, whereSegment);
- }
- }
复制代码 三、源码解读
与更新和删除语句的解析相比,对查询语句进行解析和重写的逻辑是更加复杂的,步骤也更多,需要解析到SQL语句的各个部门,分为多个方法,方法间互相配合实现对复杂查询SQL语句的解析和重写
执行的大抵流程如下:
如SQL结构复杂,需要先将一个复杂SQL拆分为若干简单SQL,然后依次对每个SQL需要重写条件的地方(select xx,from xx,join xx,where xx)进行表和条件解析然后追加过滤条件,如果遇到子查询需要递归解析子查询直到SQL所有部门都被解析到
3.1 processSelectBody
该方法是解析SELECT语句的入口方法,会先对复杂的SELECT语句进行简化拆分,再分别调用processPlainSelect()来解析每个部门- protected void processSelectBody(Select selectBody, final String whereSegment) {
- if (selectBody == null) {
- return;
- }
- if (selectBody instanceof PlainSelect) {
- processPlainSelect((PlainSelect) selectBody, whereSegment);
- } else if (selectBody instanceof ParenthesedSelect) {
- ParenthesedSelect parenthesedSelect = (ParenthesedSelect) selectBody;
- processSelectBody(parenthesedSelect.getSelect(), whereSegment);
- } else if (selectBody instanceof SetOperationList) {
- SetOperationList operationList = (SetOperationList) selectBody;
- List<Select> selectBodyList = operationList.getSelects();
- if (CollectionUtils.isNotEmpty(selectBodyList)) {
- selectBodyList.forEach(body -> processSelectBody(body, whereSegment));
- }
- }
- }
复制代码 解读:
该方法传入一个jsqlparser的Select对象,因为有的SELECT语句结构比较复杂,需要化繁为简进行拆分然后对每个部门分别进行解析,这里MyBatis-Plus考虑了三种情况:
- PlainSelect:最标准的SELECT语句格式,直接调用processPlainSelect(PlainSelect plainSelect)方法开始解析即可
- ParenthesedSelect:带括号的子查询,先去掉括号,将括号内SELECT语句再次调用processSelectBody(Select select)进行递归解析,直到格式满足PlainSelect
- SetOperationList:多个SELECT语句通过UNION、UNION ALL等组合为一个团体的SELECT语句的情况,分别拆开取出每一段SELECT,将每一段SELECT再次调用processSelectBody(Select select)进行递归解析,直到格式满足PlainSelect
还有一种select语句中带有with的情况,要把with中的查询语句提取进行解析,不过不是在这里处理的,而是在子类的processSelect方法中,调用processSelectBody方法之后
3.2 processPlainSelect
该方法用于开启一个对常规形式的SELECT语句的解析- protected void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {
- //#3087 github
- List<SelectItem<?>> selectItems = plainSelect.getSelectItems();
- if (CollectionUtils.isNotEmpty(selectItems)) {
- selectItems.forEach(selectItem -> processSelectItem(selectItem, whereSegment));
- }
- // 处理 where 中的子查询
- Expression where = plainSelect.getWhere();
- processWhereSubSelect(where, whereSegment);
- // 处理 fromItem
- FromItem fromItem = plainSelect.getFromItem();
- List<Table> list = processFromItem(fromItem, whereSegment);
- List<Table> mainTables = new ArrayList<>(list);
- // 处理 join
- List<Join> joins = plainSelect.getJoins();
- if (CollectionUtils.isNotEmpty(joins)) {
- processJoins(mainTables, joins, whereSegment);
- }
- // 当有 mainTable 时,进行 where 条件追加
- if (CollectionUtils.isNotEmpty(mainTables)) {
- plainSelect.setWhere(builderExpression(where, mainTables, whereSegment));
- }
- }
复制代码 3.7 processJoins
该方法用于解析和重写JOIN连接部门的SQL,将被驱动表(要保留部门数据)的过滤条件追加在ON条件上,并确定最终的驱动表(要保留全部数据)到底是哪一张,该方法实现的功能虽然简单,但逻辑却是该类所有的方法中最复杂的。- SELECT
- [SelectItem]
- FROM
- [FromItem]
- LEFT/RIGHT/INNER JOIN [JOIN]
- WHERE
- [Expression]
复制代码 解读:
这里假设每张表都追加一个scope = 12的过滤条件用于数据权限或多租户等功能,这里用几种类型的SQL测试用例来解读该方法,其中有些形式的SQL写法在开发中基本不会用到,但是还是列举出来一一分析下
3.7.1 隐式INNER JOIN
- protected void processSelectItem(SelectItem selectItem, final String whereSegment) {
- Expression expression = selectItem.getExpression();
- if (expression instanceof Select) {
- processSelectBody(((Select) expression), whereSegment);
- } else if (expression instanceof Function) {
- processFunction((Function) expression, whereSegment);
- } else if (expression instanceof ExistsExpression) {
- ExistsExpression existsExpression = (ExistsExpression) expression;
- processSelectBody((Select) existsExpression.getRightExpression(), whereSegment);
- }
- }
复制代码 jsqlparser解析这种隐式内连接SQL时,会默认将from后面接的第一个表userinfo作为驱动表,传入List[table] mainTables,剩下的表默认作为非驱动表在List joins中,在隐式内连接中,因为需要取多表交集,语法上实际是没有谁驱动谁的概念的,只要当前的JOIN满足if (join.isSimple()) == true,则当前JOIN的表也添加到mainTables中,并continue结束当前JOIN条件的解析,实际上隐式内连接的情况下List joins中的JOIN都满足if (join.isSimple()) == true,最后所有JOIN的表都会被加入mainTables中,最终在where上追加过滤条件,得到SQL如下:- protected void processSelectItem(SelectItem selectItem, final String whereSegment) {
- Expression expression = selectItem.getExpression();
- if (expression instanceof Select) {
- processSelectBody(((Select) expression), whereSegment);
- } else if (expression instanceof Function) {
- processFunction((Function) expression, whereSegment);
- } else if (expression instanceof ExistsExpression) {
- ExistsExpression existsExpression = (ExistsExpression) expression;
- processSelectBody((Select) existsExpression.getRightExpression(), whereSegment);
- }
- }AND userinfo.scope = 12 AND dept.scope = 12 AND role.scope = 12
复制代码 3.7.2 INNER JOIN
- SELECT name
- FROM user u
- WHERE u.math_score < (SELECT avg(score) FROM math )
- OR u.english_score > (SELECT avg(score) FROM english )
- AND (SELECT order_num FROM student ) = u.order_num
- AND u.role_id IN (SELECT id FROM role )
- AND EXISTS ( SELECT * FROM customer WHERE id = 6 )
- AND NOT EXISTS ( SELECT * FROM customer WHERE id = 7 )
复制代码 INNER JOIN的情况和隐式内连接的情况雷同,都是取多张表的交集,传入List[table] mainTables中的唯一的元素是userinfo,List joins中依次是INNER JOIN的两张表dept,role,解析第一个inner join时,userinfo,dept两表都会生存到onTables中,这会将两表各自的scope = 12过滤条件依次追加在当前inner join dept的ON后,解析到第二个inner join的表时,则是把解析到的role表加入到onTables中,同理会将这个表的过滤条件scope = 12追加在当前inner join role的ON后,第三个和更后面的JOIN的规则和第二个是一样的。
因此,和隐式内连接不同的是,INNER JOIN下过滤条件不会加在where上,而是将过滤条件全部加在每个JOIN的ON后面,最终得到SQL:- SELECT name FROM user u
- WHERE (u.math_score < (SELECT avg(score) FROM math WHERE math.scope = 12)
- OR u.english_score > (SELECT avg(score) FROM english WHERE english.scope = 12)
- AND (SELECT order_num FROM student WHERE student.scope = 12) = u.order_num
- AND u.role_id IN (SELECT id FROM role WHERE role.scope = 12)
- AND EXISTS (SELECT * FROM customer WHERE id = 6 AND customer.scope = 12)
- AND NOT EXISTS (SELECT * FROM customer WHERE id = 7 AND customer.scope = 12))
- AND user.scope = 12
复制代码 3.7.3 LEFT JOIN
- /**
- * 处理子查询等
- */
- protected void processOtherFromItem(FromItem fromItem, final String whereSegment) {
- // 去除括号
- // while (fromItem instanceof ParenthesisFromItem) {
- // fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
- // }
- if (fromItem instanceof ParenthesedSelect) {
- Select subSelect = (Select) fromItem;
- processSelectBody(subSelect, whereSegment);
- } else if (fromItem instanceof ParenthesedFromItem) {
- logger.debug("Perform a subQuery, if you do not give us feedback");
- }
- }
复制代码 LEFT JOIN取的是FROM表的全部数据,是最简单的一种情况,方法开始执行时,参数mainTables中传入userinfo,joins中存放的则是dept,role两张表,局部变量mainTable和leftTable均为userinfo,因为LEFT JOIN取的是userinfo表的全部数据,因此mainTables中的userinfo就是驱动表,过滤条件加在WHERE上。LEFT JOIN的dept和role两张表都是被驱动表,过滤条件加在ON上。- /**
- * 处理函数
- * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
- * <p> fixed gitee pulls/141</p>
- *
- * @param function
- */
- protected void processFunction(Function function, final String whereSegment) {
- ExpressionList<?> parameters = function.getParameters();
- if (parameters != null) {
- parameters.forEach(expression -> {
- if (expression instanceof Select) {
- processSelectBody(((Select) expression), whereSegment);
- } else if (expression instanceof Function) {
- processFunction((Function) expression, whereSegment);
- } else if (expression instanceof EqualsTo) {
- if (((EqualsTo) expression).getLeftExpression() instanceof Select) {
- processSelectBody(((Select) ((EqualsTo) expression).getLeftExpression()), whereSegment);
- }
- if (((EqualsTo) expression).getRightExpression() instanceof Select) {
- processSelectBody(((Select) ((EqualsTo) expression).getRightExpression()), whereSegment);
- }
- }
- });
- }
- }
复制代码 3.7.4 RIGHT JOIN
RIGHT JOIN取的是JOIN后的表的全部数据,和LEFT JOIN正好相反,方法开始执行时,参数mainTables中传入userinfo,joins中存放的则是dept,role两张表,局部变量mainTable和leftTable均为userinfo
循环第一个JOIN,起首交换驱动和非驱动表,mainTable = joinTable将dept赋给mainTable,原先的userinfo放到onTables中并追加过滤条件到ON上,再将dept放进mainTables,交换完成后,本次JOIN的驱动表dept再赋给leftTable记载下来用于下次JOIN解析
第二个JOIN,仍然是右连接,role将作为驱动表取代前次的dept,因此mainTable = joinTable将role赋给mainTable,leftTable依然记载着前次JOIN的驱动表dept,但本次RIGHT JOIN中dept已经变为被驱动表,所以dept放到onTables中追加过滤条件到本次JOIN的ON上,从而缩小前次效果集的范围
更多JOIN以此类推,RIGHT JOIN中,越是最后JOIN的表越“大“,循环结束后,role作为最终的驱动表,在where上追加过滤条件,最终得到SQL:- SELECT u.id, u.name FROM userinfo u, dept d, role r
- WHERE u.p = 1
- AND u.dept_id = d.id
- AND u.rid = r.id
复制代码 3.7.5 先INNER再RIGHT
- SELECT u.id, u.name FROM userinfo u, dept d, role r
- WHERE u.p = 1
- AND u.dept_id = d.id
- AND u.rid = r.id
- AND userinfo.scope = 12
- AND dept.scope = 12
- AND role.scope = 12
复制代码 这种情况下解析第一个INNER JOIN的逻辑和之前的是一样的,userinfo和dept同时作为驱动表,把过滤条件加在ON上,然后默认驱动表是当前JOIN的dept,并赋值给leftTable,当解析第二个的RIGHT JOIN的role表时,role表成为最终查出全部数据的驱动表,因此为前次赋值给leftTable的dept表追加过滤条件到本次RIGHT JOIN role的ON后,缩小前次JOIN的效果集范围,并最终将role生存到mainTables在where上追加过滤条件,实现查出role的独有加role和前次inner join效果集的共有,得到如下SQL:- SELECT u.id, u.name
- FROM userinfo u
- INNER JOIN dept d ON u.dept_id = d.id
- INNER JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.6 先RIGHT再INNER
- SELECT u.id, u.name
- FROM userinfo u
- INNER JOIN dept d ON u.dept_id = d.id AND userinfo.scope = 12 AND dept.scope = 12
- INNER JOIN role r ON u.rid = r.id AND role.scope = 12
- WHERE u.p = 1
复制代码 第一个RIGHT JOIN和之前的一样,起首交换表,mainTable = joinTable将dept赋给mainTable,原先的userinfo放到onTables中并追加过滤条件到ON上,再将dept放进mainTables,交换完成后,本次JOIN的驱动表dept再赋给leftTable记载下来用于下次JOIN解析,第二次循环的INNER JOIN是要把当前role表和前次的RIGHT JOIN的效果集取交集,因此会将前次的驱动表dept和当前INNER JOIN的表role都加在本次JOIN的ON上做过滤条件拼接就够了,不需要在where拼接任何条件,因此会清空mainTables,得到SQL如下:- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN dept d ON u.dept_id = d.id
- LEFT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.7 先INNER再LEFT
- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN dept d ON u.dept_id = d.id AND dept.scope = 12
- LEFT JOIN role r ON u.rid = r.id AND role.scope = 12
- WHERE u.p = 1 AND userinfo.scope = 12
复制代码 这种情况第一次循环先处理INNER JOIN,将userinfo和dept两表的过滤条件加在第一个INNER JOIN的ON上,mainTables没有元素,第二次循环处理LEFT JOIN时,因为要取前次INNER JOIN效果的所有加前次INNER JOIN效果和role表的共有,因此将过滤条件加在LEFT JOIN role的ON上缩小role表的范围即可,得到SQL:- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN dept d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.8 先LEFT再INNER
- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN dept d ON u.dept_id = d.id AND userinfo.scope = 12
- RIGHT JOIN role r ON u.rid = r.id AND dept.scope = 12
- WHERE u.p = 1 AND role.scope = 12
复制代码 解析LEFT JOIN时,取from表的全部,因此驱动表就是userinfo,INNER JOIN时又需要取role和前次LEFT JOIN效果集的交集,因此会将驱动表userinfo和role表的过滤条件加在INNER JOIN的ON上面,得到SQL如下:- SELECT u.id, u.name
- FROM userinfo u
- INNER JOIN dept d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.9 先RIGHT再LEFT
- SELECT u.id, u.name
- FROM userinfo u
- INNER JOIN dept d ON u.dept_id = d.id AND userinfo.scope = 12 AND dept.scope = 12
- RIGHT JOIN role r ON u.rid = r.id AND dept.scope = 12
- WHERE u.p = 1 AND role.scope = 12
复制代码 解析第一个RIGHT JOIN时,JOIN的表要查出全部数据,是驱动表,因此通过mainTable = joinTable;将dept设置为驱动表,并将dept存入mainTables,userinfo表存入onTables中作为被驱动表,将userinfo的过滤条件追加在ON上。
解析第二个LEFT JOIN时,要取前次JOIN的效果集的全部,role表作为当前的joinTable存入onTables,将过滤条件追加在当前JOIN的ON上,mainTables存的是主导前次效果集的表dept,在本次JOIN结束后,dept表的过滤条件加在最终的WHERE上,得到SQL:- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN dept d ON u.dept_id = d.id
- INNER JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.10 先LEFT再RIGHT
- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN dept d ON u.dept_id = d.id AND userinfo.scope = 12
- INNER JOIN role r ON u.rid = r.id AND dept.scope = 12 AND role.scope = 12
- WHERE u.p = 1
复制代码 解析第一个LEFT JOIN时,效果集需要取userinfo表的全部,mainTable, leftTable的值都是userinfo,mainTables中唯一的元素也是userinfo,LEFT JOIN dept直接把JOIN的dept表的过滤条件追加在ON上。
解析第二个RIGHT JOIN role时,最终的效果集要以role表为准了,于是mainTable赋值为role表,将mainTables清空,leftTable不为空的话,存入onTables中,于是userinfo表将在本次JOIN的ON上追加过滤条件,role表将存入到mainTables中在WHERE上追加过滤条件,得到SQL如下:- SELECT u.id, u.name
- FROM userinfo u
- INNER JOIN dept d ON u.dept_id = d.id
- LEFT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.11 FROM子查询JOIN表
LEFT JOIN:- SELECT u.id, u.name
- FROM userinfo u
- INNER JOIN dept d ON u.dept_id = d.id AND userinfo.scope = 12 AND dept.scope = 12
- LEFT JOIN role r ON u.rid = r.id AND role.scope = 12
- WHERE u.p = 1
复制代码 这种情况下,from后的是子查询,参数mainTables元素数为0,dept表加入到onTables中在ON上追加过滤条件,但是from后的子查询的过滤条件追加已经在子查询解析重写中完成,因此if (mainTable != null && !mainTables.contains(mainTable))不满足,mainTables中没有要追加条件到where上的表,如第二次还是LEFT JOIN同理,最终得到SQL如下:- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN dept d ON u.dept_id = d.id
- INNER JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 RIGHT JOIN:- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN dept d ON u.dept_id = d.id AND dept.scope = 12
- INNER JOIN role r ON u.rid = r.id AND userinfo.scope = 12 AND role.scope = 12
- WHERE u.p = 1
复制代码 这种情况,from后的是子查询,参数mainTables元素数为0,leftTable一开始肯定也为null,因此第一个RIGHT JOIN后面没有ON过滤条件,但是第一个JOIN的dept表会被mainTable = joinTable设置为驱动表,onTables没有元素会最终走到leftTable = joinTable将dept设置为leftTable,第二次RIGHT JOIN时就会追加dept的过滤条件在当前的ON上来缩小前次JOIN的效果集,得到SQL如下:- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN dept d ON u.dept_id = d.id
- LEFT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.12 FROM表JOIN子查询
RIGHT JOIN:- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN dept d ON u.dept_id = d.id AND userinfo.scope = 12
- LEFT JOIN role r ON u.rid = r.id AND role.scope = 12
- WHERE u.p = 1 AND dept.scope = 12
复制代码 如许的SQL处理起来比较简单,因为JOIN的都是子查询而不是表,因此会执行processOtherFromItem(joinItem, whereSegment)将子查询表追加的条件直接加在子查询语句的where上面,主SQL语句的条件不需要区分驱动表和非驱动表和各个表的过滤条件在ON或WHERE的位置,处理完子查询后,参数List[table] mainTables会原样返回,FROM后面的表直接在WHERE上拼接过滤条件,最终得到SQL:- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN dept d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 LEFT JOIN:- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN dept d ON u.dept_id = d.id AND dept.scope = 12
- RIGHT JOIN role r ON u.rid = r.id AND userinfo.scope = 12
- WHERE u.p = 1 AND role.scope = 12
复制代码 处理LEFT的情况和RIGHT是一样的,得到的SQL形式也相同:- SELECT u.id, u.name
- FROM (SELECT * FROM userinfo ) u
- LEFT JOIN dept d ON u.dept_id = d.id
- LEFT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.13 FROM子查询JOIN子查询
- SELECT u.id, u.name
- FROM (SELECT * FROM userinfo WHERE userinfo.scope = 12) u
- LEFT JOIN dept d ON u.dept_id = d.id AND dept.scope = 12
- LEFT JOIN role r ON u.rid = r.id AND role.scope = 12
- WHERE u.p = 1
复制代码 这种情况本质上和FROM表JOIN子查询是一样的- SELECT u.id, u.name
- FROM (SELECT * FROM userinfo ) u
- RIGHT JOIN dept d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.14 不支持的情况
processJoins()方法似乎并不是万能的,有几种我遇到的不能支持的极度情况:
1.JOIN表和JOIN子查询混用时,使用了RIGHT会导致丢掉某个表的过滤条件
以下两个是重写过的SQL,都会导致userinfo表的scope条件丢失- SELECT u.id, u.name
- FROM (SELECT * FROM userinfo WHERE userinfo.scope = 12) u
- RIGHT JOIN dept d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id AND dept.scope = 12
- WHERE u.p = 1 AND role.scope = 12
复制代码- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN (SELECT * FROM dept ) d ON u.dept_id = d.id
- RIGHT JOIN (SELECT * FROM role ) r ON u.rid = r.id
- WHERE u.p = 1
复制代码 2.from子查询后,left和right混用时,会导致表的范围限制出现题目,因为找不到前次效果集范围的基准表是哪个了
例:这是一个重写过的SQL,因为from后的表不存在(因为是子查询),在执行leftTable = mainTable == null ? joinTable时,将left join的dept表错误的作为了驱动表,导致下次right join时以dept表为基准,将dept又追加一次dept.scope = 12,实际应当以(SELECT * FROM userinfo WHERE userinfo.scope = 12)为基准,如许就导致(SELECT * FROM userinfo WHERE userinfo.scope = 12)的记载不全- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN (SELECT * FROM dept WHERE dept.scope = 12) d ON u.dept_id = d.id
- RIGHT JOIN (SELECT * FROM role WHERE role.scope = 12) r ON u.rid = r.id
- WHERE u.p = 1 AND userinfo.scope = 12
复制代码 3.case表达式中如出现select,默认不处理,可能是因为这里的select条件不影响团体查询效果的范围,没有处理的须要
例:- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN (SELECT * FROM dept ) d ON u.dept_id = d.id
- LEFT JOIN (SELECT * FROM role ) r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.7.15 小结
processJoins()方法针JOIN的表进行解析重写,并对照FROM后面的表根据每次JOIN效果集的范围确定每张表在当前JOIN中的脚色,从而调整要追加的条件的位置是在ON上还是WHERE上,做到既要精准的进行条件限制,又不能破坏原有SQL逻辑应当得到的效果集范围
3.8 processSubJoin
sub join的情况,目前还没遇到过,之后再补充,这个分支应该很少走- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN (SELECT * FROM dept WHERE dept.scope = 12) d ON u.dept_id = d.id
- LEFT JOIN (SELECT * FROM role WHERE role.scope = 12) r ON u.rid = r.id
- WHERE u.p = 1 AND userinfo.scope = 12
复制代码 3.9 processFromItem
对FROM后面的结构进行解析,解析出的有表(Table)或子查询(ParenthesedSelect)以及(table1 join table2)等结构,分别处理- SELECT u.id, u.name
- FROM (SELECT * FROM userinfo ) u
- RIGHT JOIN (SELECT * FROM dept ) d ON u.dept_id = d.id
- RIGHT JOIN (SELECT * FROM role ) r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.10 builderExpression
刚方法用于对解析出来的表在已有的条件上追加过滤条件,在FROM后面和ON后面解析出来的表和对应条件都会传到在这个方法,先将传进来的表追加条件并拼接成AND结构,再判断已有条件是使用AND还是OR连接,如果已有的条件是OR连接,则将已有提有条件用小括号括起来再去AND要追加的条件,如果已有条件就是AND连接的,则把要追加的条件和已有条件直接AND相连即可- SELECT u.id, u.name
- FROM (SELECT * FROM userinfo WHERE userinfo.scope = 12) u
- RIGHT JOIN (SELECT * FROM dept WHERE dept.scope = 12) d ON u.dept_id = d.id
- RIGHT JOIN (SELECT * FROM role WHERE role.scope = 12) r ON u.rid = r.id
- WHERE u.p = 1
复制代码 3.11 buildTableExpression
该方法本是BaseMultiTableInnerInterceptor中的一个抽象方法,用于确定对某个表要拼接的过滤条件详细是什么,由子类实现重写,这里先拼接一个scope = 12的过滤条件用于测试- SELECT u.id, u.name
- FROM userinfo u
- LEFT JOIN (SELECT * FROM dept WHERE dept.scope = 12) d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id
- LEFT JOIN (SELECT * FROM job WHERE job.scope = 12) j ON u.jid = j.id
- WHERE u.p = 1 AND role.scope = 12
复制代码 2.12 andExpression
这个方法用于给单个表在已有的条件上追加过滤条件,实现过程雷同builderExpression,一般只有删除和更新SQL才会用到这个,因为一次只能删除或更新一张表。- SELECT u.id, u.name
- FROM userinfo u
- RIGHT JOIN (SELECT * FROM dept WHERE dept.scope = 12) d ON u.dept_id = d.id
- RIGHT JOIN role r ON u.rid = r.id
- WHERE u.p = 1 AND role.scope = 12
复制代码 四、结束语
该类主要为其他业务类提供SQL解析能力,本类代码实现有很多值得学习和鉴戒之处,而且基本严谨的考虑到了所有的情况,解析SQL时,对查询的解析较为复杂,分很多步骤,因为查询语句可以写的很复杂来满足业务的需要,但是对删除和修改的解析就很简单了,因为MyBatis-Plus的插件在追加条件时基本没有对修改后的值是子查询的情况进行处理,仅仅处理针对update本身的where条件,这一点后面的系列文章也许还会做进一步分析。
繁忙的工作中抽时间阅读并DEBUG贯通该类源码,并大抵理解源码的含义并再形成本文大概花了20天左右,感觉对自己的提拔还是很大的,而且能够做到使用这个类提供的功能时胸有定见,做到开发时尽可能不去编写不支持的SQL写法,假如遇到一些题目时,也能大抵猜到题目出如今哪了。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |