在Mybatis+spring配置章节中分析过mybatis的配置加载逻辑:
简单来说,就是两个核心配置项:MapperScannerConfigurer、SqlSessionFactoryBean
MapperScannerConfigurer的作用是扫dao文件,将所有的接口dao生成BeanDefinition,然后将它们的BeanClass配置成MapperFactoryBean.class,这样,在spring生成bean的时候,调用
FactoryBean#getObject
方法,就会去调用MapperFactoryBean#getObject
方法SqlSessionFactoryBean的作用是维护datasource,并且将xml文件扫成代理类,生成的代理类存在内存map,key是namespace对应的类名,value是代理类工厂
当spring创建接口dao对应的bean时,调用MapperFactoryBean#getObject
实际生成的是代理类bean,因此调用接口方法,实际上走的是代理类的invoke方法
因此分析Mybatis语句执行流程,实际上要从代理类的invoke方法开始分析
MapperProxy
invoke
根据jdbcAPI的框架,可以知道开发一个JDBC,只需要完成几个模块:datasource、connection、statement、resultset
mybatis搞数据连接,一定也是这么做的,看invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
核心在cachedInvoker(method).invoke(proxy, method, args, sqlSession);
同时这时候代理类已经持有了datasource并且生成session了,这里有个小疑问:代理类里面持有的sqlSession是哪里来的
这个问题可以从MapperFactoryBean#getObject
往上找
// org.mybatis.spring.mapper.MapperFactoryBean#getObject
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
先看getMapper:
// org.apache.ibatis.session.SqlSessionManager#getMapper
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
取的是SqlSession自己,再看getSqlSession()
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
继续跟踪sqlSessionTemplate的存入:
// org.mybatis.spring.support.SqlSessionDaoSupport
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
这个set方法没有显式的调用点,怀疑是spring创建bean的时候配置属性(populateBean)调用的
SqlSessionDaoSupport是MapperFactoryBean的父类,即创建MapperFactoryBean的实调用了setSqlSessionFactory方法,配置了数据源
还记得MapperFactoryBean的创建位置是在MapperScannerConfigurer扫包的时候:
调用到了ClassPathMapperScanner#processBeanDefinitions
// org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
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;
}
这里给BD配置了sqlSessionFactory属性,spring创建bean的时候基于set方法配置属性,就让生成的bean持有了sqlSession,进而后面让代理类也持有了sqlSession
了解了sqlSession,继续跟进invoke方法,看cachedInvoker方法和MapperMethodInvoker#invoke
,这里实际上是调用PlainMethodInvoker#invoke
(点我跳转)
cachedInvoker
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
}
return new DefaultMethodInvoker(getMethodHandleJava9(method));
} ……
});
首先根据!m.isDefault()
区分走哪个实现类
public boolean isDefault() {
// Default methods are public non-abstract instance methods
// declared in an interface.
return ((getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) ==
Modifier.PUBLIC) && getDeclaringClass().isInterface();
}
这里看isDefault方法,实际上就是判断为public,非抽象方法,即允许接口DAO有default方法实现,有的话不走mybatis,走默认实现
而如果非default,走PlainMethodInvoker#invoke
(点我跳转)
PlainMethodInvoker
invoke
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
这里通过成员变量MapperMethod调用MapperMethod#execute
方法
MapperMethod
execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
……
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
……
return result;
}
这里就是根据返回值分流了,实际上利用前面传进来的SqlSession实现不同方法分流
可以看到SELECT场景比较多,以返回多条为例,参考executeForMany
executeForMany
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// part1. 调用执行
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
// part2. 结果封装
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
}
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
return result;
}
part1,根据是否有写列映射决定调用方法
part2,支持返回集合、数组类型
跟进DefaultSqlSession#selectLis
DefaultSqlSession
selectList
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
dirty |= ms.isDirtySelect();
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch ……
}
传进来的statement是xml中的id,则首先根据id获取statement,参考Configuration#getMappedStatement
(点我跳转)
然后下面由executor执行query方法,可以看到executor有BaseExecutor、CachingExecutor两个实现,即mybatis的缓存能力
顺着BaseExecutor#query
往下找
// BaseExecutor#query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
……
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
……
看到queryFromDatabase这个方法:
// BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
……
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
然后看BatchExecutor#doQuery
// BatchExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds,
resultHandler, boundSql);
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
终于看到最基础的jdbc套路了,到这里mybatis的执行逻辑基本拆解清楚
Configuration
getMappedStatement
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
跟进buildAllStatements方法就可以看到把xml语句解析成statement的过程
buildAllStatements
x.parseStatementNode();
……
x.resolve();
这里有两个方法,进去都会调用到LanguageDriver#createSqlSource
例如resolve调用parseStatement:
void parseStatement(Method method) {
final Class<?> parameterTypeClass = getParameterType(method);
final LanguageDriver languageDriver = getLanguageDriver(method);
getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass,
languageDriver, method);
……
看下LanguageDriver#createSqlSource
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
}
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
这里的SqlSource是用来解析sql语句的,根据sql类型,分成了动态的和非动态的,动态sql是一个重点内容,即处理#{}和${}占位符,参考DynamicSqlSource部分
这里在前面MapperRegistry#addMapper
调用parse解析也会触发一次,所以执行的时候一般都是有缓存了
// org.apache.ibatis.binding.MapperRegistry#addMapper
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
再看buildSqlSource
// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#buildSqlSource
private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver,
Method method) {
if (annotation instanceof Select) {
return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
}
if (annotation instanceof Update) {
return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Insert) {
return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof Delete) {
return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
} else if (annotation instanceof SelectKey) {
return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
}
return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
}
就是根据不同的xml方法分别执行解析了
DynamicSqlSource
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
SqlSource接口的方法就是getBoundSql方法,有几种实现,DynamicSqlSource就是负责解析动态sql的
getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
这里构造了一个SqlSourceBuilder是用来解析sql的,继续跟进SqlSourceBuilder#parse
(点我跳转)
SqlSourceBuilder
parse
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
可以看到,使用GenericTokenParser解析占位符,并最终解析成StaticSqlSource返回去
评论区