Mybatis执行流程源码分析

概要:

       在单独使用Mybatis时通常需要以下几行代码:

public class Demo {
    public static void main(String[] args) {
        // 第一阶段:MyBatis的初始化阶段
        String resource = "mybatis-config.xml";
        // 得到配置文件的输入流
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 得到SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 第二阶段:数据读写阶段
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 找到接口对应的实现
            UserMapper userMapper = session.getMapper(UserMapper.class);
            // 组建查询参数
            User userParam = new User();
            userParam.setSchoolName("Sunny School");
            // 调用接口展开数据库操作

            for (int i = 0; i < 2; i++) {
                List<User> userList =  userMapper.queryUserBySchoolName(userParam);
                System.out.println(userList);
            }
            // 打印查询结果
//            for (User user : userList) {
//                System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
//            }
        }
    }
}

    mybatis完成一次查询的实现逻辑整体可以划为三部分:

  1.  解析配置文件生成全局配置 ConfigurationConfiguration包含Mapper接口的注册器,类型处理器,xml的操作语句的映射等等。 在new SqlSessionFactoryBuilder().build(inputStream) 中实现
  2. 创建SqlSession获取Mapper接口的代理类。
  3. 最终交给Executor执行器进行参数解析,数据库操作和结果集映射,返回结果等操作。

下面以上面三块逻辑对mybatis执行流程以debug方式进行详细解析。

 

一. 全局配置的解析生成

   SqlSessionFactoryBuilder的build方法获取配置文件的输入流,然后调用XMLConfigBuilder的parse方法解析生成一个Configuration全局配置对象。

  从配置文件的根节点 /configuration开始解析

 

此方法完成Configuration对象的组装,包含了我们数据库的配置,默认的事务处理器,别名等环境信息,后重点看下mapperElement方法,它根据我们的配置把我们的Mapper接口保存到Configuration持有的MapperRegistry映射注册表中,如果配的是package会把包下所有接口都加载,我的配置是

<mappers>
    <mapper resource="com/demo/UserMapper.xml"/>    
</mappers>

所以debug的结果注册表中只有一个UserMapper,

MapperRegistry类中有一个名为knownMappers的Map,key为Mapper接口的Class对象,value为mapper接口的代理工厂。

 

MapperRegistry映射注册表的属性

看下这行代码干了啥 configuration.addMappers(mapperPackage);

调用的是MapperRegistry映射注册表的addMappers方法,继续点进去,里边完成的操作很简单,扫描包下符合条件的接口,然后添加到knownMappers属性中。最终MapperRegistry注册表中就包含了我们需要的所有Mapper接口映射。

 

二. 获取Mapper接口的代理对象

       1.   获取数据库环境配置,创建默认的事务工厂,创建默认执行器,这里看下红色框框部门,可以看到CachingExecutorSimpleExecutor包装了一下,显然是用了装饰器模式,CachingExecutor实现了mybatis的一级缓存,但是在于spring集成的时候,一级缓存会失效哦, 这里不细说了。

      最后返回一个DefaultSqlSession对象

     2.Sqlsession创建好了,下一步就是获取Mapper的代理对象了

最终调用的是MapperRegistry的getMapper方法,从knownMappers中获获取代理工厂,然后调用newInstance方法创建代理对象

   可以看到 MapperProxy就是我们Mapper接口的代理对象, 学过java动态代理的都知道, 后边我们调用接口的方法实际会执行代理对象的invoke方法。 下面就开始分析Mybatis如何执行一次查询操作。

 

三.执行查询操作

     1. 调用userMapper的queryUserBySchoolName查询方法会走到MapperProxy的invoke方法中

     

 

MapperMethod是接口中的方法信息,包含了我们的方法类型是SELECT, INSERT 还是UPDATE,返回结果是集合、map 还是void等等, 它会缓存到Mapper代理对象的methodCache属性中。

然后带着查询参数调用MapperMethodexecute方法。

 

 

 该方法会根据你接口操作类型走不同的分支进行处理,我用的是一个查询语句类型是SELECT,并且返回的是一个集合,所以会走到executeForMany方法中

后边调用的是BaseExecutor中的query方法,先尝试从缓存获取结果,获取不到调用queryFromDatabase方法去数据库查

之后又调用doQuery方法,带do就是要真正执行查询操作去了

 

走到这一步可以看到sql还是带 ? 占位符的,这时参数还没映射到sql中,

可以看到经过prepareStatement方法后,此时sql中占位符已经填充好参数了,所以prepareStatement方法是用来处理参数映射的,这个后面再说。  然后下一步就是调用JDBC中PreparedStatement对象来执行数据库查询操作,ResultHandler来处理结果集映射将结果返回给我们,结果集映射挺复杂的,处理各种resultMap,嵌套查询,多结果集处理,还可以自定义类型处理器,延迟加载等很多特性,就不一一介绍了。

 

 

最后打印了两遍我的查询结果,发现两次查询得到的是用一个对象,说明了啥子? 说明用到了一级缓存。

 

 参数映射

我的查询例子和sql

 

 

prepareStatement中调用StatementHandler的parameterize方法,最终调用DefaultParameterHandler进行参数映射,由于我的参数是一个User对象,舒服复杂类型参数,所以会背MetaObject包装,它的作用就是实现对包装对象进行反射操作的一些工具方法,如debug所示就获取到了User对象schoolName属性值 ”Sunny School“,

 

最后调用JDBC中的PreparedStatement对象给sql语句的参数赋值了

 

 

结束语

  下班回家花了三个小时写了下mybatis源码,这是老夫第一次写文章,不管有木有人看,就当是我学习生涯的一个记录吧。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值