MyBatis源码4_扩展运用

143 阅读5分钟

MyBatis扩展运用

到这里,MyBatis框架的从整个实现流程到关键源码分析,实现原理等学习研究基本上就已经完成了,其实还有很多值得研究,比如LanguageDriver的扩展、TypeHandler、缓存、日志等;MyBatis框架涵盖了开发一个Java框架的各方面,自己要开发框架脚手架可以参考学习。

就我个人而言,MyBatis的源码是相对简单的,这里的相对主要是指我以前看过的Spring,AQS、NIO...这些框架的源码,MyBatis的整个运行流程是一个串行的,基本上就可以分为两步。第一步,读取配置文件,扫描sql,将读取到的配置信息和sql信息转为对象,放入Configuration中。第二步,在运行过程中,从Configuration中根据配置信息,以及相应的组件生成代理对象、参数设置、调用JDBC执行sql、封装结果等操作。 总的来说,MyBatis的一个优秀的框架,它的源码逻辑清晰,扩展性强,并且基于它的在文件中编写sql的思想,也提供了更大的操作空间,衍生出了一些优秀的框架,如MyBatis Plus、Fluent-Mybatis、Mybatis-Generator...还有很多一些MyBatis的工具类框架,至少我在使用Hibernate的时候是没有衍生出来这么多个性化的框架的。

感慨发完了,接下来说说了解了原理我们能干点啥,首先说下我遇到过的场景,或者说奇葩需求:

读取mapper.xml中的sql语句:

我们曾经在技术探索上有这么一个要求,解析出项目中的所有sql语句,将其进行一些转换,或者分析等操作。

如离线(不连接数据库的情况下)分析出哪些模块关联操作了那些表,操作了那些字段等。

由于我们项目都是MyBatis的,因此sql语句都是写在了mapper.xml,但是mapper.xml中编写sql,由于mybatis的灵活性,可以使用各种标签,如include、sql、resultMap甚至还有动态标签if、where、forEach....这些根据运行时参数拼接sql的情况。使得要从复杂的mapper.xml中解析出来完整的sql非常困难,这时候我们就可以考虑完全走MyBatis的逻辑,让它帮我们解析,然后我们只需要拿到最后的解析结果即可,也就是运行时的BoundSql。

说一下解题思路:

由于我们只需要解析mapper.xml,我们在分析xml解析的时候知道,xml解析是从XMLMapperBuilder内部完成的,因此我们只需要扩展XMLMapperBuilder,并重写相关方法,剔除掉要跟数据库打交道的逻辑,按照这种思路,进一步重写MyBatis的其它有关mapper.xml解析的组件从而完成,不需要连接数据库,启动MyBatis的情况下完成mapper.xml解析。

还有就是针对动态标签的,如if、where、forEach...这种我们也只需要重新XxxSqlNode里面的apply方法,进行拼接sql。

贴一个我当时重写了的组件:

最后是一个调用demo

    @Test
    public void test() throws JSQLParserException, IOException, TransformerException {
        final String testFile = "D:\develop\project\" +
                "demo\sql-translate\sql-convert\src\main\" +
                "resources\mapper\SysConfigMapper.xml";
//        final String testFile = "D:\develop\project\demo\sql-translate\sql-convert\src\main\resources\mapper\SysUserOnlineMapper.xml";
        final String testId = "selectConfig";
        Resource resourceObj = ResourceUtil.getResourceObj(testFile);
        CustomConfiguration configuration = new CustomConfiguration();
        configuration.setDefaultScriptingLanguage(CustomXMLLanguageDriver.class);
        CustomXMLMapperBuilder builder = new CustomXMLMapperBuilder(
                resourceObj.getStream(),
                configuration,
                resourceObj.getName(),
                configuration.getSqlFragments()
        );
        builder.parse();
        MappedStatement ms = configuration.getMappedStatement(testId);
        BoundSql boundSql = ms.getBoundSql(null);
        String sql = boundSql.getSql();
        System.out.println(sql);  
    }

通过这种思路是能够借助MyBatis解析出mapper.xml中的sql的,我们拿到这些sql就可以进一步解析出sql中表字段等信息,可以借助JSqlParser框架。

手动注册MappedStatement

我们通过前面分析知道,MyBatis在执行sql的时候,最重要的依据就是MappedStatement,这个MappedStatement目前有两种生成方式,一种是通过注解的方式,一种就是通过mapper.xml写sql的方式。

我们遇到一个场景,现在我们有一个库,库里面有很多表,表的关系有复杂的关联关系,其它厂商想来查询表的数据,它们的查询方式没有固定的查询条件,现在就想要直接通过sql查询,并且有的表我们是不会开发出去的,要我们提供一个方案或者工具,首先不能连接数据库,框架就是MyBatis。

做法如下:

提供一个开发平台,由其它厂商开发者上传它们的mapper.xml,我们专门的开发进行审核,我们给它生成一个包含提交的mapper接口的代理jar包,下载下来引入到项目中,在查询的时候直接调用我们的jar包,在我们的jar包里面,通过rpc加密方式调用我们的查询操作。

由于没有固定查询方式,是以sql方式提交,因此传输过程中不包含sql信息,只会包含参数信息,在我们这一端会根据厂商上传的mapper.xml,进行动态注册到MappedStatement里面,从而完成这个需求。

这样做的好处,首先很方便调试,其它厂商只需要我们的表结构就可以本地调试,不需要网络互通。再一个就是非常灵活,如果按照传统rpc开发模式,我们提供商就需要针对不同厂商,提供查询接口,还得拼接查询条件,复杂的还得做各种关联条件等,但是它们又因为功能个性化,查询的五花八门的,引入这种模式后我们不用单独为他们提供查询接口,只需要审核一遍它们提交的xml文件,分析一下里面的sql语句即可(商业机密,低调,低调......)。