mybatis源码解析 - mapper代理对象的生成

Posted codingjav

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis源码解析 - mapper代理对象的生成相关的知识,希望对你有一定的参考价值。

1、简单示例

先看一个简单纯粹的mybatis demo(不集成spring等其他框架),代码结构很简单,如下图:

 

完整代码地址:kingoe/boot-study;mapper层和我们平时说的dao层指的是同一个内容,都是数据库操作的封装,但是在没有集成mybatis时,dao层的接口都是需要我们手动去写其实现类,可在上图中我们却发现:我们并没有手动去实现PersonMapper接口,但工程却能实实在在的查询数据库,获取我们需要的数据,debug代码可以发现 PersonMapper实例是一个代理对象,我们操作的其实是PersonMapper的代理实现;也就是说不用我们手动去实现PersonMapper接口,mybatis会动态生成PersonMapper的代理实例【MapperProxy$xxx类】,然后由代理实例完成数据库的操作。那么问题来了,mybatis是何时、何地、如何生成mapper代理实例的呢?我们接着往下看

2、源码分析

针对上述问题,从mybatis源码入手:

2.1、SqlSessionFactory创建

SqlSessionFactoryBuilder 通过指定 mybatis-config.xml 配置文件构建SqlSessionfactory,内部通过XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),将配置文件中各个属性解析到Configuration实例中,然后以Configuration实例构建SqlSessionFactory(实际是DefaultSqlSessionFactory);

class SqlSessionFactoryBuilder 
public SqlSessionFactory build(InputStream inputStream) 
  return build(inputStream, null, null);

........
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) 
  try 
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    return build(parser.parse());
   catch (Exception e) 
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
   finally 
    ErrorContext.instance().reset();
    try 
      inputStream.close();
     catch (IOException e) 
      // Intentionally ignore. Prefer previous error.
    
  

  
public SqlSessionFactory build(Configuration config) 
  return new DefaultSqlSessionFactory(config);

XMLConfigBuilder 内部通过 parse() 方法解析配置文件各个参数,其中parseConfiguration方法是解析的具体过程。

/**
 * 配置文件只能解析一次,多次解析会抛出异常
*/
public Configuration parse() 
  if (parsed) 
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;

/**
 * root是以configuration标签开始的文档树
 * 解析配置文件中的各个标签,并存放到Configuration实例对应的属性中
 * 解析完成之后,配置文件中的内容全部解析到了Configuration实例中
 * @param root
 */
private void parseConfiguration(XNode root) 
    try 
        //issue #117 read properties first
        propertiesElement(root.evalNode("properties"));                                // 解析配置文件中的properties标签
        Properties settings = settingsAsProperties(root.evalNode("settings"));        // 解析配置文件中的settings标签
        loadCustomVfs(settings);
        typeAliasesElement(root.evalNode("typeAliases"));                            // 解析配置文件中的typeAliases标签
        pluginElement(root.evalNode("plugins"));                                    // 解析配置文件中的plugins标签
        objectFactoryElement(root.evalNode("objectFactory"));                        // 解析配置文件中的objectFactory标签
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));            // 解析配置文件中的objectWrapperFactory标签
        reflectorFactoryElement(root.evalNode("reflectorFactory"));                    // 解析配置文件中的reflectorFactory标签
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631            
        environmentsElement(root.evalNode("environments"));                            // 解析配置文件中的environments标签
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));                // 解析配置文件中的databaseIdProvider标签
        typeHandlerElement(root.evalNode("typeHandlers"));                            // 解析配置文件中的typeHandlers标签
        mapperElement(root.evalNode("mappers"));                                    // 解析配置文件中的mappers标签
     catch (Exception e) 
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    

mapperElement(root.evalNode("mappers")); 中核心方法

public void parse() 
    if (!configuration.isResourceLoaded(resource)) 
      configurationElement(parser.evalNode("/mapper"));        // 解析映射文件Person.xml
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();    // 将mapper与namespace绑定起来; 将PersonMapper接口与MapperProxyFactory关联起来
    

    parsePendingResultMaps();    // 解析Configuration的incompleteResultMaps到Configuration的resultMaps
    parsePendingCacheRefs();    // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap
    parsePendingStatements();    // 解析Configuration的incompleteStatements到Configuration的mappedStatements


/**
 * context是映射文件:Person.xml的文档树,以mapper标签开始
 * 解析映射文件中的各个标签,并存放到MapperBuilderAssistant实例对应的属性中
 */
private void configurationElement(XNode context) 
    try 
      String namespace = context.getStringAttribute("namespace");            // 解析mapper标签的namespace属性
      if (namespace == null || namespace.equals("")) 
        throw new BuilderException("Mapper's namespace cannot be empty");
      
      builderAssistant.setCurrentNamespace(namespace);    // namespace属性值解析到Configuration的mapperRegistry中
      cacheRefElement(context.evalNode("cache-ref"));    // 解析cache-ref标签到Configuration的cacheRefMap中
      cacheElement(context.evalNode("cache"));            // 解析cache标签到Configuration的caches中
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));    // 解析parameterMap标签到Configuration的parameterMaps中
      resultMapElements(context.evalNodes("/mapper/resultMap"));        // 解析resultMap标签到Configuration的resultMaps中
      sqlElement(context.evalNodes("/mapper/sql"));                        // 解析sql标签到XMLMapperBuilder的sqlFragments中
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    // 解析select|insert|update|delete标签到Configuration的mappedStatements中
     catch (Exception e) 
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    

此时SqlSessionFactory已经创建,但PersonMapper的代理实例还没有创建;期间准备了很多东西,包括读取配置文件和映射文件的内容,并将其放置到Configuration实例的对应属性中

2.2、SqlSession创建

上述步骤一创建SqlSessionFactory后,通过 openSession() 方法获取 SqlSession对象,就可以进行数据库操作。

@Override
public SqlSession openSession() 
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);

........
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) 
  Transaction tx = null;
  try 
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
   catch (Exception e) 
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
   finally 
    ErrorContext.instance().reset();
  

该步骤实例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此时mapper代理实例仍未被创建。

2.3、Mapper代理对象的创建

DefaultSqlSession.class
@Override
public <T> T getMapper(Class<T> type) 
  return configuration.<T>getMapper(type, this);  // 类名全路径


Configuration.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) 
    // mapperRegistry 
  return mapperRegistry.getMapper(type, sqlSession);  //类名全路径


MapperRegistry.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) 
    // knownMappers map集合,存放的 key 是上述步骤解析配置文件放进去的 mapper路径名,value 是统一的MapperProxyFactory,
    //【knownMappers.put(type, new MapperProxyFactory<T>(type));】
  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); // MapperProxyFactory 工厂类创建代理对象
   catch (Exception e) 
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  


MapperProxyFactory.class
protected T newInstance(MapperProxy<T> mapperProxy) 
    // 利用JDK的动态代理生成mapper的代理实例
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]  mapperInterface , mapperProxy);


public T newInstance(SqlSession sqlSession) 
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);

生成了mapper的代理实例,后续就可以利用此代理实例进行数据库的操作了。后续通过代理类(MapperProxy)执行的方法都会走其 invoke()方法,最终都会进到 MapperMethod 类中执行对应类型的方法。【select|insert|update|delete|flush】

3、总结

1、我们用mytabis操作数据库,有一个固定流程:先创建SqlSessionFactory,然后创建SqlSession,然后再创建获取mapper代理对象,最后利用mapper代理对象完成数据库的操作;一次数据库操作完成后需要关闭SqlSession;

  2、创建SqlSessionFactory实例的过程中,解析mybatis配置文件和映射文件,将内容都存放到Configuration实例的对应属性中;创建SqlSession的过程中,有创建事务Transaction、执行器Executor,以及DefaultSqlSession;Mapper代理对象的创建,利用的是JDK的动态代理,InvocationHandler是MapperProxy,后续Mapper代理对象方法的执行都会先经过MapperProxy的invoke方法;

  3、很多细节没有讲到,但大体流程就是这样;另外提下,实际应用中,mybatis往往不会单独使用,绝大多数都是集成在spring中;关于在spring的集成下,mapper代理对象的创建过程查看:springboot集成下,mybatis的mapper代理对象究竟是如何生成的

以上是关于mybatis源码解析 - mapper代理对象的生成的主要内容,如果未能解决你的问题,请参考以下文章

mybatis源码解析 - mapper代理对象的生成

MyBatis——源码解析MyBatis框架底层的执行原理

MyBatis——源码解析MyBatis框架底层的执行原理

MyBatis——源码解析MyBatis框架底层的执行原理

MyBatis源码分析四XML解析与核心对象的构建

MyBatis源码分析四XML解析与核心对象的构建