mybatis plus 直接执行sql_Mybatis学习笔记(二) Sql的执行过程

本文深入探讨了Mybatis Plus如何直接执行SQL语句,详细阐述了Mybatis Plus在Sql执行过程中的工作原理,帮助读者理解其在Mybatis学习中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在之前的分析中,我们基本明白了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。c6f0bce027619f6b28cc431181e1365c.png也就是说我们的sql执行肯定是通过这里的proxymapper来执行的。那么我们重点看一下这里的proxymapper。因为这里是jdk动态代理,所以我们找一下proxymapper的代码。6d180f4fe978c9fc60ae456ca024b3af.pngjdk动态代理,就是说我们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;  }
继续跟踪代码,执行了参数的前置处理,并执行了查询方法928186b1db5d0dcb7935b5d8d3f60af3.png其中方法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方法上居然有缓存。如图所示3b21ad45ca90080ca695442e216ed200.png
@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);  }
在创建缓存的时候,其实也是一组list5b96b30408fd3ba5a79de3b048ac7bbd.png  
@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;  }
0433e26d89b29c4e5dcb28b4ffec0bdb.png而这里的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;  }
在最终执行的时候,作者发现调用的是数据库连接的执行。90e3dc495dc914754cc04cfe44cd63ec.png分析到这里,我们可能有点疑问,我们的executor是在哪里进行初始化的,不是说好的有拦截器么,怎么分析的过程中并没有执行?怀着这种疑问我们再来看看。我们发现executor在初始化的时候就已经创建了。032bef7fc90087320c731fd337aefc25.png作者通过代码跟踪,发现sqlSessionFactory中具有创建的相关代码。cd8b9c9e5f953cc39dcd22a34d09652b.png创建的细节为,具体的实现类为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的注入如下所示57485b448f8e10f9d52db3dd427513f8.png7db9f4a8e81295999b3659e6b97c0457.png通过上述分析,我们得出的结论是我们使用注解@Autowired注入的时候是通过MapperFactoryBean注入的,而mapperFactory在初始化的时候注入了sqlsessionfactory然后初始了sqlsessiontemplate,而sqlsessiontemplate就是sqlsession的代理类。sqlsessionfactory的初始化则如上所示。sqlsessionfactory的初始化直接就已经生成了configuration,configuration在sqlsessionfactory创建的时候会进行创建 ,在之后sqlsessiontemplate调用的时候就没有后顾之忧。同时在getmapper注入sqlsession的时候其实也是注入的会话代理,同样是jdk动态代理,最终实现的是defaultsqlsession,defaultsqlsession执行方法的时候则会按照我们configuration设置的executortype来决定具体的执行器,其中的cachexecutor我们下次分析。而sqlsession的执行获取连接的部分最后就交给了数据库连接池。4ba74c016f721abdddc9e4f7855a8f84.png3ddb4071e466affde5d467a02acc117d.png

108bfe73a7f41f04dac9828132b9c307.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值