面试:谈谈你对MyBatis执行过程之SQL执行过程理解

Mybatis源码解析之数据库连接与SQL执行 文章目录1、背景介绍2、创建SqlSession3、数据库连接4、执行SQL语句5、常见面试题 这一篇文章主要MyBatis拿到配置文件之后,即获取到数据源配置信息和SQL语句之后,然后再从源码解读分析MyBatis是如何进行数据库连接和SQL语句执行。关于MyBatis是如何解析mybatis-config.xml文件,获取到数据源的请看这篇文章: Mybatis源码解析之数据源和SQL构建 1... 阅读详情

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

前言

在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。

更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试也会觉得你已掌握Mybatis知识点了。

SQL执行过程简介

经过MyBatis初始化加载Sql执行过程所需的信息后,我们就可以通过 SqlSessionFactory 对象得倒忙SqlSession ,然后执行 SQL 语句了,接下来看看Sql执行具体过程,SQL大致执行流程图如下所示:

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

接下来我们来看看每个执行链路中的具体执行过程,

SqlSession

SqlSession 是 MyBatis 暴露给外部使用的统一接口层,通过 SqlSessionFactory 创建,且其包含和数据库打交道所有操作接口。

下面通过时序图描述 SqlSession 对象的创建流程:

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

在生成 SqlSession 的同时,基于 executorType 初始化好 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) interceptorChain.pluginAll(executor);   return executor;
}

最顶层的SqlSession接口已生成,那我们可以来看看sql的执行过程下一步是怎样的呢? 怎样使用代理类 MapperProxy 。

MapperProxy

MapperProxy 是 Mapper 接口与SQL 语句映射的关键,通过 MapperProxy 可以让对应的 SQL 语句跟接口进行绑定的,具体流程如下:

  • MapperProxy 代理类生成流程
  • MapperProxy 代理类执行操作

MapperProxy 代理类生成流程

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

其中, MapperRegistry 是 Configuration 的一个属性,在解析配置时候会在 MapperRegistry 中缓存了 MapperProxyFactory 的 knownMappers 变量 Map 集合。

MapperRegistry 会根据 mapper 接口类型获取已缓存的 MapperProxyFactory , MapperProxyFactory 会基于 SqlSession 来生成 MapperProxy 代理对象,

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
       MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
       if (mapperProxyFactory == null) {
           throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
      } else {
           try {
               return mapperProxyFactory.newInstance(sqlSession);
          } catch (Exception var5) {
               throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
          }      }  }

当调用 SqlSession 接口时, MapperProxy 怎么是实现的呢? MyBatis 的 Mapper 接口 是通过动态代理实现的,调用 Mapper 接口的任何方法都会执行 MapperProxy::invoke() 方法,

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       try {
           //Object类型执行
           if (Object.class.equals(method.getDeclaringClass())) {
               return method.invoke(this, args);
          }
//接口默认方法执行
           if (method.isDefault()) {
               if (privateLookupInMethod == null) {
                   return this.invokeDefaultMethodJava8(proxy, method, args);
              }
               return this.invokeDefaultMethodJava9(proxy, method, args);
          }
      } catch (Throwable var5) {
           throw ExceptionUtil.unwrapThrowable(var5);
      }
       MapperMethod mapperMethod = this.cachedMapperMethod(method);
       return mapperMethod.execute(this.sqlSession, args);
  }

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

但最终会调用倒忙mapperMethod::execute() 方法执行,主要是判断是 INSERT 、 UPDATE 、 DELETE 、 SELECT 语句去操作,其中如果是查询的话,还会判断返回值的类型。

public Object execute(SqlSession sqlSession, Object[] args) {
       Object result;       Object param;       switch(this.command.getType()) {
       case INSERT:           param = this.method.convertArgsToSqlCommandParam(args);
           result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
           break;
       case UPDATE:           param = this.method.convertArgsToSqlCommandParam(args);
           result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
           break;
       case DELETE:           param = this.method.convertArgsToSqlCommandParam(args);
           result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
           break;
       case SELECT:           if (this.method.returnsVoid() && this.method.hasResultHandler()) {
               this.executeWithResultHandler(sqlSession, args);
               result = null;
          } else if (this.method.returnsMany()) {
               result = this.executeForMany(sqlSession, args);
          } else if (this.method.returnsMap()) {
               result = this.executeForMap(sqlSession, args);
          } else if (this.method.returnsCursor()) {
               result = this.executeForCursor(sqlSession, args);
          } else {
               param = this.method.convertArgsToSqlCommandParam(args);
               result = sqlSession.selectOne(this.command.getName(), param);
               if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                   result = Optional.ofNullable(result);              }          }           break;
       case FLUSH:           result = sqlSession.flushStatements();           break;
       default:
           throw new BindingException("Unknown execution method for: " + this.command.getName());
      }       if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
           throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
      } else {
           return result;
      }  }

通过以上的分析,总结出

  • Mapper 接口实际对象为代理对象 MapperProxy ;
  • MapperProxy 继承 InvocationHandler ,实现 invoke 方法;
  • MapperProxyFactory::newInstance() 方法,基于 JDK 动态代理的方式创建了一个 MapperProxy 的代理类;
  • 最终会调用倒忙mapperMethod::execute() 方法执行,完成操作。
  • 而且更重要一点是, MyBatis 使用的动态代理和普遍动态代理有点区别,没有实现类,只有接口,MyBatis 动态代理类图结构如下所示:

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

以 SELECT 为例, 调用会 SqlSession ::selectOne() 方法。继续往下执行,会执行 Executor::query() 方法。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
       List var5;       try {
           MappedStatement ms = this.configuration.getMappedStatement(statement);
           var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      } catch (Exception var9) {
           throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
      } finally {
           ErrorContext.instance().reset();      }       return var5;
  }

执行倒忙Executor 类,那么我们来看看其究竟有什么?

E x ecutor

Executor 对象为SQL 的执行引擎,负责增删改查的具体操作,顶层接口 SqlSession 中都会有一个 Executor 对象,可以理解为 JDBC 中 Statement 的封装版。

Executor 是最顶层的是执行器,它有两个实现类,分别是 BaseExecutor 和 CachingExecutor

  • BaseExecutor 是一个抽象类,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。 BaseExecutor 基于适配器设计模式之接口适配会有三个子类,分别是 SimpleExecutor 、 ReuseExecutor 和 BatchExecutor 。
    • BatchExecutor : 批处理执行器,用于执行update(没有select,JDBC批处理不支持select将多个 SQL 一次性输出到数据库,
    • ReuseExecutor : 可重用执行器, 执行 update 或 select ,以sql作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement> 内,供下一次使用。简言之,就是重复使用 Statement 对象
    • SimpleExecutor : 是 MyBatis 中 默认 简单执行器,每执行一次 update 或 select ,就开启一个 Statement 对象,用完立刻关闭 Statement 对象
  • CachingExecutor : 缓存执行器,为 Executor 对象增加了 二级缓存 的相关功:先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给 Executor delegate 去数据库中取, delegate 可以是上面任何一个执行器。

在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。

看完 Exector 简介之后,继续跟踪执行流程链路分析, SqlSession 中的 JDBC 操作部分最终都会委派给 Exector 实现, Executor::query() 方法,看看在 Exector 的执行是怎样的?

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

每次查询都会先经过 CachingExecutor 缓存执行器, 会先判断二级缓存中是否存在查询 SQL ,如果存在直接从二级缓存中获取,不存在即为第一次执行,会直接执行SQL 语句,并创建缓存,都是由 CachingExecutor::query() 操作完成的。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
       BoundSql boundSql = ms.getBoundSql(parameterObject);       CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
       return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {       //获取查询语句对应的二级缓存
  Cache cache = ms.getCache();       //sql查询是否存在在二级缓存中
     if (cache != null) {
           //根据 <select> 节点的配置,判断否需要清空二级缓存
           this.flushCacheIfRequired(ms);
           if (ms.isUseCache() && resultHandler == null) {
               this.ensureNoOutParams(ms, boundSql);
               //查询二级缓存
               List<E> list = (List)this.tcm.getObject(cache, key);
               if (list == null) {
                   //二级缓存没用相应的结果对象,调用封装的Executor对象的 query() 方法
                   list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                   //将查询结果保存到二级缓存中
                   this.tcm.putObject(cache, key, list);
              }               return list;
          }      }//没有启动二级缓存,直接调用底层 Executor 执行数据数据库查询操作
       return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

如果在经过 CachingExecutor 缓存执行器(二级缓存)没有返回值的话,就会执行 BaseExecutor 以及其的实现类,默认为 SimpleExecutor ,首先会在一级缓存中获取查询结果,获得不到,最终会通过 SimpleExecutor:: () 去数据库中查询。

public <E> List<E> query(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 (this.closed) {
           throw new ExecutorException("Executor was closed.");
      } else {
           //是否清除本地缓存
           if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
               this.clearLocalCache();
          }           List list;           try {
               ++this.queryStack;
               //从一级缓存中,获取查询结果
               list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
               //获取到结果,则进行处理
               if (list != null) {
                   this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
              } else {
                   //获得不到,则从数据库中查询
                   list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
              }          } finally {
               --this.queryStack;
          }           if (this.queryStack == 0) {
               //执行延迟加载
               Iterator var8 = this.deferredLoads.iterator();
               while(var8.hasNext()) {
                   BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();                   deferredLoad.load();              }               this.deferredLoads.clear();
               if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                   this.clearLocalCache();
              }          }           return list;
      }  }

那么 SimpleExecutor::doQuery() 如何去数据库中查询获取到结果呢? 其实执行到这边mybatis的执行过程就从 Executor 转交给 StatementHandler 处理,

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
       Statement stmt = null;
       List var9;       try {
           Configuration configuration = ms.getConfiguration();           StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
           stmt = this.prepareStatement(handler, ms.getStatementLog());
           var9 = handler.query(stmt, resultHandler);      } finally {
           this.closeStatement(stmt);
      }       return var9;
  }

这样我们的执行链路分析已到 StatementHandler 了,现在让我们去一探究竟其原理

StatementHandler

StatementHandler 负责处理 Mybatis 与JDBC之间 Statement 的交互,即 Statement 对象与数据库进行交互,其为顶级接口,有4个实现类,其中三个是 Statement 对象与数据库进行交互类, 另外一个是路由功能的,

  • RoutingStatementHandler : 对 Statement 对象没有实际操作,主要负责另外三个 StatementHandler 的创建及调用, 而且在 MyBatis 执行时,使用的 StatementHandler 接口对象实际上就是 RoutingStatementHandler 对象。
  • SimpleStatementHandler : 管理 Statement 对象, 用于简单SQL的处理 。
  • PreparedStatementHandler : 管理 Statement 对象,预处理SQL的接口 。
  • CallableStatementHandler :管理 Statement 对象,用于执行存储过程相关的接口 。

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

 

在经历过 Executor 后,基于初始化加载到 MapperState 中的 StatementType 的类型通 过Configuration.newStatementHandler() 方法中的 RoutingStatementHandler 生成 StatementHandler 实际处理类。

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
       switch(ms.getStatementType()) {
       case STATEMENT:
           this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
           break;
       case PREPARED:
           this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
           break;
       case CALLABLE:
           this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
           break;
       default:
           throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
      }  }

现在先以 PreparedStatementHandler 预处理为例,接着Sql的执行链路来分析, StatementHandler::query() 到 StatementHandler::execute() 真正执行Sql查询操作。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
   String sql = boundSql.getSql();   statement.execute(sql);   return resultSetHandler.handleResultSets(statement);
}

但执行真正查询操作之前,还进行哪些处理呢? 还会进行 ParameterHandler 对 SQL 参数的预处理: 对参数进行动态Sql映射,那么 ParameterHandler 又如何实现对参数进行动态映射的呢?

ParameterHandler

ParameterHandler 参数处理器, 用来设置参数规则的,负责为sql 语句参数动态赋值,其有两个接口

  • getParameterObject:用于读取参数
  • setParameters: 用于对 PreparedStatement 的参数赋值

当 SimpleExecutor 执行构造 PreparedStatementHandler 完,会调用 parameterize() 方法将 PreparedStatement 对象里SQL转交 ParameterHandler 实现类 DefaultParameterHandler::setParameters() 方法 设置 PreparedStatement 的占位符参数 。

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;
}

DefaultParameterHandler::setParameters() 如何对SQL进行动态赋值呢? 在执行前将已装载好的BoundSql对象信息进行使用

public void setParameters(PreparedStatement ps) {
   ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());    //获取待动态赋值参数列表的封装parameterMappings   List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
   if (parameterMappings != null) {     for (int i = 0; i < parameterMappings.size(); i++) {
       ParameterMapping parameterMapping = parameterMappings.get(i);
         //是否为输入参数
       if (parameterMapping.getMode() != ParameterMode.OUT) {
         Object value;
         //获取待动态参数属性名
         String propertyName = parameterMapping.getProperty();
         if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
           value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
           value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
           value = parameterObject;
        } else {
           MetaObject metaObject = configuration.newMetaObject(parameterObject);
           value = metaObject.getValue(propertyName);
        }
         //// 在通过 SqlSource 的parse 方法得到parameterMappings 的具体实现中,我们会得到parameterMappings 的 typeHandler
         TypeHandler typeHandler = parameterMapping.getTypeHandler();
         //获取jdbc数据类型
         JdbcType jdbcType = parameterMapping.getJdbcType();
         if (value == null && jdbcType == null) {
           jdbcType = configuration.getJdbcTypeForNull();
        }
         try {
           //
           typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
           throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}

执行完SQL 参数的预处理,当 StatementHandler::execute() 真正执行查询操作执行完后,有返回结果,需要对返回结果进行 ResultSetHandler 处理,现在看看最后的结果的处理流程。

ResultSetHandler

ResultSetHandler 结果解析器,将查询结果的 ResultSet 转换成映射的对应结果( java DTO 等),其有三接口

  • handleResultSets() :处理结果集
  • handleCursorResultSets() :批量处理结果集
  • handleOutputParameters() :处理存储过程返回的结果集

其默认的实现为 DefaultResultSetHandler ,主要功能为:

  • 处理 Statement 执行后产生的结果集生成相对的输出结果、
  • 处理存储过程执行后的输出参数

那看看 DefaultResultSetHandler::handleResultSets() 如何处理?

  • 当有多个 ResultSet 的结果集合,每个ResultSet对应一个Object 对象,如果不考虑存储过程,普通的查询只有一个ResultSet
  • ResultSetWrapper 封装了 ResultSet 结果集,其属性包含 ResultSet , ResultMap 等
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {   ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
   //当有多个ResultSet的结果集合,每个ResultSet对应一个Object 对象
   final List<Object> multipleResults = new ArrayList<>();   int resultSetCount = 0;
   //获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
   ResultSetWrapper rsw = getFirstResultSet(stmt);   //获得 ResultMap 数组
   List<ResultMap> resultMaps = mappedStatement.getResultMaps();   int resultMapCount = resultMaps.size();   validateResultMapsCount(rsw, resultMapCount); // <3.1> 校验
   while (rsw != null && resultMapCount > resultSetCount) {
       //获得 ResultMap 对象
       ResultMap resultMap = resultMaps.get(resultSetCount);       //处理 ResultSet ,将结果添加到 multipleResults 中
       handleResultSet(rsw, resultMap, multipleResults, null);       //获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
       rsw = getNextResultSet(stmt);       //清理
       cleanUpAfterHandlingResultSet();       // resultSetCount ++
       resultSetCount++;  }   String[] resultSets = mappedStatement.getResultSets();   if (resultSets != null) {
       while (rsw != null && resultSetCount < resultSets.length) {
           ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);           if (parentMapping != null) {
               String nestedResultMapId = parentMapping.getNestedResultMapId();               ResultMap resultMap = configuration.getResultMap(nestedResultMapId);               handleResultSet(rsw, resultMap, null, parentMapping);          }           rsw = getNextResultSet(stmt);           cleanUpAfterHandlingResultSet();           resultSetCount++;      }  }   //如果是 multipleResults 单元素,则取首元素返回
   return ollapseSingleResultList(multipleResults);
}

其实在 ResultSetHandler 结果集处理是比较复杂的,这里只是简单的介绍一下,有兴趣的可以再深入研究一下,后期有空也会写。

执行到这边,Mybatis SQL执行基本完了,会把转换后的结果集返回到操作者。

结论

在SQL执行过程主要涉及了 SqlSession , MapperProxy , Executor , StatementHandler , ParameterHandler 以及 ResultSetHandler ,包括参数动态绑定,Sql执行查询数据库数据,结果返回集映射等,而且每个环节涉及的内容都很多,每个接口都可以抽出单独分析,后续有时间再一一详细的看看。后面还是再分析一下插件的应用。

深度解读MyBatis源码 - 从SqlSession到SQL执行入口 Hello!大家好,今天我们来一场深度的MyBatis源码之旅。本篇文章的目标是让你彻底搞懂MyBatis中SqlSession的工作原理及其与SQL执行的关系。SqlSession作为MyBatis与数据库沟通的“桥梁”,在数据持久化的流程中扮演了核心角色。SqlSession可以说是MyBatis操作数据库的“会话窗口”,也是我们用来实现增删查改的最基础工具。虽然MyBatis帮我们封装了很多方便的功能,但理解它的内部原理能帮我们写出更优雅、更高效的代码。 阅读详情

相关推荐

开源框架面试之MyBatis面试题

文章目录1、什么是 MyBatis?2、讲下MyBatis的缓存3、Mybatis是如何进行分页的?分页插件的原理是什么?4、简述Mybatis的插件运行原理,以及如何编写一个插件?5、Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql 的执行原理吗?6、#{}和${}的区别是什么?7、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?8、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?9、MyBatis 与 Hibernate 有哪些不同?10、

用文字记录下每一个瞬间的感动和每一次思考的火花 9万+

三、SqlSession的创建以及执行流程

SqlSession是通过SqlSessionFactory创建的,封装了Execcutor对象获取Mapper接口是通过动态代理完成的,使用MapperProxyFactory创建Mapper代理对象,执行的时候通过MapperMethod中封装的SqlCommand获取绑定的sql,通过MethodSignature确定接口的返回值,最终统一调用Execcutor的逻辑完成整个数据库的操作。

Fighting_Man的博客 3223

从DC到DCG:手把手教你搭建物理感知综合流程(含DEF文件处理避坑指南)

本文详细介绍了从Design Compiler(DC)过渡到DC Topographical(DCT)和DC Graphical(DCG)的完整操作框架,特别针对DEF文件处理提供避坑方案。通过环境准备、工具升级、DEF文件处理、DCG综合配置等步骤,帮助开发者高效应对28nm以下工艺节点的物理感知综合挑战,显著提升时序收敛效率。

weixin_27966471的博客 380

MyBatis 中如何执行 SQL 语句

MyBatis 提供了多种方式来执行 SQL 语句,包括直接使用 SqlSession 执行、使用映射器执行、使用 SqlSessionTemplate 执行等。在执行 SQL 语句时,需要将 SQL 语句中的参数和返回结果。

stormjun的博客 1万+

解密CANoe数据回放:从BLF到ASC的格式转换与优化策略

本文深入解析CANoe数据回放中BLF与ASC格式的转换与优化策略,帮助工程师高效处理汽车电子测试数据。详细介绍了两种格式的特性、转换方法及性能优化技巧,包括CANoe内置功能、命令行自动化和第三方工具应用,提升故障诊断和系统验证效率。

weixin_28331555的博客 536

手把手教你解决Kernel panic报错:init找不到的5种排查方法(附NFS挂载实战)

本文深入解析了Linux系统启动时出现“Kernel panic - not syncing: No working init found”报错的根本原因,并提供了五种系统性的排查方法。文章结合NFS挂载根文件系统的实战场景,详细阐述了从动态链接库依赖、内核引导参数、NFS权限配置、文件系统完整性到内核配置干扰的完整排查路径,帮助开发者快速定位并解决这一常见的系统启动故障。

weixin_29159995的博客 193

【Mybatis源码阅读】sqlSession的执行流程

首先看一个例子,看一下怎么把数据写入数据库 这个是最常见的执行方式,好的。接下来我们可以看一下是如何执行的。 可以看到SqlSession刚好包装了所有的执行方法,那么这些个方法是如何一步步的执行的呢。 首先看一下参数statement是什么,其实如果看过我上一篇对Maybaties的初始化的文章的同学就知道了,其实这个statement就是Mapperstatment的i...

worn_xiao的博客 670

GPU运维面试题

【代码】GPU运维面试题。

u011220233的博客 696

Mybatis-plusMybatis 通过获取sqlSession执行原生sql(执行程序代码中sql字符串)

Mybatis-plus/Mybatis通过获取sqlSession执行原生jdbc执行sql; 此处demo只写了执行查询sql,有需要可以执行增删改查都可,与原生jdbc调用方式一样。 @Component @Slf4j public class DBUtils { private static final String PREFIX_LOG = "【自定义DB工具】"; @Autowired private SqlSessionTemplate sqlSessionTemplate;

m0_54883970的博客 2972

WRF后处理总结:wrf-python与NCL在WRF后处理中的基本应用——变量提取、计算与可视化

判断需要提取的变量与诊断量确定后,使用getvar函数进行提取。可使用ncdump-h命令查看wrfout中的变量,wrf中的诊断量分类也可从官方文档处查阅。判断绘图类型所需的可视化绘图类型,决定了你在WRF后处理中的计算步骤。1D点线图:ll_to_xy interpline2D填色图:interplevel3D剖面图:vinterp vertcross绘图、美化色标、XY轴、标签的设置等等。

weixin_43750300的博客 1万+

SAPIEN ManiSkill3 入门教程(一)

请注意,table.set_pose(pose) 设置的是演员在世界帧中的坐标系姿势。移除的实体可以再次添加到场景中,但已在场景中的实体则不能再次添加。显示器可以是物理的(如显示器),也可以是虚拟的(如 VNC)。我们可以通过 get_pose()、get_linear_velocity() 和 get_angular_velocity()获取演员的运动量(姿势、线速度、角速度)。现在,让我们创建一个斜坡。例如,您可能需要一个简单的碰撞形状来进行快速模拟,但却需要一个复杂的视觉形状来进行逼真的渲染。

机器人控制与仿真应用 3074

mybatis 执行sql_面试:谈谈你对MyBatis执行过程之SQL执行过程理解

前言在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试也会觉得你已掌握Mybatis知识点了。SQL执行过程简介经过MyBatis初始化加载Sql执行过程所需的信息后,我们就可以通过...

weixin_39662228的博客 131

手把手完整实现STM32+ESP8266+MQTT+ThingsCloud云平台+Android App及iOS App及微信小程序及Web App应用

本教程文章完整记录实现STM32+ESP8266+MQTT+ThingsCloud云平台+Android App及iOS App及微信小程序及Web App应用。

嵌入式阿齐Archie 1万+

mybatis一个方法执行多条sql_面试:谈谈你对MyBatis执行过程之SQL执行过程理解

欢迎关注公众号【Ccww技术博客】,原创技术文章第一时间推出前言在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试也会觉得你已掌握Mybatis知识点了。SQL执行过程简介...

weixin_36158843的博客 427

微信小程序一键跳转公众号(使用 official-account 组件)

在当今移动互联网时代,用户体验已成为应用成功的关键因素之一。微信小程序作为一种便捷的应用形式,广泛应用于各个行业,为用户提供了丰富的服务和信息。然而,如何进一步提升用户体验,使其在小程序中能够更方便地获取相关信息,成为开发者需要关注的重要课题。实现一键跳转到公众号的功能,正是一个有效的解决方案。通过这一功能,用户可以轻松访问公众号,获取更多内容和服务,从而增强与品牌的互动和粘性。本文将详细介绍如何在微信小程序中实现这一功能,并提供相关的操作步骤和注意事项。

不迁怒,不贰过。小知识,大智慧。 3127

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

hive底层原理 sql执行过程_面试:谈谈你对MyBatis执行过程之SQL执行过程理解

前言在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试也会觉得你已掌握Mybatis知识点了。SQL执行过程简介经过MyBatis初始化加载Sql执行过程所需的信息后,我们就可以通过...

weixin_32540109的博客 206

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

mybatis delete返回值_面试:谈谈你对MyBatis执行过程之SQL执行过程理解

欢迎关注公众号【Ccww技术博客】,原创技术文章第一时间推出前言在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试也会觉得你已掌握Mybatis知识点了。SQL执行过程简介...

weixin_39606911的博客 98

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

面试:请你谈谈对mybatis的理解

MyBatis是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。 在mybstis还没有出来之前,我们用的都是纯JDBC,纯JDBC也有些缺点的,SQL语句和JAVA代码是写在一起的,而这是两种不同的语言。重复的创建连接,效率不高。事务处理不灵活,不是面向切面,查询结果集的映射不方便。 而mybatis恰好解决了这些问题。SQL语句全部独立的保存在xml种,使用连接池技术,灵活高效。事务处理简单,面向切面编程。查询的结果集的映射自动化动态SQL标签。 mybatis用分层的方式来表示。可以分为基础

婷er的博客 2416

sqlsession执行sql_听我的,看完MyBatis执行过程后你再去面试

前言在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试也会觉得你已掌握Mybatis知识点了。SQL执行过程简介经过MyBatis初始化加载Sql执行过程所需的信息后,我们就可以通过...

weixin_39523625的博客 588

MyBatis执行SQL的两种方式

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

2401_84009300的博客 1090

【129期】面试官:MyBatis 你只写了接口为啥就能执行 SQL 啊?

三个工作日收到了offer,头条面试体验还是很棒的,这次的头条面试好像每面技术都问了我算法,然后就是中间件、MySQL、Redis、Kafka、网络等等。第一个是算法关于算法,我觉得最好的是刷题,作死的刷的,多做多练习,加上自己的理解,还是比较容易拿下的。而且,我貌似是将《算法刷题LeetCode中文版》、《算法的乐趣》大概都过了一遍,尤其是这本。

2401_83915450的博客 1047

MyBatis 从入门到精通:面试必备知识点全解析

MyBatis 核心知识点解析 MyBatis 是 Java 生态中广泛使用的持久层框架,通过 SQL 与代码解耦提高开发效率。核心组件包括: SqlSessionFactory:线程安全,全局唯一的工厂类 SqlSession:非线程安全的会话对象 Mapper 接口:通过动态代理实现 SQL 映射 面试重点: #{}与${}区别:前者预编译防注入,后者直接拼接有风险 结果映射:resultType处理简单映射,resultMap支持复杂关联查询 动态SQL:if/choose/foreach等标签实现灵

2301_79536841的博客 2445

面试官:mybatis你只写了接口为啥就能执行sql啊?

静态代理又是一年毕业季,很多小伙伴开始去大城市打拼。来大城市第一件事就是租房,免不了和中介打交道,因为很多房东很忙,你根本找不到他。从这个场景中就可以抽象出来代理模式IS...

纯洁的微笑 4297

hive底层原理 sql执行过程_MyBatis 源码解析:通过源码深入理解 SQL 的执行过程

MySQL复习:20道常见面试题(含答案)+21条MySQL性能调优经验疫情期间“闭关修炼”,吃透这本Java核心知识,跳槽面试不心慌2020“闭关”跳槽季,啃透分布式三大技术:限流、缓存、通讯2020春招必备:MySQL(20)与Redis(20),不看答案能答对几道?一、前言1、mybatis框架图如上为mybatis的框架图,在这篇文章中通过源码来重点看下数据处理层中的参数映射,SQL解析,...

weixin_28856787的博客 195
上一篇: 身为程序员,你连网络协议都不懂,你拿什么年薪百万?
下一篇: GitHub上标星86.1k的Java学习项目!已整理成了文档版本,建议赶紧收藏!
Java奥利给
博客等级 码龄6年 150粉丝 154原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值