插件
- MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
- MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
- 默认情况下,MyBatis 允许使用插件来拦截的方法调用
包括:
• Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
• ParameterHandler (getParameterObject, setParameters)
• ResultSetHandler (handleResultSets, handleOutputParameters)
• StatementHandler (prepare, parameterize, batch, update, query)
插件开发
• 插件开发步骤
1)、编写插件类实现Interceptor接口,并使用@Intercepts注解完成插件签名
package com.xiaoqiu.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
/**
* @author 小邱
* @version 0.0.1
* @description MyFirstPlugin
* @since 2021/10/18 19:31
*/
//完成插件签名:告诉MyBatis当前插件用来拦截哪个对象的哪个方法
@Intercepts({@Signature(type= StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
public class MyFirstPlugin implements Interceptor {
//intercept:拦截:拦截目标对象的目标方法的执行
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
//plugin:包装目标对象的:包装:为目标对象创建一个代理对象
@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属性设置进来
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息:"+properties);
}
}
2)、在全局配置文件中注册插件
<plugins>
<plugin interceptor="com.xiaoqiu.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="root"/>
</plugin>
</plugins>
3)、测试
public class MyBatisTest {
public static SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
public static void main(String[] args) {
try {
EmployeeMapper mapper = getSqlSessionFactory().openSession().getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpById(10);
System.out.println(emp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
Interceptor接口
• Intercept:拦截目标方法执行
• plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
• setProperties:注入插件配置时设置的属性
插件原理
在四大对象创建的时候
1)、按照插件注解声明,按照插件配置顺序调用插件plugin方法,每个创建出来的对象不是直接返回的,而是生成被拦截对象的动态代理。
interceptorChain.pluginAll(parameterHandler);
2)、多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
3)、获取到所有的Interceptor(拦截器)(插件需要实现的接口),目标方法执行时依次从外到内执行调用interceptor.plugin(target)。返回target包装后的对象。
4)、多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值
5)、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
多个插件执行
添加第二个插件
package com.xiaoqiu.dao;
import java.util.Properties;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
@Intercepts({@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
public class MySecondPlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MySecondPlugin...intercept:"+invocation.getMethod());
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
System.out.println("MySecondPlugin...plugin:"+target);
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
System.out.println("MySecondPlugin--properties");
}
}
<plugin interceptor="com.xiaoqiu.dao.MySecondPlugin"></plugin>
测试代码不变,运行结果:
插件配置的信息:{password=root, username=root}
MySecondPlugin--properties
MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@396a51ab
MySecondPlugin...plugin:org.apache.ibatis.executor.CachingExecutor@396a51ab
MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@3e6fa38a
MySecondPlugin...plugin:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@3e6fa38a
MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@1bce4f0a
MySecondPlugin...plugin:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@1bce4f0a
MyFirstPlugin...plugin:mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@5c3bd550
MySecondPlugin...plugin:org.apache.ibatis.executor.statement.RoutingStatementHandler@5c3bd550
DEBUG 10-18 20:09:11,007 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (BaseJdbcLogger.java:145)
MySecondPlugin...intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
MyFirstPlugin...intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
DEBUG 10-18 20:09:11,069 ==> Parameters: 10(Integer) (BaseJdbcLogger.java:145)
DEBUG 10-18 20:09:11,118 <== Total: 1 (BaseJdbcLogger.java:145)
Employee [id=10, lastName=李四, email=lisi@qq.com, gender=1]
Process finished with exit code 0
案例:动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
package com.xiaoqiu.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.util.Properties;
@Intercepts({@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
public class MyThirdPlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到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;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
<plugin interceptor="com.xiaoqiu.dao.MyThirdPlugin"></plugin>
测试代码
public class MyBatisTest {
public static SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
public static void main(String[] args) {
try {
EmployeeMapper mapper = getSqlSessionFactory().openSession().getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpById(1);
System.out.println(emp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
DEBUG 10-18 20:27:58,037 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (BaseJdbcLogger.java:145)
当前拦截到的对象:org.apache.ibatis.executor.statement.RoutingStatementHandler@23348b5d
sql语句用的参数是:1
DEBUG 10-18 20:27:58,192 ==> Parameters: 3(Integer) (BaseJdbcLogger.java:145)
DEBUG 10-18 20:27:58,246 <== Total: 1 (BaseJdbcLogger.java:145)
Employee [id=3, lastName=jerry, email=jerry@qq.com, gender=0]
Process finished with exit code 0
运行原理: