前言
Mybatis现在作为我们项目中几乎必备的框架,让我们日常开发中操作数据库变得十分简单
那么Mybatis是如何集成到项目中的呢?本文就从源码层面解析Mybatis是如何工作的
源码
我们在项目中使用Mybatis都是优先在pom.xml引入一下jar包
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
mybatis-spring-boot-starter是Mybatis为了集成到springboot编写的一个starter组件,starter组件基于springboot的自动装配机制简化了配置,自动装配的原理此处不做介绍
我们直接看jar包下的spring.factories
也就是springboot在项目启动时会加载MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties,
ObjectProvider<Interceptor[]> interceptorsProvider,
ResourceLoader resourceLoader,
ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
......
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
......
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
@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);
}
}
......
}
/**
* {@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.
*/
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
}
这里代码太长了,省略了一下不重要的代码,分析下里面几个主要的bean
- SqlSessionFactory,在Mybatis里面主要用于产生SqlSession,提供操作数据库的增删改查
- SqlSessionTemplate是SqlSession接口的一个实现类,以前SqlSession的默认实现类DefaultSqlSession不是线程安全的,所以出现了线程安全的SqlSessionTemplate,并且注册为了bean
- applyConfiguration方法主要是解析了Configuration对象,并且放到了SqlSessionFactory里面,Configuration对象在Mybatis也很重要,主要是存储了各种配置
- MapperScannerRegistrarNotFoundConfiguration,在这里最主要的一个静态类,接下来会讲
MapperScannerRegistrarNotFoundConfiguration类上有个@Import({ AutoConfiguredMapperScannerRegistrar.class }),所以会加载AutoConfiguredMapperScannerRegistrar类,AutoConfiguredMapperScannerRegistrar是MybatisAutoConfiguration的一个静态内部类,实现了spring的ImportBeanDefinitionRegistrar,所以会执行registerBeanDefinitions方法
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
// 核心代码
scanner.doScan(StringUtils.toStringArray(packages));
}
......
}
接下来我们主要看核心代码,看scanner.doScan,传入了我们packages路径,packages路径就是我们配置的注解@MapperScan的value或者basePackages,也就是Mybatis要扫描的包路径,并且调用了scanner.setAnnotationClass(Mapper.class);也就是扫描的注解
看doScan
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 核心代码1
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 {
// 核心代码2
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
核心代码1处,调用的是spring提供的doScan,也就是扫描该路径下配置有@Mapper注解的bean。
这里先找到了BeanDefinitionHolder的集合,BeanDefinitionHolder可以认为是spring对bean的封装
接下来调用了processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
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);
......
}
}
这里最重要的就是设置了beanClass为mapperFactoryBeanClass,mapperFactoryBeanClass的定义
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
到这里为止已经成功的把我们Mybatis的Mapper接口注册到spring的beanDefinitionMap容器中了,beanDefinitionMap 的定义
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
最终spring会根据beanDefinitionMap 注册ioc容器,注册后我们的Mybatis Mapper的bean的存储是一个Map,key就是我们的Mapper接口首字母小写的名称,value会根据我们上面设置的beanClass创建出一个MapperFactoryBean,这个MapperFactoryBean很关键,我们看下他的定义
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
......
/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}
}
这个MapperFactoryBean继承了spring提供的FactoryBean,在spring创建bean时会调用它的getObject方法,这个getObject定义如下
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
到了SqlSessionManager的getMapper
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
到了Configuration的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 核心代码
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
继续看核心代码
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
newInstance方法
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
这里可以看到使用了jdk的动态代理,最终返回了一个MapperProxy,也就是说我们Mybatis最终注入的Mapper类实际上他的实现类是一个MapperProxy对象
MapperProxy是一个实现了InvocationHandler接口的实现类,基于动态代理,调用Mapper的任意方法时都会进入到其invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
到这里基本上就进入了Mybatis的核心代码了,基于调用Mapper接口的方法,找到对应的xml文件里面的sql,然后动态执行sql,最终返回
总结
到这里Mybatis在springboot中是如何加载的已经分析完毕了,其中核心的就是基于springboot自动装配机制来注册自己的bean,然后使用AutoConfiguredMapperScannerRegistrar来扫描Mapper接口,到ioc容器注册对应的bean,然后bean的真实实现类是MapperProxy代理类,基于动态代理来实现sql语句的执行