zoukankan      html  css  js  c++  java
  • Mybatis源码(三)

    三、获取Mapper对象

    在老的版本中,DefaultSqlSession的selectOne()方法可以直接根据Mapper.xml中的StatementID,找到SQL执行。但是这种方法属于硬编码,不以查找和修改。

    并且如果是参数传入错误,在编译阶段也是不会报错的,不利于提前发现问题。

    User user = (User)session.selectOne("com.xx.mapper.UserMapper.selectUserById", 1);
    

    在Mybatis后期的版本中提供了第二种调用方式,就是定义一个接口,然后再调用Mapper接口的方法。

    我们定义的接口名称和Mapper.xml的namespace是对应的,接口的方法跟statementID也都是相对应的,所以根据方法就能找到对应的要执行的SQL。

    UserMapper mapper = session.getMapper(UserMapper.class);
    

    QA:

    1.getMapper获得的是一个什么对象?为什么可以执行它的方法?
    
    2.到底是怎么根据Mapper找到XML的SQL执行的?
    
    1、getMapper()

    DefaultSqlSession的getMapper()方法,调用了Configuration的getMapper方法。

     public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
      }
    

    Configuration的getMapper()方法,又调用了MapperRegistry的getMapper()方法。

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    

    在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个Map中。获取Mapper代理对象,实际是从Mapper中获取对应的工厂类后,调用一下方法创建对象:

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
    

    在newInstance()方法中,先创建MapperProxy。

    MapperProxy实现了InvocationHandler接口,主要属性有三个sqlSession、mapperInterface、methodCache

      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
    
    

    最终通过JDK动态代理模式创建、返回代理对象:

      protected T newInstance(MapperProxy<T> mapperProxy) {
        // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    

    也就是说,getMapper()返回的是一个JDK动态代理对象(类型是$Proxy数字)。这个代理对象会继承Proxy类,实现被代理的接口,里面持有一个MapperProxy类型的触发管理类。

    QA:为什么要在MapperRegistry中保存一个工厂类?

    创建返回代理类,这是一个非常经典的应用。

    QA:为什么要直接代理一个接口呢?

    2、MapperProxy如何实现对接口的代理

    我们知道,JDK的动态代理,有三个核心角色:被代理类(实现类)、接口、实现了InvocationHandler的触发管理类,用来生成代理对象。

    被代理类必须实现接口,因为要通过接口获取方法,而且代理也要实现这个接口。

    但是Mybatis里面的Mapper没有实现类,怎么被代理?他忽略了实现类,直接对接口进行代理。

    Mybatis的动态代理:

    那么Mybatis里面,动态代理为什么不需要实现类?

    我们这里的目的是根据一个可以执行的方法,直接找到Mapper.xml中的StatementID,方便调用。

    根据接口类型+方法的名称找到StatementID这个逻辑在Handler类(MapperProxy)中就可以完成,所以这里也就不需要实现类了。

    总结

    获得Mapper对象的过程,实质上是获得了一个JDK动态代理对象(类型是$Proxy数字)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。

    获取代理对象

    image

  • 相关阅读:
    CodeForces.1174D.EhabandtheExpectedXORProblem(构造前缀异或和数组)
    HDU-6187.DestroyWalls(最大生成树)
    HDU.6186.CSCource.(前缀和数组和后缀和数组)
    <每日一题>Day 9:POJ-3281.Dining(拆点 + 多源多汇+ 网络流 )
    <每日一题> Day8:CodeForces-996A.Hit the Lottery(贪心)
    最小割 + 网络流变体
    <每日一题> Day7:CodeForces-1166C.A Tale of Two Lands (二分 + 排序)
    <每日一题> Day6:HDU递推专题完结
    <每日一题> Day5:简单递推两题
    POJ-3122.Pie(二分法最大化平均值)
  • 原文地址:https://www.cnblogs.com/snail-gao/p/13254679.html
Copyright © 2011-2022 走看看