Mybatis执行SQL过程

news/2024/7/4 7:15:03

文章目录

  • 1. 相关代码
  • 2. 创建SqlSession
  • 3. 创建Mapper代理对象
  • 4.sql的执行
    • 4.1 MapperProxy.invoke()
    • 4.2 mapperMethod.execute()
    • 4.3 sqlSession.selectOne
    • 4.4 CachingExecutor.query()
    • 4.5 BaseExecutor.query方法
    • 4.6 SimpleExecutor.doQuery方法

1. 相关代码

@Test
    public void test2() throws Exception{
        // 1.获取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.加载解析配置文件并获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.根据SqlSessionFactory对象获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 4.通过SqlSession中提供的 API方法来操作数据库
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectUserById(1);
        System.out.println(user);
        // 5.关闭会话
        sqlSession.close();
    }

2. 创建SqlSession

 SqlSession sqlSession = factory.openSession();

  @Override
  public SqlSession openSession() {
    // configuration.getDefaultExecutorType() 获取默认的 Executor的类型 SIMPLE
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

获取默认执行器ExecutorType.SIMPLE。

  • 继续看openSessionFromDataSource:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂,之前解析全局文件的时候创建过
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 根据事务工厂和默认的执行器类型,创建执行器 >>
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  • 根据事务工厂和默认的执行器类型,创建执行器
  /**
   * 1. 完成执行器的创建(Executor)
   * 2. 完成二级缓存的设置  装饰器模式
   * 3. 完成插件逻辑的植入  装饰器模式
   * @param transaction  事务管理器
   * @param executorType 默认是 Simple
   * @return
   */
  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) { // 针对 Statement 对象做缓存
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 默认 SimpleExecutor 每一次只是SQL操作都创建一个新的Statement对象
      executor = new SimpleExecutor(this, transaction);
    }
    // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
    // 映射文件中 <cache> 标签 --> 创建 Cache对象
    // settings 中的 cacheEnabled = true 真正的对 Executor 做了缓存的增强
    if (cacheEnabled) {
      // 穿衣服的事情 --> 装饰器模式
      executor = new CachingExecutor(executor);
    }
    // 植入插件的逻辑,至此,四大对象已经全部拦截完毕
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

最后返回DefaultSqlSession对象。

总结:创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,Executor是SQL的实际执行对象。

3. 创建Mapper代理对象

 // 4.通过SqlSession中提供的 API方法来操作数据库
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);

继续F7,

 @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

继续F7,


  /**
   * 怎么获取 Mapper 接口的代理对象
   * 存储关系:
   *    1.系统启动的时候
   *       接口类型 --》 MapperProxyFactory --》 MapperProxy --> MapperMethod
   *       的映射关系存储在 MapperRegistry 中
   *
   *  获取Mapper代理对象
   *      根据传递的接口类型 从 MapperRegistry 中获取对应的
   *      MapperProxyFactory对象
   *      然后 根据 MapperProxyFactory 对象获取 MapperProxy对象
   * @param type
   * @param sqlSession
   * @param <T>
   * @return
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // mapperRegistry中注册的有Mapper的相关信息 在解析映射文件时 调用过addMapper方法
    return mapperRegistry.getMapper(type, sqlSession);
  }

继续F7,

 /**
   * 获取Mapper接口对应的代理对象
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取Mapper接口对应的 MapperProxyFactory 对象
    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);
    }
  }

进入newInstance方法

这里创建MapperProxy对象

 public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

实际底层是JDK动态代理。

 /**
   * 创建实现了 mapperInterface 接口的代理对象
   */
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

在代码中发现代理对象是通过JDK动态代理创建,返回的代理对象。而且里面也传递了一个实现了InvocationHandler接口的触发管理类。
在这里插入图片描述

总结:获得Mapper对象的过程,实质上是获取了一个JDK动态代理对象(类型是$ProxyN)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。

4.sql的执行

4.1 MapperProxy.invoke()

User user = mapper.selectUserById(1);

由于所有的Mapper都是JDK动态代理对象,所以任意的方法都是执行触发管理类MapperProxy的invoke()方法。

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,无需走到执行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
        // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

然后进入到PlainMethodInvoker的invoke方法.

 @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // SQL执行的真正起点
      return mapperMethod.execute(sqlSession, args);
    }

4.2 mapperMethod.execute()

 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法
      case INSERT: {
        // 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来
        Object param = method.convertArgsToSqlCommandParam(args);
        // sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法
        // rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换
        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()) {
          // 返回值为空 且 ResultSet通过 ResultHandler处理的方法
          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);
          // 普通 select 语句的执行入口 >>
          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;
  }

在这一步,根据不同的type(INSERT、UPDATE、DELETE、SELECT)和返回类型:

1)调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。

2)调用sqlSession的insert()、update()、delete()、selectOne ()方法。我们以查询为例,会走到selectOne()方法。

4.3 sqlSession.selectOne

 @Override
  public <T> T selectOne(String statement, Object parameter) {
    // 来到了 DefaultSqlSession
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

在SelectList()中,我们先根据command name(Statement ID)从Configuration中拿到MappedStatement。ms里面有xml中增删改查标签配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等.


  /**
   *  SqlSession 是调用者完成数据库操作的 接口
   *      那么在SqlSession 内部其实是通过 Executor来完成具体的 数据库操作的
   * @param statement  com.bobo.mybati.dao.UserMapper.query
   * @param parameter
   * @param rowBounds
   * @param <E>
   * @return
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // MappedStatement 记录了一个 select 标签所具有的所有的信息
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰  wrapCollection(parameter) 对参数进行处理
      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的query()方法。

Executor是第二步openSession的时候创建的,创建了执行器基本类型之后,依次执行了二级缓存装饰,和插件包装。

所以,如果有被插件包装,这里会先走到插件的逻辑。如果没有显式地在settings中配置cacheEnabled=false,再走到CachingExecutor的逻辑,然后会走到BaseExecutor的query()方法。

先忽略插件。

4.4 CachingExecutor.query()

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建CacheKey:什么样的SQL是同一条SQL? >>
    // select * from t_user   select id,username,password form t_user
    // 根据特定的规则生成一个key 保证不冲突
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    // cache 对象是在哪里创建的?  XMLMapperBuilder类 xmlconfigurationElement()
    // 由 <cache> 标签决定  控制二级缓存的开关有两个 一个是 CacheEnabled 全局配置文件中的settings 配置  --》 装饰执行器 Executor  CachingExecutor
    //                                         一个是 cache 标签 映射文件中的配置  创建 Cache 对象
    if (cache != null) {
      // flushCache="true" 清空一级二级缓存 >>
      flushCacheIfRequired(ms);
      // 在 select 标签中 配置了 useCache 属性
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 获取二级缓存
        // 缓存通过 TransactionalCacheManager、TransactionalCache 管理
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) 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;
      }
    }
    // 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

4.5 BaseExecutor.query方法


  /**
   * queryStack 和 占位符的作用
   * 1.查询 学校    正常查询 --> 放入一级缓存
   * 2.查询  学校的学生  正常查询 --> 放入一级缓存
   * 3.查询  学生的学校  延迟加载,从缓存中获取
   */
  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 异常体系之 ErrorContext
    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()) {
      // flushCache="true"时,即使是查询,也清空一级缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 防止递归查询重复处理缓存
      queryStack++;
      // 查询一级缓存
      // ResultHandler 和 ResultSetHandler的区别  一级缓存
      list = resultHandler == null ? (List<E>) 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();
      // 如果LocalCacheScope的值设置为 STATEMENT 则一级缓存失效
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  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 {
      // 三种 Executor 的区别,看doUpdate
      // 默认Simple
      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;
  }

4.6 SimpleExecutor.doQuery方法


  /**
   * 到了 具体的数据库操作的步骤了  JDBC
   *     Connection
   *     Statement
   *     PreparedStatement
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param boundSql
   * @param <E>
   * @return
   * @throws SQLException
   */
  @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();
      // 注意,已经来到SQL处理的关键对象 StatementHandler >>  同时会完成  parameterHandler和resultSetHandler的实例化
      // 默认创建的是 PreparedStatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 获取一个 Statement对象  对占位符处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 执行查询
      return handler.query(stmt, resultHandler);
    } finally {
      // 用完就关闭
      closeStatement(stmt);
    }
  }

执行查询操作,如果有插件包装,会先走到被拦截的业务逻辑。

// 执行查询
      return handler.query(stmt, resultHandler);

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 如果放开日志 PreparedStatement 其实是一个 PreparedStatementLogger 的代理对象
    PreparedStatement ps = (PreparedStatement) statement;
    // 到了JDBC的流程
    ps.execute(); // 如果是代理对象的话 同样的会进入到 invoke 方法中
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

执行PreparedStatement的execute()方法,后面就是JDBC包中的PreparedStatement的执行了。
ResultSetHandler处理结果集,如果有插件包装,会先走到被拦截的业务逻辑。


http://www.niftyadmin.cn/n/4799789.html

相关文章

Python安装插件/pycharm安装插件的方法

通过命令行安装 python通过命令直接安装插件 例如&#xff1a; 安装lxml&#xff1a;pip3 install lxml 通过pycharm安装 步骤&#xff1a;【PyCharm】>【file】>【settings】>【Project Interpreter】>【】 >【lxml】>【install】

Python爬虫学习汇总(持续更新)

最近在研究爬虫&#xff0c;我把和爬虫相关的内容都总结到这了&#xff0c;这持续更新。 1、使用Python爬取妹子网的图片&#xff0c;批量下载&#xff0c;附带源码&#xff0c;超详细 2、爬虫实例源码下载&#xff0c;修改目录直接能运行 3、Python爬虫之xpath的基本使用&a…

关于重复提交数据问题的解决方案

关于重复提交数据问题的解决方案参考文章&#xff1a; &#xff08;1&#xff09;关于重复提交数据问题的解决方案 &#xff08;2&#xff09;https://www.cnblogs.com/alicePanZ/p/5071045.html &#xff08;3&#xff09;https://www.codeprj.com/blog/4d60c51.html 备忘…

IntelliJ IDEA 激活 及 License Server 安装使用 Window篇

IDEA版本&#xff1a; IntelliJ IDEA 2017.2Build #IU-172.3317.76, built on July 15, 2017Licensed to Administrator JRE: 1.8.0_152-release-915-b5 amd64JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.oWindows 7 6.1 一、激活 IntelliJ IDEA下载地址&#xff1a;https…

Window 下ChromeDriver配置、安装与验证

chromedriver的下载和配置 为何要安装&#xff1f; 最近做python爬虫&#xff0c;用到了selenium&#xff0c;所以就得安装一下chromedriver&#xff0c;如果安装的话就会报错&#xff1a;chromedriver executable needs to be in PATH等问题。 使用selenium时&#xff0c;需…

C++ 中 使用vtkGDCMImageReader.h遇到的unresolved external symbol错误解决办法

1. 错误信息一 2>gdcmMSFF.lib(gdcmUIDGenerator.obj) : error LNK2019: unresolved external symbol __imp__UuidCreate4 referenced in function "protected: static bool __cdecl gdcm::UIDGenerator::GenerateUUID(unsigned char *)" (?GenerateUUIDUIDGener…

windows xshell6启动时msvcp110.dll、msvcr110.dll、mfc110u.dll丢失解决

经过重重的磨难&#xff0c;终于解决了&#xff0c;真是踩了好多坑。 启动xshell时程序报错如下&#xff1a; 无法启动此程序&#xff0c;因为计算机中丢失MSVCR110.dll。尝试重新安装该程序以解决此问题。 尝试了好多种办法&#xff1a; 1、百度下载修复工具失败。 2、下载…

oracle10g异常日志查看

oracle10g异常日志查看参考文章&#xff1a; &#xff08;1&#xff09;oracle10g异常日志查看 &#xff08;2&#xff09;https://www.cnblogs.com/wanghonghu/archive/2012/04/18/2455651.html &#xff08;3&#xff09;https://www.javazxz.com/thread-4444-1-1.html 备…