mybatis技术框架解读

最近打算走读一下mybatis源码,先通过spring+mybatis的项目来了解mybatis的整个框架。
整个流程以下三个部分来:

1. spring项目中mybatis实例化过程

sqlSessionTemplate实例化

上图描述了spring项目中,mybatis-spring中的sqlSessionTemplate实例化过程。sqlSessionTemplate它管理 session 的生命周期,包含必要的关闭,提交或回滚操作。保证当前sqlSession与spring当前事务是相关的。sqlSessionTemplate也是sqlSession接口的实现类。作为模版类,通过操作sqlsession其它的实现类完成一些功能,在此基础上做了封装。后续也会讲到,sqlsession实现类图如下:
这里写图片描述

sqlSessionTemplate是通过sqlSessionFactory来为当前线程产生sqlSession。sqlSessionFactory只是个接口。它的实现是DefaultSessionFacotry。正如图中所示就是DefaultSessionFacotry实例化过程,它是单例模式。sqlSessionFactory的实例过程,是通过工厂bean:sqlSessionFactoryBean来产生。在sqlSessionFactoryBean实例化时,通过解析mybatis配置文件,和mapper配置文件来生成configuration实例,然后传递到Builder类通过build模式生成。核心代码如下:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;
    //通过XMLConfigBuilder解析configLocation的文件并构建出Configuration实例,
    //Configuration几乎包含了所有mybatis配置,并且会注入到每个重要的mybatis类中。
    //因为它的配置会在很多类中不断的被用到。
    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), 
      null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    }
    //.......省略......
     //通过XMLMapperBuilder解析mapperLocations并将mapper.xml文件中的信息都保存至configuration中
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder=new XMLMapperBuilder(
              mapperLocation.getInputStream(), configuration, mapperLocation.toString(), 
              configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } //.......省略.....
    //最后通过sqlSessionFactoryBuilder产生DefaultSessionFacotry实例。
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

解析mapper.xml文件过程中,会把每一个mapper.xml文件中的方法以及该方法返回和参数信息都保存。通过一个map的形式存储,后续mapper映射器执行的时候可以定位到mapper.xml文件中的sql。解析过程可参考下面的方法:

org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

map结构:key是mapper.xml文件的namespace + “.” + methodName,value是一个MappedStatement.
MappedStatement属性如下:描述了mapper.xml文件中方法的所有属性。

 private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;  //sql语句
  private Cache cache;
  private ParameterMap parameterMap;  //参数map
  private List<ResultMap> resultMaps; //mapper.xml中的resultMap
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;  //sql操作类型,insert/select/delete/update
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

有趣的是sqlSessionTemplate返回SqlSession实例的方式也有点特别。它不是直接通过sqlSessionFactory工厂类来产生sqlSession。而是通过它内部一个属性sqlSessionProxy来返回。sqlSessionProxy是一个代理类,并且设置了拦截器SqlSqlsessionInterceptor。拦截器内部完成了sqlSessionTemplate职责中的事情–统一管理sqlsession生命周期。
sqlSessionProxy实例化代码:

this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());

SqlSessionInterceptor内容:当sqlsession执行相关方法的时候就会被拦截

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //通过sqlSessionFactory获取SqlSession实例,深入内部可以发现如果有事务则会返回这个事务相关的session
      //否则会new一个DefaultSqlSessionFactory实例。
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //这其实就是当映射器执行方法时会触发到的,后续来解释
        Object result = method.invoke(sqlSession, args);
        if(!isSqlSessionTransactional(sqlSession,SqlSessionTemplate.this.sqlSessionFactory)){
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
       .....sqlsession统一处理的操作
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }

2. mapper映射器创建过程

mapper映射器创建过程

spring项目中,在配置mybatis时,会配置MapperScannerConfigurer,实例化时会扫描做扫描,实例化所有被注解了MybatisDao的mapper映射器接口

//MapperScannerConfigurer配置示例:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.youzan.sz.beautystore.**.dao"/>
    <property name="annotationClass" value="com.youzan.beauty.helper.mybatis.MybatisDao"/>
</bean>

//实例化时扫描的代码
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    //....
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));//扫描注解,实例化mapper接口。
  }

我们代码中注入的mapper接口,没有实现类,其实它的实现是一个代理类。在扫描过程中会生成mapper接口的代理类注入。在扫描过程中,会调用MapperFactoryBean.getObject方法来生成mapper代理类。getObject内部时通过
sqlSessionTemplate .getMapper方法获取

@Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

如流程图所示。深入代码,发现mapper代理类会通过sqlSessionFactory.getConfiguration.getMapper最后在 MapperRegistry.getMapper内部获取到mapperProxyFactory代理工厂类,然后通过这个工厂类生成Mapper的代理类

//MapperRegistry.getMapper方法核心代码
@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 {
   return mapperProxyFactory.newInstance(sqlSession);
 } catch (Exception e) {
   throw new BindingException("Error getting mapper instance. Cause: " + e, e);
 }
}
//mapperProxyFactory.newInstance代码就是生成一个代理类,然后拦截器是MapperProxy实例
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new 
                  MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { 
    mapperInterface }, mapperProxy);
}

问题是mapperProxyFactory是什么时候被设置到Configuration下的MapperRegistry中knownMappers属性中的?回溯到原来Configuration的实例化和设置的过程可以逐渐解释。getMapper的参数是mapper接口的完整类名,例如:com.youzan.sz.beautyshop.message.dao.ShopMessageConfigDAO。knownMappers中的值是在XMLMapperBuilder.parse解析mapper.xml文件时被设置。

//org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace(); //获取到mapper.xml中的namespace全名
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);//通过namespace获取类
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);//设置的关键代码,可以进入下一个代码段看到下层代码
        }
      }
    }
  }

 //org.apache.ibatis.session.Configuration#addMapper方法
 public <T> void addMapper(Class<T> type) {
   mapperRegistry.addMapper(type);
 }
//mapperRegistry.addMapper方法的代码便是设置knownMappers
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 {
         //key是class,value是一个MapperProxyFactory
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

好,一切解释通了。mapper代理类生成过程在此结束。接下来就是当mapper接口调用某一方法时,查找sql并返回结果。

3. mapper映射器执行过程

mapper执行过程

当mapper接口调用某一方法时,会被MapperProxy拦截

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args); //通过操作类型最后会触发sqlsession中的方法
  }

以查询列表的方法sqlsession.selectList为例,最后会执行executor的query方法:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        //在第一部分中有讲到configuration中有一个map对象,会存储mapper.xml文件中的每一个方法,
        //和方法对应的出参入参相关对象MappedStatement,这里取出来之后,就相当取出了整个方法的sql语句和属性。
        //最后执行
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

最后配合statement处理器,参数处理器,结合jdbc操作返回结果,通过结果处理器,映射成java结果对象返回,一切完成。后面将继续深入其中细节~~未完待续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值