mybatis学习笔记
Mybatis与spring集成时,在spring.xml中会配置两个重要的类,SqlSessionFactoryBean与MapperScannerConfigure。用于完成mybatis的加载与配置。
一、Mybatis核心类图
核心类是SqlSessionFactory和SqlSession

二、Mybatis加载过程
Mybatis自带的加载过程,集成到spring之后,加载过程会有一些变化,主要是bean要提交给spring容器来管理

三、SqlSessionFactoryBean介绍
spring配置mybatis的加载入口
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:com/rsms/iot/mapper/*.xml"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
</bean>
configLocation指定mybatis的全局配置位置
mapperLocations指定隐射文件的配置位置
SqlSessionFactoryBean实现接口InitializingBean,在bean加载完成后执行afterPropertiesSet完成SqlSessionFactory的创建。SqlSessionFactoryBean重新实现了mybatis原生的SqlSessionFactory的构建过程。在buildSqlSessionFactory()方法中通过XMLConfigBuilder完成对全局配置文件的解析,并构造Configuration对象。Configuration对象是全局唯一的提供了mybatis的所有配置项。
然后解析mapper文件
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();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
mapper文件是通过XMLMapperBuilder解析
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//过滤出所有的curd sql片段
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
循环解析所有mapper.xml文件,过滤出curd sql片段,构造MappedStatment(一个sql是一个MappedStatment)存放到Configuration对象的mappedStatements中,mappedStatements是StrictMap,继承hashmap,在存放的时候会存放两份,一份是id+namespace作为key值,一份是id作为key值。value值就是MappedStatment对象。
在xml文件解析完成之后,通过sqlSessionFactoryBuilder构造SqlSessionFactory对象
this.sqlSessionFactoryBuilder.build(configuration);
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
这样就完成了SqlSessionFactory创建。
四、MapperScannerConfigurer介绍
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.rsms.iot.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
MapperScannerConfigure的postProcessBeanDefinitionRegistry方法中,会通过ClassPathMapperScanner扫描basePackage下的所有接口。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner中通过doScan将原接口类型改造成MapperFactoryBean。为每个接口类创建动态代理。可以从下面源码看出
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// mapper接口是原bean类型,但是实际类型是MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
......
}
}
上面代码可以看出MapperScannerConfigure是为了避免一个一个定义MapperFactoryBean而添加的批量处理mapper接口的方法,根据basepackages路径,将mapper接口批量改造成MapperFactoryBean,但是元数据中依然保存了原接口类型信息,可以从下图看出。

五、MapperFactoryBean的装配
上一节中我们知道mapper接口最后都被改造成MapperFactoryBean,而MapperFactoryBean继承SqlSessionDaoSupport,而SqlSessionDaoSupport又继承InitializingBean,所以所有的MapperFactoryBean最后都会执行afterPropertiesSet完成自动装配。(DaoSupport->afterPropertiesSet()方法)
之后通过MapperFactoryBean的getObject获取代理对象(MapperProxy)注入到spring容器中。我们看看spring是如何获取代理对象的。
MapperFactoryBean类的getObject方法:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
调用SqlSessionTemplate的getMapper方法:
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
接着调用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
再调用MapperRegistry的getMapper方法:
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实例化一个代理对象
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Proxy.newProxyInstance就是jdk自带的动态代理完成MapperProxy的构造。
完成每个Mapper接口的代理对象的构造之后就会注入到spring容器管理。
六、mapper的调用过程
根据第三节我们知道,一系列mapper结构改造成MapperFactoryBean后进一步构造出其代理对象MapperProxy,所以调用mapper接口方法时,实际是调用MapperProxy的invoke方法,而该方法中又会调用MapperMethod的execute方法。如下代码
MapperProxy的invoke方法
@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);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
下面以列表查询为例,看下mybatis的执行过程,当然不同的执行语句调用的Executor是不同的,这里仅供参考。这张图片是网上找到的图片(自己用破解版的“某软件”辛辛苦苦画的图没保存下来,破解版好坑爹,懒的再画了)DefaultSqlSession之前,即调用mapper接口时,实际是调用MapperProxy的invoke方法,MapperProxy再调用MapperMethod的execute方法,MapperMethod才会调用DefaultSqlSession。
执行过程.png