Java Mybatis

目录

2022-04-25-09-34-36-314079.png

什么是插件?

插件就是在具体的执行流程插一脚(触发点、拦截器)来实现具体的功能。
一般插件会对执行流程中的上下文有依赖,抽象的说,也可以把MyBatis看作是JDBC的插件,只是功能越来来多,越来越强大,最后给了他一个新名字,叫做框架
不管怎样,JDBC的那一套还是不会变的,只是做了抽象、封装、归类等。
要想知道插件的原理,首先就要对它的执行流程有一定的把控。

执行流程

前边说到MyBatis是对JDBC的抽象、封装。
首先来回顾一下JDBC的执行流程。

JDBC执行流程

  1. 注册驱动;
  2. 获取Connection连接;
  3. 执行预编译;
  4. 执行SQL;
  5. 封装结果集;
  6. 释放资源;

给段伪代码通透理解下:

  1. // 注册驱动
  2. Class.forName("com.mysql.jdbc.Driver");
  3. // 获取链接
  4. Connection con = DriverManager.getConnection(url, username, password);
  5. // 执行预编译
  6. Statement stmt = con.createStatement();
  7. // 执行SQL
  8. ResultSet rs = stmt.executeQuery("SELECT * FROM ...");
  9. // 封装结果
  10. while (rs.next()) {
  11. String name = rs.getString("name");
  12. String pass = rs.getString(1); // 此方法比较高效
  13. }
  14. // 释放资源
  15. if (rs != null) { // 关闭记录集
  16. }
  17. if (stmt != null) { // 关闭声明
  18. }
  19. if (conn != null) { // 关闭连接对象
  20. }

上边的代码是不是很熟悉,相信每个入门写Java代码的人,都写过这段代码。
紧接着,继续来了解MyBatis的执行流程。

MyBatis执行流程

  1. 读取MyBatis的核心配置文件;
  2. 加载映射文件;
  3. 构造会话工厂获取SqlSessionFactory
  4. 创建会话对象SqlSession
  5. Executor执行器;
  6. MappedStatement对象;
  7. 输入参数映射;
  8. 封装结果集;

上边的文字可能不太好理解,这里也画一幅执行流程图,来方便理解。
Fcant_2022-04-25_10-03-54.png
有没有觉得MyBatis的执行流程和JDBC的执行流程主干也差不多,只是在主干过程中,把一些配置(mybatis-config.xml)、常用的定义文件单独抽离出来(mapper.xml)和一些附带扩展性的拦截点抽离了出来。
下面着重讲一讲拦截点,因为插件就是基于拦截点来做的扩展。

拦截点

结合上边的MyBatis执行流程,看下图的各个拦截点:
Fcant_2022-04-25_10-04-18.png
文字描述,MyBatis允许使用插件来拦截的方法调用包括:

  1. Executor:拦截执行器的方法 (**update, query, flushStatements, commit, rollback,getTransaction, close, isClosed**),Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,也是可以通过插件来实现自定义的二级缓存的;
  2. ParameterHandler:拦截参数的处理 (**getParameterObject, setParameters**) ,Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里可以使用插件来实现对一级缓存的操作(禁用等等);
  3. ResultSetHandler:拦截结果集的处理 (**handleResultSets, handleOutputParameters**) ,Mybatis实现Sql入参设置的对象。插件可以改变Sql的参数默认设置;
  4. StatementHandler:拦截Sql语法构建的处理 (**prepare, parameterize, batch, update, query**) ,Mybatis把ResultSet集合映射成POJO的接口对象。可以定义插件对Mybatis的结果集自动映射进行修改。

    拦截器为什么能够拦截

    org.apache.ibatis.session.Configuration是在MyBatis初始化配置的类。
    其中的newParameterHandlernewResultSetHandlernewStatementHandlernewExecutor这几个方法在创建指定的对象(newParameterHandler创建ParameterHandlernewResultSetHandler创建ResultSetHandlernewStatementHandler创建StatementHandlernewExecutor创建Executor)对象的时候,都会调用一个统一的方法:
    2022-04-25-09-34-36-698545.png
    这4个方法实例化了对应的对象之后,都会调用interceptorChainpluginAll方法,那么下面再来看pluginAll干了什么。
    包路径:org.apache.ibatis.plugin.InterceptorChain

    1. public Object pluginAll(Object target) {
    2. Interceptor interceptor;
    3. for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
    4. interceptor = (Interceptor)var2.next();
    5. }
    6. return target;
    7. }

    原来这个pluginAll方法就是遍历所有的拦截器,然后顺序执行插件的plugin方法,一层一层返回原对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。当调用四大接口对象的方法时候,实际上是调用代理对象的响应方法,代理对象又会调用四大接口对象的实例。
    这里看到所有的拦截器Interceptor,其实它和平常写代码一样,也是多态的利用,存在一个拦截器Interceptor接口,在实现插件的时候,也实现这个接口,就会被调用。

    Interceptor接口

    包路径:org.apache.ibatis.plugin

    1. public interface Interceptor {
    2. Object intercept(Invocation var1) throws Throwable;
    3. default Object plugin(Object target) {
    4. return Plugin.wrap(target, this);
    5. }
    6. default void setProperties(Properties properties) {
    7. }
    8. }

    这个接口只声明了三个方法:

  5. setProperties方法是在Mybatis进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置;

  6. plugin方法是插件用于封装目标对象的,通过该方法可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin.wrap(target, this)
  7. intercept方法就是要进行拦截的时候要执行的方法;

    编写简单的MyBatis插件

    注:MyBatis默认没有一个拦截器接口的实现类,开发者可以实现符合自己需求的拦截器
    1. @Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
    2. public class ExamplePlugin implements Interceptor {
    3. public Object intercept(Invocation invocation) throws Throwable {
    4. return invocation.proceed();
    5. }
    6. public Object plugin(Object target) {
    7. return Plugin.wrap(target, this);
    8. }
    9. public void setProperties(Properties properties) {
    10. }
    11. }
    全局xml配置(实例化bean)
    1. <plugins>
    2. <plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
    3. </plugins>
    这个拦截器拦截**Executor**接口的**update**方法(其实也就是SqlSession的新增,删除,修改操作),所有执行**executor****update**方法都会被该拦截器拦截到,就在里边做相对应的逻辑处理就可以了。

    总结

    分析了什么是插件首先需要了解执行流程,然后回顾JDBC流程来推导出MyBatis的执行流程,通过初始化的org.apache.ibatis.session.Configuration为切入点,跟踪到interceptorChainpluginAll方法;最后通过一个简单的插件来实际操作了一波。