MyBatis框架全面解析(四)mybatis插件原理+手写自定义插件delete转update

文章内容输出来源:拉勾教育Java高薪训练营;
系列文章连接:

MyBatis框架全面解析(一)为何我们需要mybatis

MyBatis框架全面解析(二)如何手写一个简易的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。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值