一般我们在spring整合mybatis可以进行如下处理:
@Configuration
public class SqlSessionConf {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(getDataSource());
sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("com/leo/test/mybatis/spring/mapper/PersonMapper.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean;
}
@Bean
public DataSource getDataSource(){
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://xxxx:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
// @Bean
// public MapperScannerConfigurer mapperScannerConfigurer() throws Exception {
// MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
// mapperScannerConfigurer.setBasePackage("com.leo.test.mybatis.spring");
// mapperScannerConfigurer.setSqlSessionFactory(getSqlSessionFactoryBean().getObject());
// return mapperScannerConfigurer;
// }
@MapperScan("com.leo.test.mybatis.spring.mapper")
public class Bootstrap {
private static final Logger log = LoggerFactory.getLogger(Bootstrap.class);
public static void main(String[] args) throws Exception{
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
context.register(Bootstrap.class);
context.scan("com.leo.test.mybatis.spring");
context.refresh();
SqlSessionFactory sqlSessionFactoryBean = context.getBean(SqlSessionFactory.class);
PersonMapper personMapper = (PersonMapper)context.getBean(PersonMapper.class);
System.out.println(personMapper.selectPerson(1));
context.close();
}
}
这里我简化了一些配置。
首先说明,mybatis整合进spring主要是用了spring的工厂bean即FactoryBean
来实现相关逻辑,整合中主要有两个比较重要的类:
SqlSessionFactoryBean
工厂beanMapperScan
注解
首先看SqlSessionFactoryBean
,
主要看其afterPropertiesSet
方法:
// SqlSessionFactoryBean.java
@Override
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory;
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
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);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
可以看到,这里主要是解析出Configuration
,然后通过SqlSessionFactoryBuilder
来构造SqlSessionFactory
,并赋值给SqlSessionFactoryBean.sqlSessionFactory
,然后当spring要获取SqlSessionFactory
的时候,通过SqlSessionFactoryBean.getObject
工厂方法获取到SqlSessionFactoryBean.sqlSessionFactory
。这样,SqlSessionFactory
相当于注入到了spring的容器中。
在看另外一个MapperScan
注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
.......
}
这里主要看MapperScannerRegistrar
这个类:
其实现了ImportBeanDefinitionRegistrar
接口:
// ImportBeanDefinitionRegistrar.java
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
在MapperScannerRegistrar
中实现如下:
// MapperScannerRegistrar.java
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
String defaultScope = annoAttrs.getString("defaultScope");
if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
总结一下,这里主要干了什么,说白了主要就是干了一件事情:
注册MapperScannerConfigurer
.
其主要逻辑在:
// MapperScannerConfigurer.java
@Override
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
这里主要是通过ClassPathMapperScanner
来对指定的mapper包进行扫描,
// ClassPathMapperScanner.java
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) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
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;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
这里会解析每个Mapper接口,然后封装成BeanDefinition,但是请注意,这里BeanDefinition对应的class类型并不是Mapper接口对应的类型,而是统一都是MapperFactoryBeanl类型,即Mapper接口注册到IOC容器的都是MapperFactoryBean类型
。
可以看到MapperFactoryBean
也是一个工厂bean,当我们通过IOC容器获取一个Mpper类型的时候,会找到工厂bean为Mapper类型的bean,然后调用其getObject
方法。
// MapperFactoryBean.java
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
看到了吗,还记得之前讲解MyBatis原理的时候,就是通过这种方式获取Mapper的,这样通过Mybatis的特性和spring的特性将两者结合到一起了。