基于Spring boot来分析Mybatis Mapper类生成过程以及自动配置过程,需要对spring boot的自动配置有一定的了解。
开门见山,先分析一下Mybatis源码代码入口,可以从Mybatis自动装配入手。
使用mybatis的自动装配终会引入这么一个依赖:mybatis-spring-boot-autoconfigure.这个包可以作为跟踪入口。
来看到这个包里面的这个文件,里面配置了自动装配所需要的配置类
/org/mybatis/spring/boot/mybatis-spring-boot-autoconfigure/1.3.2/mybatis-spring-boot-autoconfigure-1.3.2.jar!/META-INF/spring.factories
内容
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
我们主要看MybatisAutoConfiguration
类,这里面进行了Mapper动态类的生成与映射。
我们看org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration
,很明显的是一个配置bean,通过@Import注解引入了AutoConfiguredMapperScannerRegistrar
配置来进行Bean信息注册。可以推测这里的Bean信息注册应该是Mapper代理类的注册,那么Mapper代理类生成的入口也可以推断为在这里面。
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
继续看如下所示org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar
源码的注册Bean信息方法。可以推测 scanner.doScan(StringUtils.toStringArray(packages))
扫描Mapper应该进行了Mapper动态类生成与注册。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logger.debug("Searching for mappers annotated with @Mapper");
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
//获取包路径
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
//执行扫描Mapper
scanner.doScan(StringUtils.toStringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}
继续跟踪,doScan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//扫描并获取BeanDefintion
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 {
//根据BeanDefintion生成一个Mapper代理类并且注册
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
继续跟踪processBeanDefinitions,方法特别长,概括来说主要就是利用FactoryBean的方式进行Mapper代理类的注册,而代理类Bean的生成自然就在FactoryBean内部完成。下面的逻辑可以简单看看,主要看FactoryBean的getBean方法。
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
//设置FactoryBean所需要产出的Mapper 代理Bean的接口类型
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//设置FactoryBean的类型,实际就是org.mybatis.spring.mapper.MapperFactoryBean
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);
}
}
}
继续跟踪org.mybatis.spring.mapper.MapperFactoryBean源码,getObject方法如下,到这里就可以知道getSqlSession().getMapper(this.mapperInterface);应该就是生成Mapper代理类的地方,然后这里返回的代理类通过FactoryBean注册到spring容器中。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
跟踪getSqlSession().getMapper(this.mapperInterface);可以知道Mapper生成过程。
跟踪调用链:
来到这里源码,通过代理类工厂mapperProxyFactory新加你一个实例,这个实例就是Mapper的代理类。那么
mapperProxyFactory是何时生成的?给个思路,其实就是在org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory这个方法里面包含了这个过程。也就是在构建sqlSessionFactory的时候生成mapperProxyFactory,大概过程就是
@SuppressWarnings("unchecked")
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);
}
}
继续跟踪mapperProxyFactory.newInstance(sqlSession);
很传统的Java动态代理代码,那么代理逻辑想必都是在mapperProxy的invoke中实现的。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
跟踪org.apache.ibatis.binding.MapperProxy#invoke,就可以看到最终依靠MapperMethod对象来执行实际的数据库操作与结果集映射,这一点后续分析。
@Override
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);
}
//根据接口方法获取MapperMethod对象,对象包含返回对象类型,映射sql等
final MapperMethod mapperMethod = cachedMapperMethod(method);
//Mapper方法执行过程
return mapperMethod.execute(sqlSession, args);
}
总结上面两点过程就是一个了解Mapper接口是如何生成动态类来执行操作以及如何注册到容器的过程。下面分析的是Mapper代理类是如何实际执行的。包括参数映射,结果集映射等。
跟踪mapperMethod.execute方法内部,接着以执行executeForMany(sqlSession, args);
为例往下跟踪,会执行sqlSession.selectList(command.getName(), param);接下来实际执行会通过代理的方式执行一些预先处理逻辑,我们跳过跟踪,直接在org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)断点查看。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)代码执行selectList;针对下面的代码做一下解析。
首先是根据statement获取到在解析xml文件配置得到的配置对象,然后我们Mybatis中最重要的Executor会担任执行者角色,实际执行数据库查询操作。
但是需要注意的是如果我们使用了插件,那么 executor.query这个操作将会被代理执行,换句话说应该是executor本身就是一个代理类,里面的代理逻辑包装了实际executor.query逻辑。这里暂时不分析有插件的情况。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从配置获取Mapper xml文件解析得到的sql,参数,结果类型等信息对象
MappedStatement ms = configuration.getMappedStatement(statement);
//如果有配置插件,则通过代理执行;否则直接对应executor实例的query方法。
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在无插件的情况下继续跟踪以下调用链路
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//设置标志位,防止并发使用当前查询请求时,前一个请求没完成,后一个请求用了未完成请求的一级缓存
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//完成请求,去除标记
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
继续跟踪doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//获取StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//Statement处理操作,类似于jdbc中Statement相关操作封装。获取连接,Statement等
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在这里改为由StatementHandler继续执行,继续跟踪handler.query(stmt, resultHandler);
下面都是熟悉的jdbc代码,最终resultSetHandler. handleResultSets(ps);处理返回的查询结果,并且映射为用户配置的对象
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
resultSetHandler. handleResultSets(ps);跟踪下去就是结果集映射处理。可以推测的是根据根据结果集,利用反射机制进行新对象的创建以及值的设置。实际上实现也是这样,可以直接在
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)断点处理查看。
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建结果对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
//给结果设置属性值
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
回到之前说的executor执行可能会被代理的代码,如果有插件的话,这里的executor就是一个代理对象。下面看是如何实现的。以分页插件为例。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从配置获取Mapper xml文件解析得到的sql,参数,结果类型等信息对象
MappedStatement ms = configuration.getMappedStatement(statement);
//如果有配置插件,则通过代理执行;否则直接对应executor实例的query方法。
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
先看executor是如何注入到这个类的,代码如下。是在创建DefaultSqlSession的时候就注入的,猜测代理对象的生成逻辑应该就是在新建DefaultSqlSession时一起进行的。
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
跟踪DefaultSqlSession新建的逻辑,都会有以下代码
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
所以是在configuration.newExecutor(tx, execType)中建立executor,代码如下。在这里所有插件将会对executor进行包装带来。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//所有插件对executor进行包装代理
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
代理逻辑。可以看出如果是多个插件,还会对已经包装的对象再进行一次包装,多重包装。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
我们以分页插件为例看一下,包装逻辑的实现。com.github.pagehelper.PageInterceptor#plugin
@Override
public Object plugin(Object target) {
//TODO Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化,因此考虑在这个方法中做一次判断和初始化
//TODO https://github.com/pagehelper/Mybatis-PageHelper/issues/26
return Plugin.wrap(target, this);
}
org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
//判断插件是否适用当前目标
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//生成代理类
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
根据上面的所有信息可以得出一个结论就是代理逻辑都是放在Plugin中的invoke。在这里面再调用插件的拦截逻辑。分页插件的话是会计算一个分页sql传递给executor的执行方法,从而实现分页。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//调用拦截器的拦截逻辑,里面会继续调用executor的执行方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}