前言
在 mybatis 中自写插件,需要先了解SqlSession下的四大对象、Interceptor接口以及工具类MetaObject 。
SqlSession下的四大对象
Mybatis 根据映射器的 XML 文件的命名空间与接口的全路径相对应,sql与方法绑定起来,通过动态代理,让接口跑起来。然后采用命令模式,使用SqlSession接口的方法使它能够执行查询。
Mapper 执行的过程是通过Executor、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回。
- Executor 代表执行器,由它来调度 StatementHandler、ParameterHandler和ResultHandler等来执行对应SQL。
- StatementHandler 的作用是使用数据库的 Statement(PreparedStatement)执行操作。
- ParameterHandler 用于SQL 对参数的处理。
- ResultHandler 是进行最后数据集(ResultSet)的封装处理。
Interceptor 接口
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
Object plugin(Object target);
void setProperties(Properties var1);
}
- intercept 方法 :它将直接覆盖所拦截对象原有的方法,是插件的核心方法。参数为 Invocation 对象,通过它可以反射调用原来对象的方法。
- plugin 方法 : target 是被拦截对象,它的作用是给被拦截对象生成一个代理对象,并返回它。
- setProperties : 允许在 plugin 元素中配置所需参数,方法在插件初始化的时候就被调用了一次。
工具类MetaObject
在MyBatis中,四大对象给我们提供的 public 设置属性的方法很少,我们很难通过对象自身去操作相关的属性信息。但是有了 MetaObject 这个工具类我们就可以去读取或者修改四大对象的属性。
常用的方法有:
- SystemMetaObject.forObject(Object obj) : 获取MetaObject对象。
- Object getValue(String name) : 获取对象属性值。
- void setValue(String name, Object value) : 修改属性值。
玩具插件
功能:打印出要执行Sql的参数,并可对参数进行相关自定义修改。
@Intercepts({@Signature(type = StatementHandler.class, // 有四大对象,这里确定拦截的对象
method = "prepare", // 确定要拦截的方法 参数的改变要在prepare
args = {Connection.class, Integer.class} // 要拦截方法的参数
)})
public class ModifyParametersPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 取出被拦截对象
StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();
// 由于四大对象public的属性方法很少,MetaObject提供给我们特殊方法去修改四大对象的相关属性
MetaObject metaStmtHandler = SystemMetaObject.forObject(stmtHandler);
// 分离代理对象,从而形成多次代理,通过两次循环最原始的被代理类,mybatis使用的是JDK代理
while (metaStmtHandler.hasGetter("h")) {
Object object = metaStmtHandler.getValue("h");
metaStmtHandler = SystemMetaObject.forObject(object);
}
// 分离最后一个代理对象的目标类
while (metaStmtHandler.hasGetter("target")) {
Object object = metaStmtHandler.getValue("target");
metaStmtHandler = SystemMetaObject.forObject(object);
}
BoundSql boundSql = (BoundSql) metaStmtHandler.getValue("delegate.boundSql");
Object parameterObject = boundSql.getParameterObject();
System.out.println("parameterObject: " + parameterObject.toString());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping) parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 在parameterMapping中获取属性的名字
String propertyName = parameterMapping.getProperty();
Object value;
// 然后通过MetaObject 对设置 属性的值
MetaObject metaParameterObject = SystemMetaObject.forObject(parameterObject);
value = metaParameterObject.getValue(propertyName);
String sql = (String) metaStmtHandler.getValue("delegate.boundSql.sql");
// 此处可以对value进行各种操作,然后在重新赋值
if (sql.toLowerCase().contains(" like ")){
value = EscapeUtil.escapeChar(value.toString());
}
// 这个插件对mybatis的级联查询产生了冲突
metaParameterObject.setValue(propertyName,value);
System.out.println("name:" + propertyName);
System.out.println("value:" + value);
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}