環(huán)境:mybatis-spring 2.0.3
可以通過如下方式向在Spring中集成Mybatis,通過向Spring注冊SqlSessionFactoryBean
和@MapperScan
啟用Mybatis的功能
@Configuration
@MapperScan("com.holybell.mybatis.mapper") // Mapper接口掃描路徑
public class Config {
/**
* 通過SqlSessionFactoryBean獲得SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
@Bean
public DataSource dataSource() {
// ... 省略創(chuàng)建一個DataSource
}
}
Mybatis利用Spring提供的BeanFactory
接口球恤,實現(xiàn)了SqlSessionFactoryBean
辜昵,簡單來說它就是一個構(gòu)建SqlSessionFactory
的工廠類,雖然注冊的是SqlSessionFactoryBean
咽斧,但是它生產(chǎn)的對象也會注冊到Spring容器中
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean,
ApplicationListener<ApplicationEvent> {
// spring的資源加載器
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER
= new PathMatchingResourcePatternResolver();
// spring的類注解解析器
private static final MetadataReaderFactory METADATA_READER_FACTORY
= new CachingMetadataReaderFactory();
private SqlSessionFactory sqlSessionFactory;
/**
* FactoryBean向Spring容器注冊之后堪置,
* Spring會回調(diào)這個方法獲得它生產(chǎn)的bean
*/
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* Spring會在bean初始化完成之后回調(diào)這個方法
*/
public void afterPropertiesSet() throws Exception {
// 省略Assert校驗...
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
* 構(gòu)造Mybatis的SqlSessionFactory
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
final Configuration targetConfiguration;
// 省略其他屬性的裝配
// 掃描路徑躬存,向mybatis注冊TypeHandler
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage,
TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface())
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
// 如果注冊SqlSessionFactoryBean的時候配置了Mapper文件地址
// 那么就會去解析Mapper文件,生成Configruation中的各種配置信息舀锨,如MapperStatement
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 這里就和不集成Spring的Mybatis操作一樣
// 解析Mapper文件的配置岭洲,生產(chǎn)MappedStatement等信息
XMLMapperBuilder xmlMapperBuilder
= new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(),
configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
// ...省略異常
} finally {
ErrorContext.instance().reset();
}
}
// ...
return this.sqlSessionFactoryBuilder.build(configuration);
}
/**
* 從指定的路徑packagePatterns加載指定assignableType注解的類
*/
private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType)
throws IOException {
Set<Class<?>> classes = new HashSet<>();
String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packagePattern : packagePatternArray) {
// 加載類資源,每個Resource對象表示一個被加載的文件資源坎匿,此處為class文件
Resource[] resources = RESOURCE_PATTERN_RESOLVER
.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(packagePattern)
+ "/**/*.class");
// 遍歷每個加載到的Class文件盾剩,解析注解信息
for (Resource resource : resources) {
try {
// 解析類的注解信息
ClassMetadata classMetadata = METADATA_READER_FACTORY
.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
// 符合條件的類加入集合返回
classes.add(clazz);
}
} catch (Throwable e) {
// ... 省略異常日志
}
}
}
return classes;
}
}
簡而言之,Mybatis借助SqlSessionFactoryBean
完成了在不集成Spring的情況下SqlSessionFactory
的創(chuàng)建替蔬,同時可以借助這個類裝配Interceptor
告私、TypeHandler
等組件