• MyBatis原理分析之四:一次SQL查询的源码分析


    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的。

     

    准备工作

    Mybatis完成一次SQL查询需要使用的代码如下:

     

    Java代码  收藏代码
    1. String resource = "mybatis.cfg.xml";  
    2.         Reader reader = Resources.getResourceAsReader(resource);  
    3.         SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);  
    4.           
    5.       <strong>  </strong>SqlSession session = ssf.openSession();  
    6.           
    7.         try {  
    8.             UserInfo user = (UserInfo) session.selectOne("User.selectUser""1");  
    9.             System.out.println(user);  
    10.         } catch (Exception e) {  
    11.             e.printStackTrace();  
    12.         } finally {  
    13.             session.close();  
    14.         }  

     

    本次我们需要进行深入跟踪分析的是:

     

    Java代码  收藏代码
    1. SqlSession session = ssf.openSession();  
    2.         
    3. UserInfo user = (UserInfo) session.selectOne("User.selectUser""1");  
     

     

     

    源码分析

     

    第一步:打开一个会话,我们看看里面具体做了什么事情。

     

    Java代码  收藏代码
    1. SqlSession session = ssf.openSession();  

     

     

    DefaultSqlSessionFactory的 openSession()方法内容如下:

     

    Java代码  收藏代码
    1. public SqlSession openSession() {  
    2.     return openSessionFromDataSource(configuration.getDefaultExecutorType(), nullfalse);  
    3.   }  
     

     

    跟进去,我们看一下openSessionFromDataSource方法到底做了啥:

     

    Java代码  收藏代码
    1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  
    2.     Connection connection = null;  
    3.     try {  
    4.       final Environment environment = configuration.getEnvironment();  
    5.       final DataSource dataSource = getDataSourceFromEnvironment(environment);  
    6.       TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);  
    7.       connection = dataSource.getConnection();  
    8.       if (level != null) {  
    9.         connection.setTransactionIsolation(level.getLevel());  
    10.       }  
    11.       connection = wrapConnection(connection);  
    12.       Transaction tx = transactionFactory.newTransaction(connection, autoCommit);  
    13.       Executor executor = configuration.newExecutor(tx, execType);  
    14.       return new DefaultSqlSession(configuration, executor, autoCommit);  
    15.     } catch (Exception e) {  
    16.       closeConnection(connection);  
    17.       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);  
    18.     } finally {  
    19.       ErrorContext.instance().reset();  
    20.     }  
    21.   }  
     

     

    这里我们分析一下这里所涉及的步骤:

    (1)获取前面我们加载配置文件的环境信息,并且获取环境信息中配置的数据源。

    (2)通过数据源获取一个连接,对连接进行包装代理(通过JDK的代理来实现日志功能)。

    (3)设置连接的事务信息(是否自动提交、事务级别),从配置环境中获取事务工厂,事务工厂获取一个新的事务。

    (4)传入事务对象获取一个新的执行器,并传入执行器、配置信息等获取一个执行会话对象。

     

    从上面的代码我们可以得出,一次配置加载只能有且对应一个数据源。对于上述步骤,我们不难理解,我们重点看看新建执行器和DefaultSqlSession。

    首先,我们看看newExecutor到底做了什么?

     

    Java代码  收藏代码
    1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  
    2.    executorType = executorType == null ? defaultExecutorType : executorType;  
    3.    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;  
    4.    Executor executor;  
    5.    if (ExecutorType.BATCH == executorType) {  
    6.      executor = new BatchExecutor(this, transaction);  
    7.    } else if (ExecutorType.REUSE == executorType) {  
    8.      executor = new ReuseExecutor(this, transaction);  
    9.    } else {  
    10.      executor = new SimpleExecutor(this, transaction);  
    11.    }  
    12.    if (cacheEnabled) {  
    13.      executor = new CachingExecutor(executor);  
    14.    }  
    15.    executor = (Executor) interceptorChain.pluginAll(executor);  
    16.    return executor;  
    17.  }  

     

    上面代码的执行步骤如下:

    (1)判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE。

    (2)根据执行器类型返回不同类型的执行器(执行器有三种,分别是 BatchExecutor、SimpleExecutor和CachingExecutor,后面我们再详细看看)。

    (3)跟执行器绑定拦截器插件(这里也是使用代理来实现)。

     

    DefaultSqlSession到底是干什么的呢?

    DefaultSqlSession实现了SqlSession接口,里面有各种各样的SQL执行方法,主要用于SQL操作的对外接口,它会的调用执行器来执行实际的SQL语句。

     

    接下来我们看看SQL查询是怎么进行的

     

     

    Java代码  收藏代码
    1. UserInfo user = (UserInfo) session.selectOne("User.selectUser""1");  

     实际调用的是DefaultSqlSession类的selectOne方法,该方法代码如下:

     

    Java代码  收藏代码
    1. public Object selectOne(String statement, Object parameter) {  
    2.     // Popular vote was to return null on 0 results and throw exception on too many.  
    3.     List list = selectList(statement, parameter);  
    4.     if (list.size() == 1) {  
    5.       return list.get(0);  
    6.     } else if (list.size() > 1) {  
    7.       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());  
    8.     } else {  
    9.       return null;  
    10.     }  
    11.   }  
     

    我们再看看selectList方法(实际上是调用该类的另一个selectList方法来实现的):

     

    Java代码  收藏代码
    1. public List selectList(String statement, Object parameter) {  
    2.     return selectList(statement, parameter, RowBounds.DEFAULT);  
    3.   }  
    4.   
    5.   public List selectList(String statement, Object parameter, RowBounds rowBounds) {  
    6.     try {  
    7.       MappedStatement ms = configuration.getMappedStatement(statement);  
    8.       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
    9.     } catch (Exception e) {  
    10.       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
    11.     } finally {  
    12.       ErrorContext.instance().reset();  
    13.     }  
    14.   }  

     

    第二个selectList的执行步骤如下:

    (1)根据SQL的ID到配置信息中找对应的MappedStatement,在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析并放到一个MappedStatement里面,并将MappedStatement对象放到一个Map里面进行存放,Map的key值是该SQL块的ID。

    (2)调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象(此处为空)和结果处理方式。

     

    好了,目前只剩下一个疑问,那就是执行器到底怎么执行SQL的呢?

     

    上面我们知道了,默认情况下是采用SimpleExecutor执行的,我们看看这个类的doQuery方法:

     

    Java代码  收藏代码
    1. public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
    2.     Statement stmt = null;  
    3.     try {  
    4.       Configuration configuration = ms.getConfiguration();  
    5.       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler);  
    6.       stmt = prepareStatement(handler);  
    7.       return handler.query(stmt, resultHandler);  
    8.     } finally {  
    9.       closeStatement(stmt);  
    10.     }  
    11.   }  

     

    doQuery方法的内部执行步骤:

    (1) 获取配置信息对象。

    (2)通过配置对象获取一个新的StatementHandler,该类主要用来处理一次SQL操作。

    (3)预处理StatementHandler对象,得到Statement对象。

    (4)传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理。

     

     

    我们看一下newStatementHandler到底做了什么?

     

     

    Java代码  收藏代码
    1. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {  
    2.     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);  
    3.     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);  
    4.     return statementHandler;  
    5.   }  

     

     

    上面代码的执行步骤:

    (1)根据相关的参数获取对应的StatementHandler对象。

    (2)为StatementHandler对象绑定拦截器插件。

     

    RoutingStatementHandler类的构造方法RoutingStatementHandler如下:

     

    Java代码  收藏代码
    1. public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {  
    2.   
    3.    switch (ms.getStatementType()) {  
    4.      case STATEMENT:  
    5.        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler);  
    6.        break;  
    7.      case PREPARED:  
    8.        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler);  
    9.        break;  
    10.      case CALLABLE:  
    11.        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler);  
    12.        break;  
    13.      default:  
    14.        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());  
    15.    }  
    16.   
    17.  }  

     

    根据 MappedStatement对象的StatementType来创建不同的StatementHandler,这个跟前面执行器的方式类似。StatementType有STATEMENT、PREPARED和CALLABLE三种类型,跟JDBC里面的Statement类型一一对应。

     

    我们看一下prepareStatement方法具体内容:

    Java代码  收藏代码
    1. private Statement prepareStatement(StatementHandler handler) throws SQLException {  
    2.    Statement stmt;  
    3.    Connection connection = transaction.getConnection();  
    4.    //从连接中获取Statement对象  
    5.    stmt = handler.prepare(connection);  
    6.    //处理预编译的传入参数  
    7.    handler.parameterize(stmt);  
    8.    return stmt;  
    9.  }  
     






  • 相关阅读:
    【转】centos7升级git版本
    小程序购物车抛物线动画(通用)
    IDEA高级操作
    JAVA获取各种路径
    这些SpringBoot天生自带Buff工具类你都用过哪些?
    搜狗输入法简繁问题
    Java8 Stream流递归,几行代码搞定遍历树形结构
    SpringBoot 启动时实现自动执行代码的几种方式讲解
    公司用的 MySQL 团队开发规范,非常详细,建议收藏!
    Springboot整合websocket全面解析
  • 原文地址:https://www.cnblogs.com/jeffen/p/6251197.html
Copyright © 2020-2023  润新知