环境: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() {
// ... 省略创建一个DataSource
}
}
Mybatis利用Spring提供的BeanFactory接口,实现了SqlSessionFactoryBean,简单来说它就是一个构建SqlSessionFactory的工厂类,虽然注册的是SqlSessionFactoryBean,但是它生产的对象也会注册到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会回调这个方法获得它生产的bean
*/
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* Spring会在bean初始化完成之后回调这个方法
*/
public void afterPropertiesSet() throws Exception {
// 省略Assert校验...
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
* 构造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文件的配置,生产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的创建,同时可以借助这个类装配Interceptor、TypeHandler等组件