Mybatis-Interceptor


插件(plugins)

MyBatis plugins

插件(plugins)

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的方法
  • ParameterHandler (getParameterObject, setParameters) 拦截参数的处理
  • ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理
  • StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理

官方文档地址

提示 覆盖配置类

除了用插件来修改 MyBatis 核心行为以外,还可以通过完全覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会极大影响 MyBatis 的行为,务请慎之又慎。

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

拦截器使用

工作中遇到一个需求,为服务的数据表增加逻辑删除功能,服务本身技术栈为(SpringBoot+Mybatis)当时自己考虑到了两个方法:

  1. 将项目的Mybatis升级到Mybatis-Plus,然后增加逻辑删除字段
  2. 编写Mybatis插件,将所有的查询语句增加状态值判断,将Delete语句转换为逻辑删除Update。

Mybatis-Plus官方说明中很重要的一点是逻辑删除”只对自动注入的 sql 起效”,也就是说XML中自定义的SQL不会自动拼接逻辑删除条件也不会将物理删除更改为逻辑删除。如果依然修改XML升级就没有意义。

于是考虑第二种方案,拦截SQL将delete语句修改为逻辑删除,在Select语句中添加逻辑删除判断。但是后来证明自己有明显的考虑不足之处。因为复杂嵌套SQL拼接where条件十分难搞。

package com.rrc.authority.base.interceptor;

import com.rrc.authority.base.utils.JsqlparserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class LogicDeleteInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
        // 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());

        // 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是			    BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
		// id为执行的mapper方法的全路径名,如com.cq.UserMapper.insertUser, 便于后续使用反射
        String id = mappedStatement.getId();
        // sql语句类型 select、delete、insert、update
        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
        
        // 数据库连接信息
        Configuration configuration = mappedStatement.getConfiguration();
        DataSource dataSource = configuration.getEnvironment().getDataSource();
        
        BoundSql boundSql = statementHandler.getBoundSql();
        // 获取到原始sql语句
        String sql = boundSql.getSql().toLowerCase();
        log.info("请求SQL语句 {}", sql);
       	// 具体处理逻辑
		//通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql);
        
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        // 空方法
    }
}

Jsqlparser解析SQL

从SQL中提取表名

String sql = "SELECT * FROM TABLE1";
Statement statement = CCJSqlParserUtil.parse(sql);
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> tableList = tablesNamesFinder.getTableList(statement);

提取SQL中的表别名


/**
 * 获取SQL中的全部表别名
 *
 * @param sql sql语句
 * @return String
 */
public static Map<String,String> getMainJoinTableAlias(String sql) throws JSQLParserException {
    Map<String,String> map = new HashMap<>();
    PlainSelect plainSelect = (PlainSelect)((Select) CCJSqlParserUtil.parse(sql)).getSelectBody();

    Table table = (Table)plainSelect.getFromItem();

    if(Objects.nonNull(table.getAlias())){
        map.put(table.getName(), table.getAlias().getName());
    }

    if (Objects.nonNull(plainSelect.getJoins())) {
        for (Join join: plainSelect.getJoins()) {
            table = (Table)join.getRightItem();
            if(Objects.nonNull(table.getAlias())){
                map.put(table.getName(), table.getAlias().getName());
            }
        }
    }

    return map;
}

引用

细粒度权限,用户只能看到自己相关的数据

mybatis-插件拦截器动态替换表名

关于JSqlparser使用攻略(高效的SQL解析工具)

mybatis

mybatis-spring-boot

https://www.cnblogs.com/yougewe/p/10072740.html 下次学习这个


文章作者: WangQingLei
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WangQingLei !
  目录