一、动态改变SQL运行的参数
我们可以在目标方法放行前后,做非常多的事情,以到达动态修改 MyBatis 的运行流程。
在上面的插件开发基础上,当我们测试要查询id为1号的员工时,实际从数据库查询3号员工。
插件的实现:
/**
* 完成插件签名
* 告诉MyBatis当前插件用用来拦截哪个对象的哪个方法
*/
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
}
)
public class MyFirstPlugin implements Interceptor {
/**
* intercept:拦截
* 拦截目标对象的目标方法的执行。
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin...intercept:" + invocation.getMethod());
//动态的改变一下SQL运行的参数,以前查询1号员工,实际从数据库查询3号员工
System.out.println("当前拦截到的对象:" + invocation.getTarget());
//拿到 PreparedStatementHandler ===> ParameterHandler ===>parameterObject
Object target = invocation.getTarget();
// 拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql 语句用的参数是:" + value);
//修改完SQL语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", "3");
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* plugin:包装
* 包装目标对象的;包装,为目标对象创建一个代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MyFirstPlugin...plugin:MyBatis将要包装的对象" + target);
//借助Plugin的 wrap 方法来使用当前Interceptor 包装我们目标对象
Object wrap = Plugin.wrap(target, this);
//返回当前 target 创建的动态代理
return wrap;
}
/**
* setProperties:
* 将插件注册时 property 属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息" + properties);
}
}
测试代码:
@Test
public void test1() throws IOException {
//1、获取 sqlSessionFactory
SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
//2、获取 sqlSession 实例,能直接执行已经映射的 SQL 语句
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//3、获取接口的实现类对象
/**
* 推荐使用
* 会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
*/
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
} finally {
sqlSession.close();
}
}
运行结果:
可以看到在测试程序中传入的参数是1,我们在插件中手动修改为了 3,MyBatis在传递参数的时候是插件中的值,而且也生效了。
分析:如何修改参数的?
① 首先来看 StatementHandler 接口中的 parameterize() 方法,这是我们要拦截的方法,这也是来给参数赋值的方法;
② 然后是到 RoutingStatementHandler 里面的 方法,具体是由 delegate 来执行的:
在它的构造器里面来给 delegate 赋值,我们用的默认都是 PREPARED,
③ 再看 PreparedStatementHandler 的 parameterize() 方法
可以看到具体调用的是 parameterHandler的方法。
④ ParameterHandler 接口中的方法:
⑤ 默认的是实现是 DefaultParameterHandler 类:
在ParameterHandler 中有以下的成员变量信息,
获取如果我们要修改参数的时候,直接获取 paramterObject 对象,然后再重新赋值即可。
注意:在使用插件时,一定要谨慎修改,因为这里可以触及到 MyBatis 的底层原理。