美文网首页
Mybatis核心源码浅析及拦截器原理

Mybatis核心源码浅析及拦截器原理

作者: 这个ID狠温柔 | 来源:发表于2019-11-13 10:42 被阅读0次

    Mybatis拦截器

    MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    2. ParameterHandler (getParameterObject, setParameters)
    3. ResultSetHandler (handleResultSets, handleOutputParameters)
    4. StatementHandler (prepare, parameterize, batch, update, query)

    使用方法

    1. 实现Interceptor接口,并指定想要拦截的方法签名args即可
    @Intercepts({@Signature(type = Executor.class,method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    })
    public class MyPageInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("将逻辑分页改为物理分页");
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0]; // MappedStatement
            BoundSql boundSql = ms.getBoundSql(args[1]); // Object parameter
            RowBounds rb = (RowBounds) args[2]; // RowBounds
            // RowBounds为空,无需分页
            if (rb == RowBounds.DEFAULT) {
                return invocation.proceed();
            }
        }
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
        @Override
        public void setProperties(Properties properties) {
        }
    }
    
    1. 全局xml相关配置
    <!-- 控制全局缓存(二级缓存)-->
    <setting name="cacheEnabled" value="true"/>
    <plugins>
        <plugin interceptor="com.eason.MyPageInterceptor"></plugin>
    </plugins>
    

    这个拦截器是拦截Executor实例中的query方法,这里的Executor是负责执行底层映射语句的内部对象。
    接下来就开始看源码啦。

    源码解析

    1. 编程式代码
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource); // 1
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 2
    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
    try {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        Blog blog = mapper.selectBlogById(1);
        System.out.println(blog);
    } finally {
        session.close();
    }
    

    首先看

    InputStream inputStream = Resources.getResourceAsStream(resource); // 1
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 2
    

    先获取配置文件输入流,传入build方法,build方法会根据配置信息生成DefaultSqlSessionFactory,


    1.png

    再看parser.parse(),解析过就不会再解析了,解析主要是在parseConfiguration(parser.evalNode("/configuration"));中,

    public Configuration parse() {
      if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      }
      parsed = true;
      parseConfiguration(parser.evalNode("/configuration"));
      return configuration;
    }
    

    解析各个节点信息并且保存下来


    2.png

    再看

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
    

    默认会创建new DefaultSqlSession(configuration, executor, autoCommit);


    3.png

    这里的executor是SIMPLE类型,并且在创建Executor的时候会在所有自定义的拦截器链中再包装一次(如果自定了对应的Executor的拦截器的话)


    4.png
    再来看interceptorChain.pluginAll(executor);这里的interceptors会在前面解析配置文件的时候放进自定义的拦截器,遍历自定义的拦截器,
    5.png

    会将当前executor包装一次,


    6.png
    实际会调用org.apache.ibatis.plugin.Plugin#wrap方法,这里的signatureMap是前面解析的自定义的拦截器Interceptor的注解信息(定义拦截的Executor,ParameterHandle,ResultSetHandler ,StatementHandler相关信息),如果定义了拦截executor的拦截器的话,会生成动态代理对象,Plugin实现了InvocationHandler接口,invoke方法中回去执行自定义拦截器的intercept方法。
    7.png
    再看
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    

    会从缓存的Map knownMappers中去拿到MapperProxyFactory对象,


    8.png

    这个knownMappers里面在前面解析配置文件的时候会以配置文件的namespace生成的class为key,MapperProxyFactory为value缓存起来。


    9.png
    mapperProxyFactory.newInstance(sqlSession);会返回Mapper的代理对象,
    10.png

    MapperProxy实现了InvocationHandler接口,所以执行mapper.selectBlogById方法是在MapperProxy对象的invoke方法中被代理执行,最后执行mapperMethod.execute(sqlSession, args);


    11.png
    mapper配置文件中id为selectBlogById的标签为select,并开启了二级缓存
    12.png
    所以执行,真正执行数据查询相关操作的是org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne方法
    13.png
    statement为配置文件中的namespace+id,
    14.png
    MappedStatement保存一个select/delete/update标签的所有内容,RowBounds默认为0到Integer.MAX_VALUE;
    15.png

    因为我开启了二级缓存,所以会先去二级缓存中去查找数据,先生存二级缓存的key


    17.png
    这个key主要是根据类名+方法名+limit+sql+参数+环境,随后才去真正执行查询操作
    18.png
    先去二级缓存查找,没有数据再去一级缓存中去查询(一级缓存默认开启)
    19.png
    一级缓存没有再去数据库查询
    20.png
    从数据库查询前先占位,再去执行数据库相关操作
    21.png

    重要的来了

    查询前先new StatementHandler对象,生成StatementHandler对象的过程中也会new ParameterHandler和ResultSetHandler对象,也会加入拦截器链中去,

    22.png
    默认PreparedStatementHandler
    23.png
    24.png
    同时会加入拦截器链
    25.png
    执行prepareStatement(handler, ms.getStatementLog()),这里面会去执行ParameterHandler拦截器
    26.png
    调用parameterize方法
    27.png
    最后去执行ParameterHandler(DefaultParameterHandler)的setParameters方法,这个方法可被拦截(如果自定义了ParameterHandler拦截器的话)
    28.png
    执行Handler.query(stmt,resultHandler)方法,先会执行 StatementHandler的拦截器
    29.png
    再去执行ResultSetHandler拦截器
    30.png
    放入一级缓存中去
    31.png
    再去执行executor拦截器
    32.png
    返回结果
    点个赞赞赞赞赞赞赞赞赞赞吧_

    相关文章

      网友评论

          本文标题:Mybatis核心源码浅析及拦截器原理

          本文链接:https://www.haomeiwen.com/subject/ushhictx.html