MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生 Map 使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects, 普通的 Java 对象) 映射成数据库中的记录。如何新建MyBatis 源码工程请点击MyBatis 源码分析 -IDEA 新建 MyBatis 源码工程。
MyBatis 框架主要完成的是以下 2 件事情:
- 根据 JDBC 规范建立与数据库的连接。
- 通过反射打通 Java 对象与数据库参数交互之间相互转换的关系。
MyBatis 框架是一种典型的交互式框架,先准备好交互的必要条件,然后构建一个交互的环境,在交互环境中划分会话,在会话中与数据库进行交互数据。
1 MyBatis 主要的类
- Configuration MyBatis 所有的配置信息都维持在 Configuration 对象之中。
- SqlSession 作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数据库增删改查功能
- Executor MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护
- StatementHandler 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 结果集转换成 List 集合。
- ParameterHandler 负责对用户传递的参数转换成 JDBC Statement 所需要的参数,
- ResultSetHandler 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合;
- TypeHandler 负责 java 数据类型和 jdbc 数据类型之间的映射和转换
- MappedStatement MappedStatement 维护了一条 <select|update|delete|insert> 节点的封装,
- SqlSource 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回
- BoundSql 表示动态生成的 SQL 语句以及相应的参数信息
以上几个类在 SQL 操作中都会涉及,在 SQL 操作中重点关注下 SQL 参数什么时候写入和结果集怎么转换为 Java 对象,这两个过程正好对应的类是 PreparedStatementHandler 和 ResultSetHandler 类。
( 图片来自《深入理解 mybatis 原理》 MyBatis 的架构设计以及实例分析)
2 SQL 执行流程
MyBatis 主要设计目的还是为了让我们在执行 SQL 时对输入输出的数据的管理更加方便,所以方便的让我们写出 SQL 和方便的获取 SQL 的执行结果是 MyBatis 的核心竞争力。下面就用一个例子来从源码角度看一下 SQL 的完整执行流程。
新建配置文件 conf.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">settings</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">setting </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="cacheEnabled"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">setting </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="lazyLoadingEnabled"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="false"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">setting name="logImpl" value="STDOUT_LOGGING"/</span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 日志 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">settings</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">typeAliases</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">typeAlias </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="com.luoxn28.dao.User"</span><span style="color: rgba(255, 0, 0, 1)"> alias</span><span style="color: rgba(0, 0, 255, 1)">="User"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">typeAliases</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">environments </span><span style="color: rgba(255, 0, 0, 1)">default</span><span style="color: rgba(0, 0, 255, 1)">="development"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">environment </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="development"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">transactionManager </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="JDBC"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 声明使用那种事务管理机制 JDBC/MANAGED </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 配置数据库连接信息 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">dataSource </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="POOLED"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="driver"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="com.mysql.jdbc.Driver"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="url"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="jdbc:mysql://192.168.1.150:3306/xxx"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="username"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="xxx"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="password"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="xxx"</span> <span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">dataSource</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">environment</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">environments</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">mappers</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">mapper </span><span style="color: rgba(255, 0, 0, 1)">resource</span><span style="color: rgba(0, 0, 255, 1)">="userMapper.xml"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">mappers</span><span style="color: rgba(0, 0, 255, 1)">></span>
</configuration>
首先建立数据表,这里就以 user 表为例 :
DROP TABLE IF EXISTS user; CREATE TABLE user ( id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, password VARCHAR(32) NOT NULL, sex int, email VARCHAR(32), phone VARCHAR(16), admin VARCHAR(16) );
然后新建与数据表对应的类 User:
/** * User - 用户类 */ public class User {</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> MAN = 0; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 男生</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> WOMAN = 1; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 女生</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> OTHER = 2; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 其他</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> id; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户id</span> <span style="color: rgba(0, 0, 255, 1)">private</span> String name; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户名</span> <span style="color: rgba(0, 0, 255, 1)">private</span> String password; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户密码</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> sex; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户性别</span> <span style="color: rgba(0, 0, 255, 1)">private</span> String email; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户邮箱</span> <span style="color: rgba(0, 0, 255, 1)">private</span> String phone; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户手机</span> <span style="color: rgba(0, 0, 255, 1)">private</span> String admin; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用户是否是管理员,"admin"表示是管理员,其他为普通用户</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> User() { } </span><span style="color: rgba(0, 0, 255, 1)">public</span> User(String name, String password, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> sex, String email, String phone) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.password =<span style="color: rgba(0, 0, 0, 1)"> password; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sex =<span style="color: rgba(0, 0, 0, 1)"> sex; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.email =<span style="color: rgba(0, 0, 0, 1)"> email; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.phone =<span style="color: rgba(0, 0, 0, 1)"> phone; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.admin = ""<span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> User(String name, String password, String sex, String email, String phone) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.password =<span style="color: rgba(0, 0, 0, 1)"> password; setSex(sex); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.sex = sex;</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.email =<span style="color: rgba(0, 0, 0, 1)"> email; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.phone =<span style="color: rgba(0, 0, 0, 1)"> phone; </span><span style="color: rgba(0, 0, 255, 1)">this</span>.admin = ""<span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> getId() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> id; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setId(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> id) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.id =<span style="color: rgba(0, 0, 0, 1)"> id; } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getName() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> name; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setName(String name) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name; } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getPassword() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> password; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setPassword(String password) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.password =<span style="color: rgba(0, 0, 0, 1)"> password; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> getSex() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sex; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setSex(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> sex) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sex =<span style="color: rgba(0, 0, 0, 1)"> sex; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setSex(String sexStr) { </span><span style="color: rgba(0, 0, 255, 1)">int</span> sex =<span style="color: rgba(0, 0, 0, 1)"> Integer.valueOf(sexStr); </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (Integer.valueOf(sexStr)) { </span><span style="color: rgba(0, 0, 255, 1)">case</span> 0<span style="color: rgba(0, 0, 0, 1)">: { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sex =<span style="color: rgba(0, 0, 0, 1)"> MAN; </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">case</span> 1<span style="color: rgba(0, 0, 0, 1)">: { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sex =<span style="color: rgba(0, 0, 0, 1)"> WOMAN; </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">: { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sex =<span style="color: rgba(0, 0, 0, 1)"> OTHER; </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">; } } } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getEmail() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> email; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setEmail(String email) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.email =<span style="color: rgba(0, 0, 0, 1)"> email; } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getPhone() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> phone; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setPhone(String phone) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.phone =<span style="color: rgba(0, 0, 0, 1)"> phone; } </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getAdmin() { </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> admin; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setAdmin(String admin) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.admin =<span style="color: rgba(0, 0, 0, 1)"> admin; } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String toString() { </span><span style="color: rgba(0, 0, 255, 1)">return</span> "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", sex=" + sex + ", email='" + email + '\'' + ", phone='" + phone + '\'' + ", admin='" + admin + '\'' + '}'<span style="color: rgba(0, 0, 0, 1)">; }
}
再新建 usre 表的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.luoxn28.dao.UserDao">
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">select </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="getById"</span><span style="color: rgba(255, 0, 0, 1)"> parameterType</span><span style="color: rgba(0, 0, 255, 1)">="int"</span><span style="color: rgba(255, 0, 0, 1)"> resultType</span><span style="color: rgba(0, 0, 255, 1)">="User"</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> SELECT * FROM user WHERE id=#{id}; </span><span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> #{xxx} xxx为类中的数据域名称 </span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">select</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">select </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="getAll"</span><span style="color: rgba(255, 0, 0, 1)"> resultType</span><span style="color: rgba(0, 0, 255, 1)">="com.luoxn28.dao.User"</span><span style="color: rgba(0, 0, 255, 1)">></span><span style="color: rgba(0, 0, 0, 1)"> SELECT * FROM user; </span><span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">select</span><span style="color: rgba(0, 0, 255, 1)">></span>
</mapper>
最后新建测试类:
/** * MyBatis 测试类 */ public class TestMain {</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> IOException { String resouce </span>= "conf.xml"<span style="color: rgba(0, 0, 0, 1)">; InputStream is </span>=<span style="color: rgba(0, 0, 0, 1)"> Resources.getResourceAsStream(resouce); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 构建sqlSession工厂</span> SqlSessionFactory sqlSessionFactory = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SqlSessionFactoryBuilder().build(is); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取sqlSession</span> SqlSession session =<span style="color: rgba(0, 0, 0, 1)"> sqlSessionFactory.openSession(); User user; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 第一种方式: 直接执行已映射的 SQL 语句 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> String statement </span>= "com.luoxn28.dao.UserDao.getById"<span style="color: rgba(0, 0, 0, 1)">; user </span>= session.selectOne(statement, 1<span style="color: rgba(0, 0, 0, 1)">); System.out.println(user); } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> { session.close(); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 第二种方式: 执行更清晰和类型安全的代码 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
// UserDao userDao = session.getMapper(UserDao.class);
// user = userDao.getById(1);
// System.out.println(user);
}}
由于我们分析的是 SQL 的执行流程,那就重点关注下 user = session.selectOne(statement, 1); 这行代码 ~ 注意,传进去的参数是 1。
session 是 DefaultSqlSession 类型的,因为 sqlSessionFactory 默认生成的 SqlSession 是 DefaultSqlSession 类型。selectOne()会调用 selectList()。
// DefaultSqlSession 类 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // CURD 操作是交给 Excetor 去处理的 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();} }
在 DefaultSqlSession.selectList 中的各种 CURD 操作都是通多 Executor 进行的,这里 executor 的类型是 CachingExecutor,接着跳转到其中的 query 方法中。
// CachingExecutor 类 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 获取绑定的 sql 命令,比如 "SELECT * FROM xxx" CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
getBoundSql 为了获取绑定的 sql 命令,在创建完 cacheKey 之后,就进入到 CachingExecutor 类中的另一个 query 方法中。
// CachingExecutor 类 @Override public <E> List<E> query(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, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>)tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
这里真正执行 query 操作的是 SimplyExecutor 代理来完成的,接着就进入到了 SimplyExecutor 的父类 BaseExecutor 的 query 方法中。
// SimplyExecutor 的父类 BaseExecutor 类 @SuppressWarnings("unchecked") @Override 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 (closed) { throw new ExecutorException("Executor was closed.");} if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache(); } List<E> list; try { queryStack++; /** * localCache 是一级缓存,如果找不到就调用 queryFromDatabase 从数据库中查找 */ 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(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache();} } return list; }
因为是第一次 SQL 查询操作,所以会调用 queryFromDatabase 方法来执行查询。
// SimplyExecutor 的父类 BaseExecutor 类 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 { 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; }
从数据库中查询数据,进入到 SimplyExecutor 中进行操作。
// SimplyExecutor 类 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(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 子流程 1:SQL 查询参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler 封装了 Statement // 子流程 2:SQL 查询操作和结果集的封装 return handler.<E>query(stmt); } finally {closeStatement(stmt); } }
注意,在 prepareStatement 方法中会进行 SQL 查询参数的设置,也就是咱们最开始传递进来的参数,其值为 1。handler.<E>query(stmt) 方法中会进行实际的 SQL 查询操作和结果集的封装(封装成 Java 对象)。当流程走到这里时,程序已经压栈有一定深度了,因为接下来程序分析会兵分两路,一方面深入到 SQL查询及结果集的设置子流程 1 中,然后再深入到 SQL 查询操作和结果集的封装子流程 2,因为还会回到这里,所以就来一张调用栈的特写吧:
子流程 1:SQL 查询参数的设置
// SimplyExecutor 类 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 获取一个 Connection Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); // 设置 SQL 查询中的参数值 return stmt; }
通过 getConnection 方法来获取一个 Connection,调用 prepare 方法来获取一个 Statement(这里的 handler 类型是 RoutingStatementHandler,RoutingStatementHandler 的 prepare 方法调用的是 PrepareStatementHandler 的 prepare 方法,因为 PrepareStatementHandler 并没有覆盖其父类的 prepare 方法,其实最后调用的是 BaseStatementHandler 中的 prepare 方法。是不是绕晕了,那就再看一遍吧 :) )。调用 parameterize 方法来设置 SQL 的参数值(这里最后调用的是 PrepareStatementHandler 中的 parameterize 方法,而 PrepareStatementHandler.parameterize 方法调用的是 DefaultParameterHandler 中的 setParameters 方法)。
// PrepareStatementHandler 类 @Override public void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}
// DefaultParameterHandler 类 @Override public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); 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); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull();} try { typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException e) { throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);} catch (SQLException e) { throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);} } } } }
到这里为止,已经给 Statement 设置了最初传递进去的参数(值为 1)了,那么接着分析流程 2:
流程 2:SQL 查询及结果集的设置
// RoutingStatementHandler 类 @Override public <E> List<E> query(Statement statement) throws SQLException { return delegate.<E>query(statement); }
// RoutingStatementHandler 类 @Override public <E> List<E> query(Statement statement) throws SQLException { // 这里就到了熟悉的 PreparedStatement 了 PreparedStatement ps = (PreparedStatement) statement; // 执行 SQL 查询操作 ps.execute(); // 结果交给 ResultHandler 来处理 return resultSetHandler.<E> handleResultSets(ps); }
// DefaultResultSetHandler 类(封装返回值,将查询结果封装成 Object 对象) @Override public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());</span><span style="color: rgba(0, 0, 255, 1)">final</span> List<Object> multipleResults = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList<Object><span style="color: rgba(0, 0, 0, 1)">(); </span><span style="color: rgba(0, 0, 255, 1)">int</span> resultSetCount = 0<span style="color: rgba(0, 0, 0, 1)">; ResultSetWrapper rsw </span>=<span style="color: rgba(0, 0, 0, 1)"> getFirstResultSet(stmt); List</span><ResultMap> resultMaps =<span style="color: rgba(0, 0, 0, 1)"> mappedStatement.getResultMaps(); </span><span style="color: rgba(0, 0, 255, 1)">int</span> resultMapCount =<span style="color: rgba(0, 0, 0, 1)"> resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); </span><span style="color: rgba(0, 0, 255, 1)">while</span> (rsw != <span style="color: rgba(0, 0, 255, 1)">null</span> && resultMapCount ><span style="color: rgba(0, 0, 0, 1)"> resultSetCount) { ResultMap resultMap </span>=<span style="color: rgba(0, 0, 0, 1)"> resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); rsw </span>=<span style="color: rgba(0, 0, 0, 1)"> getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount</span>++<span style="color: rgba(0, 0, 0, 1)">; } String[] resultSets </span>=<span style="color: rgba(0, 0, 0, 1)"> mappedStatement.getResultSets(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (resultSets != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">while</span> (rsw != <span style="color: rgba(0, 0, 255, 1)">null</span> && resultSetCount <<span style="color: rgba(0, 0, 0, 1)"> resultSets.length) { ResultMapping parentMapping </span>=<span style="color: rgba(0, 0, 0, 1)"> nextResultMaps.get(resultSets[resultSetCount]); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (parentMapping != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { String nestedResultMapId </span>=<span style="color: rgba(0, 0, 0, 1)"> parentMapping.getNestedResultMapId(); ResultMap resultMap </span>=<span style="color: rgba(0, 0, 0, 1)"> configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, parentMapping); } rsw </span>=<span style="color: rgba(0, 0, 0, 1)"> getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount</span>++<span style="color: rgba(0, 0, 0, 1)">; } } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> collapseSingleResultList(multipleResults);
}
ResultSetWrapper 是 ResultSet 的包装类,调用 getFirstResultSet 方法获取第一个 ResultSet,同时获取数据库的 MetaData 数据,包括数据表列名、列的类型、类序号等,这些信息都存储在 ResultSetWrapper 类中了。然后调用 handleResultSet 方法来来进行结果集的封装。
// DefaultResultSetHandler 类 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);} } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }
这里调用 handleRowValues 方法来进行值的设置:
// DefaultResultSetHandler 类 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 封装数据 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
// DefaultResultSetHandler 类 // 封装数据 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>(); skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
// DefaultResultSetHandler 类 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // createResultObject 为新创建的对象,数据表对应的类 Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty(); if (shouldApplyAutomaticMappings(resultMap, false)) { // 这里把数据填充进去,metaObject 中包含了 resultObject 信息 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; return resultObject; } return resultObject; }
// DefaultResultSetHandler 类(把 ResultSet 中查询结果填充到 JavaBean 中) private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (autoMapping.size() > 0) { // 这里进行 for 循环调用,因为 user 表中总共有 7 项,所以也就调用 7 次 for (UnMappedColumnAutoMapping mapping : autoMapping) { // 这里将 esultSet 中查询结果转换为对应的实际类型 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value); } } } return foundValues; }
mapping.typeHandler.getResult 会获取查询结果值的实际类型,比如我们 user 表中 id 字段为 int 类型,那么它就对应 Java 中的 Integer 类型,然后通过调用 statement.getInt("id") 来获取其 int 值,其类型为 Integer。metaObject.setValue 方法会把获取到的 Integer 值设置到 Java 类中的对应字段。
// MetaObject 类 public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null && prop.getChildren() != null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value);} else {objectWrapper.set(prop, value); } }
metaValue.setValue 方法最后会调用到 Java 类中对应数据域的 set 方法,这样也就完成了 SQL 查询结果集的 Java 类封装过程。最后贴一张调用栈到达 Java 类的 set 方法中的快照:
参考:
2、《深入分析 Java Web 技术内幕》的 iBatis 章节
3、《深入理解 mybatis 原理》 MyBatis 的架构设计以及实例分析
4、