[图片] 图片来源 https://terasolunaorg.github.io,非常推荐此文 org.mybatis-3.2.8 mybatis-spring-1.2.3 mybatis-spring-1.2.xsd [图片] 通过上面图片中描述可知,Spring 会扫描 basePackage 下的所有Mappe ..

MyBatis-Spring:Mapper 接口是怎么定位到 Mapper.xml 的

mybatis0001.jpg
图片来源 https://terasolunaorg.github.io,非常推荐此文

mybatis-spring-1.2.xsd

mybatis0002.jpg

通过上面图片中描述可知,Spring 会扫描 basePackage 下的所有Mapper接口并注册为MapperFactoryBean

MyBatis-Spring 官网描述
mybatis0006.jpg

MapperFactoryBean

mybatis0003.jpg

mybatis0004.jpg

@Autowired Mapper

# MapperFactoryBean中getObject方法
public T getObject() throws Exception {
   return this.getSqlSession().getMapper(this.mapperInterface);
}

MapperFactoryBean会从它的getObject方法中获取对应的Mapper接口,而getObject内部还是通过我们注入的属性调用SqlSession接口的getMapper(Mapper接口)方法来返回对应的Mapper接口的代理对象。这样就通过把SqlSessionFactory和相应的Mapper接口交给Spring管理实现了MybatisSpring的整合。

追踪一下getMapper 方法

  1. SqlSessionTemplate

    ......省略
    public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
    }
    
  2. Configuration

    ......省略
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
    
  3. MapperRegistry

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

可以看出是从 knownMappers 这个Map中取出了 MapperProxyFactory 对象,然后去构建动态代理类,下面先通过knownMappers 来分析下MyBatis 的初始化流程。

SqlSessionFactoryBean

SqlSessionFactoryBean 实现了 InitializingBean接口,在初始化时会执行afterPropertiesSet方法。此方法中会buildSqlSessionFactory

public void afterPropertiesSet() throws Exception {
   Assert.notNull(this.dataSource, "Property 'dataSource' is required");
   Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
   this.sqlSessionFactory = this.buildSqlSessionFactory();
}

摘取几段buildSqlSessionFactory中的逻辑

......
`xmlConfigBuilder.parse();`
......
`xmlMapperBuilder.parse();`
......
`return this.sqlSessionFactoryBuilder.build(configuration);`

XMLConfigBuilder

private void parseConfiguration(XNode root) {
   try {
	[properties(属性)](http://www.mybatis.org/mybatis-3/zh/configuration.html#properties)
	this.propertiesElement(root.evalNode("properties"));
	[typeAliases(类型别名)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases)
	this.typeAliasesElement(root.evalNode("typeAliases"));
	[plugins(插件)](http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins)
	this.pluginElement(root.evalNode("plugins"));
	[objectFactory(对象工厂)](http://www.mybatis.org/mybatis-3/zh/configuration.html#objectFactory)
	this.objectFactoryElement(root.evalNode("objectFactory"));
	this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
	[settings(设置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#settings)
	this.settingsElement(root.evalNode("settings"));
	[environments(环境配置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#environments)
	this.environmentsElement(root.evalNode("environments"));
	[databaseIdProvider(数据库厂商标识)](http://www.mybatis.org/mybatis-3/zh/configuration.html#databaseIdProvider)
	this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
	[typeHandlers(类型处理器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers)
	this.typeHandlerElement(root.evalNode("typeHandlers"));
	[mappers(映射器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers)
	this.mapperElement(root.evalNode("mappers"));
   } catch (Exception var3) {
	throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
   }
}

Mapper 加载的四种方式

XMLMapperBuilder

前两种Mapper的加载方式,都会调用mapperParser.parse(); 方法,第三种方式是直接加入Configuration的配置中configuration.addMapper(mapperInterface);

public void parse() {
   if (!this.configuration.isResourceLoaded(this.resource)) {
	this.configurationElement(this.parser.evalNode("/mapper"));
	# Set<String> loadedResources 集合,标识已经加载
	this.configuration.addLoadedResource(this.resource);
        # 绑定映射器到 namespace
	this.bindMapperForNamespace();
   }
   this.parsePendingResultMaps();
   this.parsePendingChacheRefs();
   this.parsePendingStatements();
}

private void bindMapperForNamespace() {
   String namespace = this.builderAssistant.getCurrentNamespace();
	`可以看出最终还是以第三种方式加载 <!-- 使用映射器接口实现类的完全限定类名 -->`
        try {
	   boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException var4) {
        }
	if (boundType != null && !this.configuration.hasMapper(boundType)) {
		this.configuration.addLoadedResource("namespace:" + namespace);
		this.configuration.addMapper(boundType);
	}
   }
}

Configuration.addMapper

Configuration.addMapper 调用的是 mapperRegistry.addMapper

public <T> void addMapper(Class<T> type) {
   this.mapperRegistry.addMapper(type);
}

MapperRegistry.addMapper

注册接口,到这里正好和上面 MapperFactoryBeangetObject方法 对应起来了

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

看下在MapperRegistry addMapper()方法,里面维护了knownMappers,key 为Class<T> type,值为MapperProxyFactory ( 创建Mapper代理对象的工厂 ),并且用MapperAnnotationBuilder 来解析构建MappedStatement

public <T> void addMapper(Class<T> type) {
   if (type.isInterface()) {
	if (this.hasMapper(type)) {
		throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
	}
	boolean loadCompleted = false;
	try {
		this.knownMappers.put(type, new MapperProxyFactory(type));
		MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
		parser.parse();
		loadCompleted = true;
	} finally {
		if (!loadCompleted) {
			this.knownMappers.remove(type);
		}
	}
   }
}

MapperAnnotationBuilder

看下 addMapper 中的 parser.parse();

public void parse() {
   String resource = this.type.toString();
   if (!this.configuration.isResourceLoaded(resource)) {
	this.loadXmlResource();
	this.configuration.addLoadedResource(resource);
	this.assistant.setCurrentNamespace(this.type.getName());
	this.parseCache();
	this.parseCacheRef();
	# 获取Mapper 接口中的所有方法
	Method[] methods = this.type.getMethods();
	Method[] arr$ = methods;
	int len$ = methods.length;
	for(int i$ = 0; i$ < len$; ++i$) {
	   Method method = arr$[i$];
	   try {
	      if (!method.isBridge()) {
		# 将 `Mapper.xml` 中每个方法 解析成`MappedStatement`对象
		this.parseStatement(method);
	      }
	   } catch (IncompleteElementException var8) {
		this.configuration.addIncompleteMethod(new MethodResolver(this, method));
	   }
	}
   }
   this.parsePendingMethods();
}

MapperBuilderAssistant

构建 MappedStatement 对象并加入全局配置 configuration.addMappedStatement(statement);相对应的 Configuration 中提供了getMappedStatement(String id)方法

public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
   if (this.unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
   } else {
      id = this.applyCurrentNamespace(id, false);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = new 
      org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType);
      statementBuilder.resource(this.resource);
      statementBuilder.fetchSize(fetchSize);
      statementBuilder.statementType(statementType);
      statementBuilder.keyGenerator(keyGenerator);
      statementBuilder.keyProperty(keyProperty);
      statementBuilder.keyColumn(keyColumn);
      statementBuilder.databaseId(databaseId);
      statementBuilder.lang(lang);
      statementBuilder.resultOrdered(resultOrdered);
      statementBuilder.resulSets(resultSets);
      this.setStatementTimeout(timeout, statementBuilder);
      this.setStatementParameterMap(parameterMap, parameterType, statementBuilder);
      this.setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
      this.setStatementCache(isSelect, flushCache, useCache, this.currentCache, statementBuilder);
      MappedStatement statement = statementBuilder.build();
      this.configuration.addMappedStatement(statement);
      return statement;
   }
}

MapperProxyFactory

@Autowired注入的Mapper最终会调用MapperRegistrygetMapper(Class<T> type, SqlSession sqlSession)方法, getMapper 中会通过 knownMappers.get(type) 获取到的MapperProxyFactory来构建 JDK 代理对象。

protected T newInstance(MapperProxy<T> mapperProxy) {
   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);
}

MapperProxy

看下代理类的 invoke 方法,是交给了MapperMethod 来执行。

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);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

🐾 参考

  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    118 引用 • 394 回帖 • 767 关注
4 回帖   
请输入回帖内容...
  • dapengpengpeng      
    展开

    感谢兄弟 帮了大忙了,写的很好👍

    该回帖因偏离主题而被折叠
    1 操作
    LYHFUU 在 2019-05-21 16:56:30 折叠了该回帖
  • Eddie

    你的标题说 mybatis,内容怎么聊到 spring 去了。。。。

  • LYHFUU  

    改成 Mybatis-Spring 了。~V~

  • LYHFUU      
    展开

    粉的味道太重😰

    该回帖因偏离主题而被折叠
    1 操作
    LYHFUU 在 2019-05-21 16:55:38 折叠了该回帖