Mybatis源码分析之binding 模块分析


为什么使用mapper接口就能对数据库进行访问?


在回答这个问题之前我们先看一下Mybatis编程方式与传统的Ibatis的区别 :

 @Test
    // ibatis编程模型 本质分析
    public void originalOperation() throws IOException {
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.执行查询语句并返回结果 通过nameSpace 加id来确认接口
        TUser user = sqlSession.selectOne("com.enjoylearning.mybatis.mapper.TUserMapper.selectByPrimaryKey", 2);
        System.out.println(user.toString());
    }
    //mybatis编程方式 
    @Test
    public void testAutoMapping() throws IOException {
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.获取对应mapper
        //sqlSession到底把请求转发给谁?
        //怎么找到相对应的id ?
        //怎么传参数的 ?
        //给你一个接口,返回一个实现类
        TUserTestMapper mapper = sqlSession.getMapper(TUserTestMapper.class);
        // 4.执行查询语句并返回多条数据
        List<TUser> users = mapper.selectAll();
        for (TUser tUser : users) {
            System.out.println(tUser);
        }
    }

在这里插入图片描述


一、与Ibatis编程模型的对比?

我们可以看到原生的Ibatis的模型 需要通过namespane+id 然后传入参数才可以让sqlSession去执行sql的 那我们日常使用的时候而是直接通过接口直接调用方法的 那么问题就出来了我们又没有写mapper的实现类,mybatis到底是怎么实现它的实现类并且调用Ibatis的模式的呢?

SqlSession 是 MyBatis 对外提供数据库访问最主要的 API,但是因为直接使用SqlSession 进行数据库开发存在代码可读性差、可维护性差的问题,所以我们很少使用,而是使用 Mapper 接口的方式进行数据库的开发。表面上我们在使用 Mapper 接口编程,实际上MyBatis 的内部,将对Mapper 接口的调用转发给了 SqlSession,这个请求的转发是建立在配置文件解读、动态代理增强的基础之上实现的,实现的过程有三个关键要素:

  • 找到SqlSession 中对应的方法执行;
  • 找到命名空间和方法名(两维坐标)
  • 传递参数
    要实现上述的步骤,必须对 bindling 模块有深入的分析;

二、对Ibatis进行怎么样封装的?以及核心类

1.哪些类是干什么的?在什么地方被加载的 ?

在这里插入图片描述

  • MapperRegistry:mapper 接口和对应的代理对象工厂的注册中心;
  • MapperProxyFactory:用于生成mapper 接口动态代理的实例对象;保证Mapper 实例对象是局部变量;
  • MapperProxy:实现了 InvocationHandler 接口,它是增强 mapper 接口的实现;
  • MapperMethod:封装了 Mapper 接口中对应方法的信息,以及对应的 sql 语句的信息;它是 mapper 接口与映射配置文件中 sql 语句的桥梁; MapperMethod 对象不记录任何
  • 状态信息,所以它可以在多个代理对象之间共享;MapperMethod 内几个关键数据结构:
  • SqlCommand : 从configuration 中获取方法的命名空间.方法名以及 SQL 语句的类型;
  • MethodSignature:封装 mapper 接口方法的相关信息(入参,返回类型);
  • ParamNameResolver: 解析mapper 接口方法中的入参,将多个参数转成 Map;

代码入口类:

org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
 @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
  	}

我们可以看到它是从configuration里面的mapperRegistry 获取的mapper接口的
那么问题来了,这个mapper接口到底是什么时候注册进来的?
答案是这个类其实是在加载的时候就已经配置进来的

//org.apache.ibatis.builder.xml.XMLMapperBuilder#parse 
 public void parse() {
        //判断是否已经加载该配置文件
        if (!configuration.isResourceLoaded(resource)) {
            //处理mapper节点
            configurationElement(parser.evalNode("/mapper"));
            /*将mapper文件添加到configuration.loadedResources中*/
            configuration.addLoadedResource(resource);
            /*注册mapper接口 找到Class类与 xml文件所处的位置 重点分析 */
            bindMapperForNamespace();
        }
        //处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //处理解析失败的CacheRef节点
        parsePendingCacheRefs();
        //处理解析失败的Sql语句节点
        parsePendingStatements();
    } 
  //  org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace 
  //通过这个方法来进行注册mapper的 

我们可以看到在config中有一个类记录了mapper接口与对应MapperProxyFactory之间的关系

/*mapper接口的动态代理注册中心*/
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this); 
  
  private final Configuration config;//config对象,mybatis全局唯一的
  //记录了mapper接口与对应MapperProxyFactory之间的关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

但是这个MapperProxyFactory是什么呢?用于生成mapper 接口动态代理的实例对象;保证Mapper 实例对象是局部变量

  //mapper接口的class对象
  private final Class<T> mapperInterface;
//key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  ....

我们可以看到 有一个MapperMethod类 还有MethodSignature 两个内部类 ,可以大概看看这个类是干什么的

//org.apache.ibatis.binding.MapperMethod.SqlCommand
//org.apache.ibatis.binding.MapperMethod.MethodSignature
	//从configuration中获取方法的命名空间.方法名以及SQL语句的类型
    private final SqlCommand command;
    //封装mapper接口方法的相关信息(入参,返回类型);
    private final MethodSignature method;
	 //是从哪儿实例化的?也就是实例化过程,也是在加载阶段来存到config里面对象的 ,是通过这个构造方法来实例化的
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
    }
     public static class MethodSignature {
        /**
         * 返回类型  接口
         * List<TUser> selectAll();
         */
        private final boolean returnsMany;//返回参数是否为集合类型或数组
        private final boolean returnsMap;//返回参数是否为map
        private final boolean returnsVoid;//返回值为空
        private final boolean returnsCursor;//返回值是否为游标类型
        private final boolean returnsOptional;//返回值是否为Optional
        private final Class<?> returnType;//返回值类型 (也就是我们返回具体参数)
       	..... 
       }

好到现在我们 已经了解了它3大步骤的来源 那么久可以从源码层面来了解它是怎么获取的了

org.apache.ibatis.binding.MapperRegistry#getMapper  调用了 
	->org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession) 

  public T newInstance(SqlSession sqlSession) {
	 //每次调用都会创建新的MapperProxy对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  } 
  ->
    protected T newInstance(MapperProxy<T> mapperProxy) {
	//创建实现了mapper接口的动态代理对象 与 通过动态代理来生成一个对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  -->>org.apache.ibatis.binding.MapperProxy#invoke 
  -->org.apache.ibatis.binding.MapperMethod#execute 

动态代理传入了MapperProxy对象 里面的invok方法就是对代理进行增强的
在invok里面最终进行了方法的调用: 重要的代码入下 : 用来区分调用Ibatis的哪一些方法

  Object result;
        //对三部翻译进行执行
        //根据sql语句类型以及接口返回的参数选择调用不同的方法 翻译完之后在判断去执行哪一个方法
        //应该把接口给返回给谁呢?
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {//返回值为void
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {//返回值为集合或者数组
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {//返回值为map
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {//返回值为游标
                    result = executeForCursor(sqlSession, args);
                } else {
                    //处理返回为单一对象的情况
                    //通过参数解析器解析解析参数
                    //第三部翻译 入参转化成mapo
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                            (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = OptionalUtil.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;

从SqlSession.getMapper(Class)方法开始跟踪,画出 binding 模块的时序图如下所示:
在这里插入图片描述

总结

在 binding 模块的运行流程中实现三步翻译的核心方法是 MapperMethod.execute(SqlSession, Object[]),翻译的过程描述如下:

  • 通过Sql 语句的类型(MapperMethod.SqlCommand.type)和 mapper 接口方法的返回参(MapperMethod.MethodSignature.returnType)确定调用 SqlSession 中的某个方法;
  • 通过MapperMethod.SqlCommand.name 生成两维坐标;
  • 通过 MapperMethod.MethodSignature.paramNameResolve 将传入的多个参数转成Map 进行参数传递;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值