插件(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 的行为,务请慎之又慎。
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截Sql语法构建的处理
拦截器使用
工作中遇到一个需求,为服务的数据表增加逻辑删除功能,服务本身技术栈为(SpringBoot+Mybatis)当时自己考虑到了两个方法:
- 将项目的Mybatis升级到Mybatis-Plus,然后增加逻辑删除字段
- 编写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;
}
引用