在之前的分析中,我们基本明白了mybatis对接口和xml的sql文件的组装拼接的原理。但是我们执行sql又是如何实现的,或者说sql的执行到底走了哪些流程。在上次的分析中我们知道mybatis采用了动态代理的方式,而且的pagehelper分页的时候也是动态代理。那么这之间到底是怎么执行的,除此之外我们也应当考虑mybatis提供的四大拦截器的具体执行顺序。所以这是我们今天的主要工作。
首先我们知道,我们通过mybatis执行sql大概是这样的。
/**
* 项目表
*/
@Autowired
private ProjectInfoPoMapper projectInfoPoMapper;
/**
* 添加一个项目
* @param request 添加监控项目
* @return
*/
@Override
public ResponseResult addMonitorProject(ProjectAddRequest request) {
ProjectDomain projectDomain=new ProjectDomain();
BeanUtils.copyProperties(request,projectDomain);
return projectDomain.insert(projectInfoPoMapper);
}
/**
* 添加到数据库
* @param projectInfoPoMapper
* @return
*/
public ResponseResult insert(ProjectInfoPoMapper projectInfoPoMapper) {
ProjectInfoPo po=new ProjectInfoPo();
BeanUtils.copyProperties(this,po);
po.setCreateTime(new Date());
if (projectInfoPoMapper.insert(po)>0){
return ResponseResult.success(true);
}
return ResponseResult.error("add fail");
}
显然这里的@Auwired我们已经解决了。而insert方法也是在接口中定义的。projectInfoPoMapper实体其实也是在spring启动的时候创建好了。但是我们好奇的是底层是如何实现的。所以我们还是跟踪一下。
在上期文章中,作者说过knowsmapper的map结构的代理mapper缓存。其中的元素就是proxymapperfactory。
也就是说我们的sql执行肯定是通过这里的proxymapper来执行的。那么我们重点看一下这里的proxymapper。因为这里是jdk动态代理,所以我们找一下proxymapper的代码。
jdk动态代理,就是说我们autowried注入的是动态代理生成的对象。我们在调用的时候其实只调用了接口,但是最后执行了被代理类的方法。这块的method就是接口法发起的。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
//如果是Object类,那么直接诶执行。
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);
}
在此我们还看到这里做了一个方法的缓存
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
其中对sql的属性进行分析。
public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
//获取方法名称
final String methodName = method.getName();
final Class declaringClass = method.getDeclaringClass();
//解析statement的映射关系
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
//获取sql的类型
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
我们继续跟进sql的执行
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 UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
//执行更新操作
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
//执行删除操作
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
//如果返回值是空的,并且方法上有对结果的拦截
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//返回的值是list
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回的值是mapper
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());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
可以看到上述操作对我们要执行的sql进行分类,然后去执行。我们在此大概得分析一下传入参数的解析,然后将重点放在下游调用链上。
在对sql进行解析的时候,将其参数转换为map
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
传入参数,返回map
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Mapparam = new ParamMap<>();
int i = 0;
for (Map.Entryentry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
在我们执行列表查询的时候执行了executeformany方法
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
//参数转为map
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
//通过sqlsession查询数据库
result = sqlSession.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;
}
继续跟踪代码,执行了参数的前置处理,并执行了查询方法
其中方法configuration.getMappedStatement是从xml文件的类中找到参数映射关系。warpcollection方法主要是对传入的参数进行map化,我们也看到这里默认collection、list、array等。也就是说我们传入这些值得时候其实是不用标记的。
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap map = new StrictMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
代码跟踪到这里的时候,作者发现在query方法上居然有缓存。如图所示
@Override
public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//参数绑定处理
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//执行
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在创建缓存的时候,其实也是一组list
@Override
public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//先从缓存中查询
List list = (List) tcm.getObject(cache, key);
if (list == null) {
//缓存中为空的,开始查库
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//添加到缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
通过这些时间的学习,我们也能感觉到,在写代码的时候一般将重要的操作放到抽象类或者父类中,子类其实是对父类的修正。我们继续看查库操作
@SuppressWarnings("unchecked")
@Override
public Listquery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//如果是刷新缓存,就清理掉本地缓存
clearLocalCache();
}
List list;
try {
queryStack++;
//尝试从缓存中查询
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
//对缓存中的参数进行更新
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//直接查库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
在查库的时候,mybatis进行了如下操作
private ListqueryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List 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;
}
而这里的preparestatement就是获取数据库连接的地方。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//设置事务时间
stmt = handler.prepare(connection, transaction.getTimeout());
//将传入的参数和数字关联
handler.parameterize(stmt);
return stmt;
}
在最终执行的时候,作者发现调用的是数据库连接的执行。
分析到这里,我们可能有点疑问,我们的executor是在哪里进行初始化的,不是说好的有拦截器么,怎么分析的过程中并没有执行?怀着这种疑问我们再来看看。
我们发现executor在初始化的时候就已经创建了。
作者通过代码跟踪,发现sqlSessionFactory中具有创建的相关代码。
创建的细节为,具体的实现类为configuration:
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);
}
//添加插件,这里的插件是通过jdk代理生成的。也就是说executor最后还是动态生成的
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
而要使用的executor的初始化则是通过configuration来产生的。configuration的注入如下所示
通过上述分析,我们得出的结论是我们使用注解@Autowired注入的时候是通过MapperFactoryBean注入的,而mapperFactory在初始化的时候注入了sqlsessionfactory然后初始了sqlsessiontemplate,而sqlsessiontemplate就是sqlsession的代理类。sqlsessionfactory的初始化则如上所示。sqlsessionfactory的初始化直接就已经生成了configuration,configuration在sqlsessionfactory创建的时候会进行创建 ,在之后sqlsessiontemplate调用的时候就没有后顾之忧。同时在getmapper注入sqlsession的时候其实也是注入的会话代理,同样是jdk动态代理,最终实现的是defaultsqlsession,defaultsqlsession执行方法的时候则会按照我们configuration设置的executortype来决定具体的执行器,其中的cachexecutor我们下次分析。而sqlsession的执行获取连接的部分最后就交给了数据库连接池。