文章内容输出来源:拉勾教育Java高薪训练营;
系列文章连接:
MyBatis框架全面解析(二)如何手写一个简易的mybatis框架
前言
Mybatis提供了一种简单易用得插件扩展机制,我们可以基于此拦截mybatis四大核心对象,从而进行功能扩展。
一、基本原理
Mybaits 存在四大核心对象:
Executor 执行器
StatementHandler SQL语法构建起
ParameterHandler 参数处理器
ResultSetHandler 结果集处理器
这四大对象创建时,底层都调用了interceptorChain.pluginAll()得方法,该方法内部调用了interceptor.plugin()。
而这个Interceptor就是mybatis允许我们自定义得拦截器类,很容易想到,我们可以在plugin方法中,对传入得对象进行代理,然后就能对指定得方法进行增强处理。
mybatis得插件机制正是基于jdk动态代理+责任链模式完成得
二:使用示例
@Intercepts({
@Signature(type = StatementHandler.class,//准备拦截得对象类
method = "prepare",//对象方法
args = { Connection.class, Integer.class}),//方法有重载,故需要指定参数来进行确定
})
public class ExampleInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//这里就是要增强得方法
System.out.println("插件已执行");
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
//为目标对象创建代理
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
//可以读取插件配置得属性
System.out.println("属性"+properties);
}
}
配置文件:
<plugins>
<plugin interceptor="com.lagou.interceptor.ExampleInterceptor">
<property name="column" value="deleted"></property>
</plugin>
</plugins>
此时,StatementHandler实现类只要执行指定得方法,就可以进入我们得方法体内。
三、手写插件
在了解插件得基本原理后,我们就可以尝试自己实现。
在实际生产中,经常有这样得需求,表删除得时候不能物理删除,而是需要进行逻辑删除。那么我们可以实现这样一个功能,拦截delete语句,将其替换为指定得update语句。
1.配置,指定需要更新得字段名,我们默认将该字段更新为1来代替delete语句
<plugins>
<plugin interceptor="com.lagou.interceptor.MyInterceptor">
<property name="column" value="deleted"></property>
</plugin>
</plugins>
2.插件实现:
@Intercepts({
@Signature(type = Executor.class,//delete原本需要走到Executor得update方法,我们再次将其进行拦截
method = "update",
args = { MappedStatement.class,Object.class}),
})
public class MyInterceptor implements Interceptor {
//接收配置得逻辑删除字段
Properties properties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//我们得目标是替换sql语句,该语句存储在BoundSql里面,接下来尝试替换
Object[] args = invocation.getArgs();
MappedStatement statement = (MappedStatement)args[0];
SqlSource sqlSource = statement.getSqlSource();
BoundSql boundSql = sqlSource.getBoundSql(args[1]);
String sql = boundSql.getSql();
System.out.println("替换前sql"+sql);
//判断该方法是不是delete方法
if (SqlUtil.isDelete(sql)) {
//因为MappedStatement、SqlSource、BoundSql都不支持对属性进行修改。故我们只能重新建立对象,将其完全替代
//替换新的mapperStatement
args[0]=newStatement(statement,boundSql);
}
return invocation.proceed();
}
private MappedStatement newStatement(MappedStatement ms,BoundSql boundSql) {
//将delete语句转化为update语句
String sql = SqlUtil.parseSql(boundSql.getSql(), properties.getProperty("column"));
SqlSource newSqlSource = new StaticSqlSource(ms.getConfiguration(), sql, boundSql.getParameterMappings());
String id = ms.getId()+"myInterceptor";
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), id, newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
public class SqlUtil {
public static Boolean isDelete(String sql){
return sql.toLowerCase().startsWith("delete");
}
public static String parseSql(String sql,String column){
String[] s = sql.split(" ");
int tmp = 0;
StringBuilder sb = new StringBuilder();
for (int i=0;i<s.length;i++){
if (s[i].toLowerCase().equals("where")){
tmp = i;
break;
}
}
sb.append("update ").append(s[tmp-1]).append(" set "+column+" = 1 ");
for (int j=tmp;j<s.length;j++){
sb.append(s[j]).append(" ");
}
return sb.toString();
}
}
执行测试:
public class MybatisDemoTest {
@Test
public void test() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
mapper.delete(1);
}
}
测试结果:
替换前sql delete from user where id = ?
19:54:24,999 DEBUG JdbcTransaction:137 - Opening JDBC Connection
19:54:25,179 DEBUG PooledDataSource:406 - Created connection 2032169857.
19:54:25,180 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@79207381]
19:54:25,183 DEBUG deletemyInterceptor:159 - ==> Preparing: update user set deleted = 1 where id = ?
19:54:25,208 DEBUG deletemyInterceptor:159 - ==> Parameters: 1(Integer)
19:54:25,214 DEBUG deletemyInterceptor:159 - <== Updates: 1
回顾之后,我们可以发现,重点就在于mapperStatement得再构建,该类得属性都无法修改,只能重新创建。
附:
通用mapper以及pageHelper插件同样基于此思想,他们都对Executor得方法进行了拦截,替换MapperStatement。