mybatis整合spring。 需要弄明白以下问题
- 如何生成mybatis的Configuration类和SqlSessionFactory类的。
- 如何生成Mapper代理类并放入容器中。
- 如何进行事务管理(本文不说)
第1个问题。
Configuration的生成是在org.mybatis.spring.SqlSessionFactoryBean 类中生成的,
这个类在mybatis-spring-boot-starter包的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
类中的@bean注解的sqlSessionFactory方法生成的。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
factory.setConfiguration(properties.getConfiguration());
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
//在这个方法里面会生成Configuration和SqlSessionFactory的。
return factory.getObject();
}
在factory.getObject();方法中会调用buildSqlSessionFactory方法, 所以具体生成逻辑在org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory方法中,这里会生成 Configuration并使用它生成一个
SqlSessionFactory。
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory方法的代码如下。
/**
* Build a {@code SqlSessionFactory} instance.
*
* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
* {@code SqlSessionFactory} instance based on an Reader.
* Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration 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) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
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();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
在上面代码中会进行大量的校验, 并在最后使用configuration生成了sqlSessionFactory类
第2个问题
mybatis-spring-boot-starter包分析
现在开发使用的都是springboot, 整合mybtais只要引入下如下包就可以了
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
打开包可以看到,包中没有代码,只有一个spring.provides文件。
spring.provides文件
provides: mybatis-spring-boot-autoconfigure
spring.provides文件只有一行。 了解springboot的starter的原理的人应该明白这个spring.factories和spring.providesd文件的作用。接下来就需要查看mybatis-spring-boot-autoconfigure包中源码了
mybatis-spring-boot-autoconfigure包分析
这个包也很简单只有三个类。 其中
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
类是最重要的类。 下面类中我会写上一些注释,具体的看注释就明白了
###org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
这个类中会根据配置生成mybatis的Configuration类, 并且扫描classpath中的mapper注解的类, 生成对应的mapper的到容器中。 如果没有使用mapper注解的话,哪些mapper类是怎样注入容器中的呢?
/**
* {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a
* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.
*
* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
* configuration file is specified as a property, those will be considered,
* otherwise this auto-configuration will attempt to register mappers based on
* the interface definitions in or under the root auto-configuration package.
*
* @author Eddú Meléndez
* @author Josh Long
* @author Kazuki Shimizu
* @author Eduardo Macarrón
*/
@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
private static Log log = LogFactory.getLog(MybatisAutoConfiguration.class);
/**
* 这里注入是的mybatis的配置的对象。只是mybatis的配置,不是数据源的配置。
* 里面有mapperLocations和typeAliasesPackage等信息
*/
@Autowired
private MybatisProperties properties;
/**
* 这里是mybatis的拦截器,只要你实现了拦截器并放入容器中, 这里就会注入
*/
@Autowired(required = false)
private Interceptor[] interceptors;
/**
* 资源加载类,下面会用到加载一些资源
*/
@Autowired
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@Autowired(required = false)
private DatabaseIdProvider databaseIdProvider;
/**
* 这个是校验方法, 如果你配置了mybatis的config.xml地址。 这里就会校验你传入的地址是否错误。
* 如果没有这个方法就不用校验
*/
@PostConstruct
public void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(), "Cannot find config location: " + resource
+ " (please add config file or check your Mybatis " + "configuration)");
}
}
/**
* 这个方法才是重点方法,这个方法是生成SqlSessionFactory的方法。 其实mybatis的启动配置逻辑都在SqlSessionFactoryBean类中
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
factory.setConfiguration(properties.getConfiguration());
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
/**
这里是使用生成SqlSessionTemplate
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
/**
* This will just scan the same base package as Spring Boot does. If you want
* more power, you can explicitly use
* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed
* mappers working correctly, out-of-the-box, similar to using Spring Data JPA
* repositories.
* 这个类也很重要, 这个类主要是生成代理的mapper类, 并把这些mapper类放入容器中。 具体逻辑在内部方法中
*
*
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
/**
* 会自动注入
*/
private BeanFactory beanFactory;
/**
* 会自动注入
*/
private ResourceLoader resourceLoader;
/**
* 这个方法会扫描Mapper注解的接口,并且生成对应的类
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
log.debug("Searching for mappers annotated with @Mapper'");
//这个是mybatis-spring中的类, 会根据mapper接口生成代理类放入容器中
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
//获取项目中所有的需要AutoConfiguration的包。其实就是扫描这个项目
List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory);
for (String pkg : pkgs) {
log.debug("Using auto-configuration base package '" + pkg + "'");
}
//扫面保包含Mapper注解的类
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
//可以追到里面看看, doScan--> registerBeanDefinition 中会有把生成的beanDefiniation放入容器中。
scanner.doScan(StringUtils.toStringArray(pkgs));
} catch (IllegalStateException ex) {
log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled.");
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
/**
* {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up
* creating instances of {@link MapperFactoryBean}. If
* {@link org.mybatis.spring.annotation.MapperScan} is used then this
* auto-configuration is not needed. If it is _not_ used, however, then this
* will bring in a bean registrar and automatically register components based
* on the same component-scanning path as Spring Boot itself.
*/
@Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
log.debug(String.format("No %s found.", MapperFactoryBean.class.getName()));
}
}
}
org.mybatis.spring.mapper.ClassPathMapperScanner
这是一个很重要的类,这个类的作用主要是扫描特定路径下的类,并生成对应的bean放入容器中, Mapper的注册方式有三种
- 使用MapperScan注解,
- 在mybatis-config.xml中配置。
- spring中使用MapperScannerConfigurer的bean指定basePackage的方式。
其实这三种方式最终都是使用的 ClassPathMapperScanner 类来扫描类并注入容器中的。
使用MapperScan注解
如果使用了这个注解, 就会被org.mybatis.spring.annotation.MapperScannerRegistrar类探测到。 这个类继承了ImportBeanDefinitionRegistrar类(其作用就是动态新增bean到容器中)。在MapperScannerRegistrar中会获取mapperScan注解的信息, MapperScan中其实包含了很多的怎么根据过滤信息来获取mapper。ClassPathMapperScanner这些过滤信息都会生成对应的过滤器赋值给 ClassPathMapperScanner对象,在使用scanner对象扫描到mapper
在mybatis-config.xml中配置。
如果在mybatis的配置文件中配置了mappers. 并且使用的是class属性。这个就是mybatis了, 和spring无关。这里不展开说明。
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
使用MapperScannerConfigurer的bean来配置
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
配置如上。 可以 通过property传入接口,或者传入包名来获取mapper信息。 MapperScannerConfigurer类的定义如下
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
...........
/**
* {@inheritDoc}
*
* @since 1.0.2
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//这里还是使用了ClassPathMapperScanner来扫描信息
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));
}
.............
}
从postProcessBeanDefinitionRegistry类中知道, 器最终还是使用ClassPathMapperScanner来扫面mapper接口信息并放入容器中。
Mybatis-spring包分析
到ClassPathMapperScanner其实就是进入了Mybatis-spring包了
org.mybatis.spring.mapper.MapperFactoryBean
其实上面ClassPathMapperScanner扫描的beanDefinition都是 MapperFactoryBean 类型。在ClassPathMapperScanner的processBeanDefinitions方法中会设置扫描处理的beanDefinition的值, 其中就设置了beanClass的值为this.mapperFactoryBean.getClass()。 并且在这个方法中还把sqlSessionFactory也设置进去了
org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions方法的源码如下:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
/**
** 这里就把BeanDefinition的class值设置成mapperFactoryBean类了。
* 真正的mapper的bean是mapperFactoryBean的getObject方法中产生的。
*/
definition.setBeanClass(this.mapperFactoryBean.getClass());
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) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
可以看看MapperFactoryBean的getObject方法
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里就是很简单的将mapper的bean的生成了