Mybatis源码解析-MapperRegistry注册mapper接口

来源:互联网 发布:淘宝客开源cms 编辑:程序博客网 时间:2024/05/19 10:36

知识储备

  1. SqlsessionFactory-mybatis持久层操作数据的根本,具体的解析是通过SqlSessionFactoryBean生成的,具体的形成可见>>>Spring mybatis源码篇章-SqlSessionFactoryBean
  2. MapperInterface-mybatis的java接口类,用于service/controller层的调用,具体的解析是通过MapperScannerConfigurer扫描接口并封装成MapperFactoryBean来注册生成,可见>>>Spring mybatis源码篇章-sql mapper配置文件绑定mapper class类
  3. 保存mybatis的各种信息都是由org.apache.ibatis.session.Configuration类来维护的

MapperRegistry

由于mybatis与数据库进行通信需要保证其java访问类必须为接口类,所以我们必须了解其是怎么保存这些接口类的访问方式,入口由Configuration#addMapper()方法调用,如下述MapperFactoryBean#checkConfig()源码

  @Override  protected void checkDaoConfig() {    super.checkDaoConfig();    notNull(this.mapperInterface, "Property 'mapperInterface' is required");    Configuration configuration = getSqlSession().getConfiguration();    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {      try {        //只关注此处的添加mapper接口        configuration.addMapper(this.mapperInterface);      } catch (Throwable t) {        throw new IllegalArgumentException(t);      } finally {        ErrorContext.instance().reset();      }    }  }

MapperRegistry#addMapper()-创建mapper访问代理

直接阅读源码

  public <T> void addMapper(Class<T> type) {    if (type.isInterface()) {      if (hasMapper(type)) {        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");      }      boolean loadCompleted = false;      try {        //将mapper接口包装成mapper代理        knownMappers.put(type, new MapperProxyFactory<T>(type));        //解析接口上的注解或者加载mapper配置文件生成mappedStatement        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);        parser.parse();        loadCompleted = true;      } finally {        if (!loadCompleted) {          knownMappers.remove(type);        }      }    }  }

看下关于mapper的代理如何生成

MapperProxyFactory

  • 内部属性概览

      //被代理类  private final Class<T> mapperInterface;  //支持对被代理类进行缓存  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  • 创建代理类

      @SuppressWarnings("unchecked")  protected T newInstance(MapperProxy<T> mapperProxy) {    //采用JDK自带的Proxy代理模式生成    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  }  public T newInstance(SqlSession sqlSession) {    //MapperProxy为InvocationHandler的实现类    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);    //真实生成代理    return newInstance(mapperProxy);  }

接下来阅读下MapperProxy是怎么运用反射调用mapper接口类的方法

MapperProxy

直接看对JDK的proxy代理的实现方法

      @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if (Object.class.equals(method.getDeclaringClass())) {          try {            return method.invoke(this, args);          } catch (Throwable t) {            throw ExceptionUtil.unwrapThrowable(t);          }        }        //尝试从缓存中获取,也就是看到的methodCache        final MapperMethod mapperMethod = cachedMapperMethod(method);        //通过MapperMethod对象调用方法        return mapperMethod.execute(sqlSession, args);      }        private MapperMethod cachedMapperMethod(Method method) {        MapperMethod mapperMethod = methodCache.get(method);        if (mapperMethod == null) {          //注意此处的传的参数为mapper接口类、method对象、Configuration对象          mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());          methodCache.put(method, mapperMethod);        }        return mapperMethod;      }

需要观察下MapperMethod是如何操作数据的

MapperMethod

  • 构造函数

      //Sql指令类  private final SqlCommand command;  //方法签名类  private final MethodSignature method;  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {    this.command = new SqlCommand(config, mapperInterface, method);    this.method = new MethodSignature(config, method);  }
  • 主要方法execute()

      public Object execute(SqlSession sqlSession, Object[] args) {Object result;//CURD操作,对持久层返回的结果集进行处理if (SqlCommandType.INSERT == command.getType()) {  //获取method方法上的带有@Param的参数,默认返回0,1,2,3...  Object param = method.convertArgsToSqlCommandParam(args);  //最终是通过sqlSession接口对象获取结果集,注意此处的command.getName()为mappedStatementId  result = rowCountResult(sqlSession.insert(command.getName(), param));} else if (SqlCommandType.UPDATE == command.getType()) {  Object param = method.convertArgsToSqlCommandParam(args);  result = rowCountResult(sqlSession.update(command.getName(), param));} else if (SqlCommandType.DELETE == command.getType()) {  Object param = method.convertArgsToSqlCommandParam(args);  result = rowCountResult(sqlSession.delete(command.getName(), param));} else if (SqlCommandType.SELECT == command.getType()) {  //查询语句的各种情况应对  if (method.returnsVoid() && method.hasResultHandler()) {    executeWithResultHandler(sqlSession, args);    result = null;  } else if (method.returnsMany()) {    result = executeForMany(sqlSession, args);  } else if (method.returnsMap()) {    result = executeForMap(sqlSession, args);  } else {    Object param = method.convertArgsToSqlCommandParam(args);    result = sqlSession.selectOne(command.getName(), param);  }} else {  throw new BindingException("Unknown execution method for: " + command.getName());}return result;  }
  1. SqlCommand是MapperMethod的静态内部类,主要通过mapperInterface和method从Configuration中获取MappedStatement对象保存其id和type属性,供sqlsession持久层接口调用

  2. MethodSignature是MappedMethod的静态内部类,主要对method对象的返回类型、参数等进行归类

  3. MapperMethod的作用是处理SqlSession接口调用CRUD操作后产生的结果集

MapperRegistry#getMapper()-获取mapper代理类

源码如下

  @SuppressWarnings("unchecked")  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 {      //获取mapper代理类      return mapperProxyFactory.newInstance(sqlSession);    } catch (Exception e) {      throw new BindingException("Error getting mapper instance. Cause: " + e, e);    }  }

那么获取代理类这个操作是如何被调用的呢?这其实是bean工厂对其里面所有的beanDefinition进行实例化调用的,这点可查看>>>Spring源码情操陶冶-AbstractApplicationContext#finishBeanFactoryInitialization。而针对FactoryBean接口的实例化会调用其中的getObject()方法,所以我们看下MapperFactoryBean#getObject()方法

  public T getObject() throws Exception {    //通过sqlSessionTemplate调用Configuration#getMapper()方法间接调用MapperRegistry#getMapper()    return getSqlSession().getMapper(this.mapperInterface);  }

小结

本文是对MapperFactoryBean#checkDaoConfig()方法的补充,通过对Mybatis的MapperRegistry的分析我们可以得出以下结论:

  • mapperInterface的内部方法数据持久层访问是通过JDK的代理来完成的

  • MapperMethod是对上述代理的method方法的真实处理,主要是对sqlSession的返回结果集进行对应的整理输出,具体读者可自行查阅分析

  • sqlSession的CRUD肯定牵扯到MappedStatement对象的使用,后续我们着重分析下SqlSessionTemplate的源码加深我们对mybatis的理解

阅读全文
0 0