下面是一个常见的Spring整合MyBatis的applicationContext.xml配置:
SqlSessionFactoryBean:负责解析配置文件,并实例化SqlSessionFactory和创建SqlSession
MapperScannerConfigurer:负责创建Mapper,并且注入到Spring的IOC容器中
下面我们分析SqlSession的创建.
继承体系:
字段:
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
//EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();
private boolean failFast;
private Interceptor[] plugins;
private TypeHandler>[] typeHandlers;
private String typeHandlersPackage;
private Class>[] typeAliases;
private String typeAliasesPackage;
private Class> typeAliasesSuperType;
//issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class extends VFS> vfs;
private Cache cache;
private ObjectFactory objectFactory;
private ObjectWrapperFactory objectWrapperFactory;
// 方法略...
}
SqlSessionFactoryBean继承的3个接口都是与Spring相关的,最重要的就是FactoryBean和InitializingBean接口,这两个接口的实现负责创建SqlSessionFactory。
而类中的字段大部分都是可通过配置文件配置的属性。
FactoryBean接口:
//先调用afterPropertiesSet,实例化sqlSessionFactory
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
// 返回Bean的类型--SqlSessionFactory
public Class extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
//使用单例模式
public boolean isSingleton() {
return true;
}
InitializingBean接口:
public void afterPropertiesSet() throws Exception {
// 确保dataSource和sqlSessionFactoryBuilder不为空
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//创建sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
可以看到,在获取Bean的时候,首先都会调用 afterPropertiesSet() 实例化sqlSessionFactory,最后由buildSqlSessionFactory()方法完成实例化,下面是完成实例化的代码:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
//将配置的config.xml文件解析(如果配置了的话),完成configuration的创建
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) {
//读取config.xml配置文件,创建Configuration,注意,此时还未对配置文件进行解析
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
// 没有配置文件、使用默认配置
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
//--------------------------------------------------
// 设置objectFactory、objectWrapperFactory、vfs、别名、插件plugins、typeHandlers、databaseIdProvider、cache等属性,略
//解析配置文件,将配置的属性设置到configuration对象中
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
//创建有Spring管理的transactionFactory
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
//设置Environment属性
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
//如果配置了mapperLocations属性的话,解析它们
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
//解析mapper.xml,省略try/catch以及log语句
XMLMapperBuilder xmlMapperBuilder =
new XMLMapperBuilder(mapperLocation.getInputStream(),configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
}
} else {
//打印日志,略
}
//创建sqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
(因为代码较长,省略部分设置属性的代码)
1、实例化SqlSessionFactory的时候,MyBatis首先还是会创建最重要的Configuration对象,该对象包含了MyBatis的所有配置信息,是非常重要的一个类,我们编写的config.xml配置文件就是为了覆盖它的默认配置。可以看到,如果没有编写配置文件的话,MyBatis会使用它的默认Configution。
2、设置objectFactory、objectWrapperFactory、vfs、别名、插件plugins、typeHandlers、databaseIdProvider、cache等属性,这些属性大部分都是用户可以对MyBatis进行拓展、自定义功能的类,略
3、解析config.xml配置文件,将我们的配置设置到Configuration中,下图是解析步骤:
4、设置事务相关的transactionFactory和全局相关的Environment属性
5、解析我们配置的mapperLocations属性,并扫描包中的mapper
6、创建SqlSessionFactory--传入Configuration对象,实例化DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
至此,SqlSessionFactoryBean的任务就已经结束了,下面继续分析MapperScannerConfigurer
在第一部分MapperScannerConfigurer的配置中,我们需要设置sqlSessionFactory或者sqlSessionFactoryBeanName属性,让Spring在创建MapperScannerConfigurer后,将上一步创建好的SqlSessionFactory注入到其中。而另一项必需的属性就是basePackage属性,该属性是我们Mapper接口的位置
MapperScannerConfigurer的类定义为:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
其中最重要的接口是 BeanDefinitionRegistryPostProcessor 接口,该接口的实现了将Mapper接口的代理类添加到了Spring容器。
下面是实现:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//由ClassPathMapperScanner类扫描配置的basePackage,将其中的mapper加入到Spring中
//设置我们配置的属性
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来扫描mapper,scan方法实现如下:
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//委托到了doScan方法
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
真正进行扫描的工作放到了doScan方法中:
public Set doScan(String... basePackages) {
//调用父类方法,将package中的mapper属性都封装到 BeanDefinition中
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//对 beanDefinitions 进行修改
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
doScan方法中,首先调用父类的doScan方法,将basePackage中的mapper接口扫描一遍,将mapper类的信息都封装到 BeanDefinition 中,然后通过修改 BeanDefinition的信息,达到将mapper添加到容器的目的。
private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
//将mapper接口的class属性设置为MapperFactoryBean,并且添加构造函数的参数为mapper的类名
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//将sqlSessionFactory添加到属性中,这样Spring创建MapperFactoryBean的时候,就可以动态的注入sqlSessionFactory
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
//-------------------------------------------
//设置mapper是通过类型注入
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
可以看到,mapper的BeanDefinition被动态的修改了:
1、类型被修改为了 MapperFactoryBean
2、添加了一个构造函数的参数--mapper的类名
3、添加了SqlSessionFactory的引用,这样Spring在创建MapperFactoryBean的时候,可以将SqlSessionFactory注入进去
接下来我们看下MapperFactoryBean的实现
因为在BeanDefinition中,添加了一个构造函数的参数(mapper的className),所以Spring在实例化MapperFactoryBean的时候会传入mapper的class:
public MapperFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
因为MapperFactoryBean继承了FactoryBean结构,所以它是一个创建Bean的工厂类,真正注入到Spring IOC中的Bean在getObject方法中:
//通过SqlSession获取mapper
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
//FactoryBean生产的类型就是mapper类型
public Class getObjectType() {
return this.mapperInterface;
}
//单例
public boolean isSingleton() {
return true;
}
至此,Spring完成了Mapper放入IOC的功能,这样,我们编写代码的时候,可以直接使用Sprng的方式来注入mapper
SqlSessionDaoSupport通过sqlSession字段维护了一个SqlSessionTemplate对象,并提供getSqlSession供子类获取该SqlSessionTemplate对象使用。该SqlSessionTemplate是其初始化时传入的SqlSessionFactory创建的:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public SqlSession getSqlSession() {
return this.sqlSession;
}