`

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

阅读更多

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

 

准备工作

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

 

 

String resource = "mybatis.cfg.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
        
        SqlSession session = ssf.openSession();
        
        try {
        	UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            session.close();
        }

 

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

 

  SqlSession session = ssf.openSession();
        
  UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
 

 

 

源码分析

 

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

 

SqlSession session = ssf.openSession();

 

 

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

 

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
 

 

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

 

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Connection connection = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final DataSource dataSource = getDataSourceFromEnvironment(environment);
      TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      connection = dataSource.getConnection();
      if (level != null) {
        connection.setTransactionIsolation(level.getLevel());
      }
      connection = wrapConnection(connection);
      Transaction tx = transactionFactory.newTransaction(connection, autoCommit);
      Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeConnection(connection);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
 

 

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

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

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

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

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

 

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

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

 

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

 

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

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

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

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

 

DefaultSqlSession到底是干什么的呢?

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

 

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

 

 

UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");

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

 

public Object selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List list = 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方法(实际上是调用该类的另一个selectList方法来实现的):

 

public List selectList(String statement, Object parameter) {
    return selectList(statement, parameter, RowBounds.DEFAULT);
  }

  public List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

 

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

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

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

 

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

 

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

 

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

 

doQuery方法的内部执行步骤:

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

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

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

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

 

 

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

 

 

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

 

 

上面代码的执行步骤:

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

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

 

RoutingStatementHandler类的构造方法RoutingStatementHandler如下:

 

 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

 

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

 

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

 private Statement prepareStatement(StatementHandler handler) throws SQLException {
    Statement stmt;
    Connection connection = transaction.getConnection();
    //从连接中获取Statement对象
    stmt = handler.prepare(connection);
    //处理预编译的传入参数
    handler.parameterize(stmt);
    return stmt;
  }
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论
13 楼 showlives 2016-08-19  
     
12 楼 一键注册 2016-06-28  
还更新吗  大大
11 楼 menghe200801 2016-02-19  
dwedwedwedwe
10 楼 menghe200801 2016-02-19  
[/color][color=orange]ferfwfwefwefwefwefwef
9 楼 他大姨妈 2016-02-02  
看得刚起兴,没下文了。整个这个系列都有这个问题。不如spring源码分析写得好
8 楼 lhqzch 2015-11-14  
感觉还不错
7 楼 要什么来什么 2015-11-05  
很好,很深入
6 楼 gaogao2006551302 2014-07-11  
没写全啊,重要的代理还没讲。。。
5 楼 windy0605 2013-12-13  
meixiewan
4 楼 bluend1004 2013-08-29  
咋突然戛然而止了。。。。感觉楼主没有写完啊!!!
3 楼 liuguofeng 2013-03-31  
非常的好, 
2 楼 Half209 2012-12-20  
很好,楼主继续写啊,我还等着呢
1 楼 zwbill 2012-09-17  
讲解的很到位,学习了

相关推荐

    sql server 2005 技术内幕t-sql查询源码

    《SQL Server 2005 技术内幕:T-SQL查询源码》是一本深入探讨SQL Server 2005中T-SQL查询技术的专业书籍。T-SQL(Transact-SQL)是Microsoft SQL Server数据库管理系统中使用的SQL扩展版本,它在标准SQL的基础上增加...

    Microsoft SQL Server 2008技术内幕:T-SQL语言基础—源代码

    13. **性能优化**:T-SQL优化是数据库管理的重要部分,包括查询优化、索引策略、查询计划分析等。书中会给出实用的性能调优技巧。 通过阅读《Microsoft SQL Server 2008技术内幕:T-SQL语言基础》及其源代码,读者...

    spark sql解析-源码分析

    在这个源码分析中,我们将深入探讨Spark SQL的工作原理、主要组件以及其解析过程。 1. **DataFrame API**:DataFrame是Spark SQL的核心概念,它是一种分布式数据集合,提供了类似SQL的API用于数据操作。DataFrame是...

    易语言源码易语言SQLSERVER查询分析器源码.rar

    《易语言SQLSERVER查询分析器源码》是一个针对SQL Server数据库进行查询和分析的软件开发项目,使用易语言作为编程工具。易语言是中国自主研发的一种简单易学、功能强大的编程语言,旨在降低编程门槛,让更多的人...

    sql注入网站源码

    SQL注入是一种常见的网络安全漏洞,主要出现在使用动态SQL语句构建数据库查询的应用程序中。这个"sql注入网站源码"提供了一个具有SQL注入漏洞的ASP(Active Server Pages)网站实例,对于学习如何检测、防范和修复这...

    sql server 2005 web查询分析器(c#源码)

    SQL Server 2005 Web查询分析器是一个基于C#编程语言开发的工具,它允许用户通过Web界面来管理和分析数据库。这个工具的核心功能在于提供一个远程访问数据库的平台,尤其是对于那些需要在Web环境中对SQL Server 2005...

    数据库实验(sql server):高级SQL查询(分组、统计、嵌套、组合查询【附SQL源码.TXT】)

    这里的嵌套查询首先找到课程名为“数据库原理”的课程编号,然后使用这个编号作为外部查询的一部分,来计算选了这门课的学生数量。 #### 组合查询(IN 条件/UNION) **IN 条件**允许用户基于子查询的结果来筛选...

    DSL-SQL源码分析

    这篇博客文章"DSL-SQL源码分析"可能探讨了如何通过源码理解DSL-SQL的工作原理以及其在实际应用中的实现。 DSL-SQL的核心思想是将SQL语句转换成一种易于理解和操作的结构化表达方式,通常是通过编程语言的函数或类来...

    深入理解Spark:核心思想及源码分析.pdf

    《深入理解Spark:核心思想及源码分析》这本书旨在帮助读者深入掌握Apache Spark这一大数据处理框架的核心原理与实现细节。Spark作为一个快速、通用且可扩展的数据处理系统,已经在大数据领域得到了广泛应用。它提供...

    SQL注入源码

    SQL注入的基本原理是攻击者通过输入特殊的SQL代码,使得原本的查询逻辑发生改变,进而获取未授权的信息,修改、添加或删除数据库中的数据,甚至控制整个服务器。例如,一个简单的用户登录表单如果没有正确处理用户...

    查询分析器(C#源码)

    查询分析器是数据库管理系统中的一个重要组成部分,主要用于解析和优化SQL查询语句。在C#编程环境中,我们可以构建自己的查询分析器来实现类似的功能。本文将深入探讨查询分析器的工作原理,以及如何使用C#实现这一...

    SQLSERVER查询分析器源码

    通过阅读和学习这份源码,开发者不仅可以了解SQLSERVER查询分析器的工作原理,还能深入理解易语言在数据库应用开发中的应用技巧。 总之,基于易语言的SQLSERVER查询分析器源码为我们提供了一个研究和学习数据库操作...

    易语言SQLSERVER查询分析器

    《易语言SQLSERVER查询分析器》是一款专为易语言编程环境设计的工具,它提供了连接、操作和分析SQL Server数据库的能力。源码的开放性使得开发者可以深入理解其内部工作原理,同时也为自定义功能和扩展提供了可能。...

    Access和SQL操作源码

    总之,"Access和SQL操作源码"这个资源为初学者提供了一手的实践素材,通过阅读和分析这些代码,可以提升数据库编程能力,理解C#如何与数据库进行高效、安全的数据交互。这是一个宝贵的学习起点,能够帮助初学者奠定...

    SQLSERVER查询分析器.rar

    这个RAR压缩包可能包含了一个用易语言编写的SQL Server查询分析器的源代码,这对于理解SQL查询的工作原理以及如何与SQL Server交互非常有价值。 SQL Server查询分析器主要功能: 1. **SQL语句编写**:用户可以通过...

    PHP+Mysql 带SQL注入源码 下载

    MySQL是最常用的开源关系型数据库管理系统之一,而SQL注入则是一个常见的安全漏洞,通常发生在不正确过滤用户输入的情况下,导致恶意SQL命令被执行。 在压缩包文件 "6f3c3e6b84be4f4c83d65218d4e91b2a" 中,虽然...

    SQL组合查询和ACCESS查询模块源码

    "易语言源码大全"提供的SQL组合查询和Access查询模块的源码,可以让开发者深入研究其内部工作机制,了解如何在实际项目中应用这些查询技术。通过阅读和分析这些源码,开发者可以提高自己的编程能力,学习到如何高效...

    C# 版SQL美化解析器源码项目

    开发者可以从中学习到如何使用C#来解析SQL语句,理解SQL语法分析的原理,以及如何实现代码层面的SQL美化功能。项目的开源性质意味着任何人都可以查看、学习、修改或扩展其功能,这对于提升个人技能或改进现有项目...

    asp源码-sql注入演示源码.zip

    总之,"asp源码-sql注入演示源码.zip"提供了一个实际的环境,用于深入理解SQL注入和XSS漏洞的工作原理,以及如何防范这些威胁。通过研究并理解这些源码,开发者可以提升自己的安全意识,从而创建更安全的网络应用。

    sql通用防注入源码

    一、SQL注入原理及危害 SQL注入攻击是当用户输入的数据未经验证或过滤,直接被用作构造SQL查询的一部分时发生的。攻击者可以通过构造特定的输入,使SQL查询执行非预期的操作,如读取、修改或删除数据库中的数据。...

Global site tag (gtag.js) - Google Analytics