文章目录
简介
最近重构项目,遇到一些关于MyBatis的奇奇怪怪的问题,还久没有看MyBatis源码都有点生疏了,这里重新梳理一下。
首先,先看一个简要的流程图:
在Spring中我们基本配置SqlSessionFactoryBean的DataSource,xml文件位置,配置文件位置就可以使用MyBatis了。
其中的细节,请看后面的内容。
SqlSessionFactoryBean
故事还得从SqlSessionFactoryBean说起,看名字,咱就知道是一个FactoryBean,就是一个生成工厂的类,生产什么的的工厂呢?
生成SqlSessionFactory的工厂。
其实在结合Spring使用MyBatis的时候,我们主要就是通过配置SqlSessionFactoryBean来获取SqlSessionFactory。
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapping/*.xml"));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis/mybatis-config.xml"));
return sessionFactory.getObject();
}
当然处理设置Datasource、MyBatis自身配置文件的位置、包含SQL的xml文件位置,我们还可以通过SqlSessionFactoryBean配置一些其他东西:
- 类型的别名
- 拦截器(Interceptor)
- 类型处理器(TypeHandler)
- ……
如果对这些组件不太了解,可以看看下面这篇文章:
我们知道有那些组件之后,还需要了解Spring到底如何使用它们,所以我们还需要理解一个重要的组件Configuration。
Configuration
一般我们不需要修改Configuration,SqlSessionFactoryBean会去读取Mybatis的配置文件解析,如果没有会自动创建一个。这些逻辑都在SqlSessionFactoryBean#buildSqlSessionFactory方法中。
其实在SqlSessionFactoryBean注册的类型别名、类型处理器也注册在了Configuration中,不过这里不是我们关注的重点,我们关注的重点是Configuration在实例化的时候,创建了一个重要的类MapperRegistry。
MapperRegistry
看名字我们就能猜到MapperRegistry多半是用来存放Mapper的地方,实际上MapperRegistry维护着一个Map:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
map的key是我们Mapper的class,value是MapperProxyFactory
MapperProxyFactory这玩意儿啊,看名字我们知道是一个生产MapperProxy的工厂
MapperProxy这个类看名字我们就可以猜是我们Mapper的代理类,实际上也正是如此,MapperProxy实现了InvocationHandler,Java标准代理模式,如果对Java的动态代理不太了解,可以看一下:Java动态代理细探和Cglib、Javassist、JDK动态代理
MappeProxy将Mapper的每一个方法封装为MapperMethod
为什么我们调用一个空的Mapper接口,就获取到数据了?具体的怎么处理参数、怎么执行SQL、怎么处理返回值就可以在MapperMethod找。
详细的介绍,可以参考下面两篇文章:
MyBatis源码之:MapperProxyFactory与MapperProxy
MyBatis源码之:MapperMethod
MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
MapperScan注解有一个@Import(MapperScannerRegistrar.class)
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,ImportBeanDefinitionRegistrar这个接口通过Import就会调用其接口方法。
MapperScannerRegistrar还实现了ResourceLoaderAware,就是为了获取ResourceLoader,看名字就知道是为了获取资源,例如,ClassPathXmlApplicationContext就实现了ResourceLoader。
接下来我们看一下MapperScannerRegistrar这个类
MapperScannerRegistrar
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,通过@Import导入,会调用registerBeanDefinitions方法,所以直接看MapperScannerRegistrar#registerBeanDefinitions的方法就可以了。
不同版本的实现不一样,高版本的多封装了一个MapperScannerConfigurer类,不过实现都是使用ClassPathMapperScanner去扫描包。
注意:扫描包并不是直接创建了代理,而是创建了一个MapperFactoryBean,具体实现看ClassPathMapperScanner#processBeanDefinitions方法
关键代码:
definition.setBeanClass(this.mapperFactoryBeanClass);
MapperFactoryBean
MapperFactoryBean一看就知道是生产MapperFactory的类,是通过getObject获取MapperFactory。
MapperFactoryBean#checkDaoConfig方法中,把Mapper注册到了Configuration中,实际上通过前面的介绍,我们知道是注册到了MapperRegistry中。
@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 {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
但是checkDaoConfig是怎么调用的呢?
MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport继承了DaoSupport,DaoSupport实现了InitializingBean:
InitializingBean这玩意儿咱应该比较熟悉,Spring在执行完setter方法之后,就会调用它的afterPropertiesSet方法。
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
如何调用
我们使用Mapper时候,Spring实际注入的类是MapperProxy,MapperProxy最终会通过MapperMethod来执行对应的方法。
最后
流程中有一个可能常遇到的问题因为在MapperMethod中没有展开细说,如经常遇到:
Invalid bound statement (not found):
Unknown execution method for:
类的错误,定位到MapperMethod的SqlCommand类,debug。