MyBatis这是现在很流行ORM框架,这是非常强大。事实上现却比較简单、优雅。

本文主要讲述MyBatis的架构设计思路,而且讨论MyBatis的几个核心部件。然后结合一个select查询实例。深入代码,来探究MyBatis的实现。

一、MyBatis的框架设计

      
注:上图非常大程度上參考了iteye 上的chenjc_it所写的博文原理分析之二:框架总体设计 中的MyBatis架构体图,chenjc_it总结的很好,赞一个!

1.接口层---和数据库交互的方式

MyBatis和数据库的交互有两种方式:

a.使用传统的MyBatis提供的API;

b. 使用Mapper接口

1.1.使用传统的MyBatis提供的API

这是传统的传递Statement Id 和查询參数给
SqlSession 对象。使用
SqlSession对象完毕和数据库的交互;MyBatis
提供了很方便和简单的API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和MyBatis
自身配置信息的维护操作。

上述使用MyBatis
的方法,是创建一个和数据库打交道的SqlSession对象,然后依据Statement
Id
和參数来操作数据库。这样的方式固然非常easy和有用。可是它不符合面向对象语言的概念和面向接口编程的编程习惯。因为面向接口的编程是面向对象的大趋势,MyBatis
为了适应这一趋势。添加了另外一种使用MyBatis
支持接口(Interface)调用方式。

1.2. 使用Mapper接口

MyBatis
将配置文件里的每个<mapper>
节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟<mapper>
节点中的<select|update|delete|insert>
节点项相应,即<select|update|delete|insert>
节点的id值为Mapper
接口中的方法名称,parameterType
值表示Mapper
相应方法的入參类型,而resultMap
值则相应了Mapper
接口表示的返回值类型或者返回结果集的元素类型。

依据MyBatis
的配置规范配置好后,通过SqlSession.getMapper(XXXMapper.class) 方法。MyBatis
会依据对应的接口声明的方法信息。通过动态代理机制生成一个Mapper
实例,我们使用Mapper
接口的某一个方法时,MyBatis
会依据这种方法的方法名和參数类型。确定Statement Id,底层还是通过SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject);
等等来实现对数据库的操作。(至于这里的动态机制是如何实现的,我将准备专门一片文章来讨论。敬请关注~

MyBatis
引用Mapper
接口这样的调用方式。纯粹是为了满足面向接口编程的须要。

(事实上另一个原因是在于,面向接口的编程。使得用户在接口上能够使用注解来配置SQL语句,这样就能够脱离XML配置文件,实现“0配置”)。

2.数据处理层

数据处理层能够说是MyBatis
的核心,从大的方面上讲,它要完毕三个功能:

a. 通过传入參数构建动态SQL语句;

b. SQL语句的运行以及封装查询结果集成List<E>

2.1.參数映射和动态SQL语句生成

动态语句生成能够说是MyBatis框架很优雅的一个设计,MyBatis
通过传入的參数值,使用
Ognl 来动态地构造SQL语句
,使得MyBatis
有非常强的灵活性和扩展性。

參数映射指的是对于java
数据类型和jdbc数据类型之间的转换:这里有包含两个过程:查询阶段,我们要将java类型的数据。转换成jdbc类型的数据,通过
preparedStatement.setXXX()
来设值;还有一个就是对resultset查询结果集的jdbcType
数据转换成java
数据类型。

至于详细的MyBatis是怎样动态构建SQL语句的,我将准备专门一篇文章来讨论。敬请关注~

2.2. SQL语句的运行以及封装查询结果集成List<E>

动态SQL语句生成之后,MyBatis
将运行SQL语句,并将可能返回的结果集转换成List<E> 列表。MyBatis
在对结果集的处理中。支持结果集关系一对多和多对一的转换,而且有两种支持方式,一种为嵌套查询语句的查询,另一种是嵌套结果集的查询。

3. 框架支撑层

3.1. 事务管理机制

事务管理机制对于ORM框架而言是必不可少的一部分。事务管理机制的质量也是考量一个ORM框架是否优秀的一个标准,对于数据管理机制我已经在我的博文《深入理解mybatis原理》
MyBatis事务管理机制

中有很具体的讨论,感兴趣的读者能够点击查看。

3.2. 连接池管理机制

因为创建一个数据库连接所占用的资源比較大。 对于数据吞吐量大和訪问量很大的应用而言,连接池的设计就显得很重要,对于连接池管理机制我已经在我的博文《深入理解mybatis原理》
Mybatis数据源与连接池

中有很具体的讨论,感兴趣的读者能够点击查看。

3.3. 缓存机制

为了提高数据利用率和减小server和数据库的压力。MyBatis
会对于一些查询提供会话级别的数据缓存,会将对某一次查询,放置到SqlSession
中,在同意的时间间隔内。对于全然同样的查询,MyBatis
会直接将缓存结果返回给用户,而不用再到数据库中查找。

至于详细的MyBatis缓存机制,我将准备专门一篇文章来讨论,敬请关注~

  3. 4. SQL语句的配置方式

传统的MyBatis
配置SQL
语句方式就是使用XML文件进行配置的,可是这样的方式不能非常好地支持面向接口编程的理念,为了支持面向接口的编程,MyBatis
引入了Mapper接口的概念。面向接口的引入,对使用注解来配置SQL
语句成为可能。用户仅仅须要在接口上加入必要的注解就可以。不用再去配置XML文件了,可是,眼下的MyBatis
仅仅是对注解配置SQL
语句提供了有限的支持,某些高级功能还是要依赖XML配置文件配置SQL
语句。

4 引导层

引导层是配置和启动MyBatis
配置信息的方式。MyBatis
提供两种方式来引导MyBatis
:基于XML配置文件的方式和基于Java API
的方式,读者能够參考我的还有一片博文:Java Persistence with MyBatis 3(中文版) 第二章 引导MyBatis

二、MyBatis的主要构件及其相互关系

从MyBatis代码实现的角度来看,MyBatis的基本的核心部件有下面几个:

  • 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语句以及对应的參数信息
  • Configuration       
    MyBatis全部的配置信息都维持在Configuration对象之中。

(注:这里仅仅是列出了我个人觉得属于核心的部件,请读者不要先入为主,觉得MyBatis就仅仅有这些部件哦!每一个人对MyBatis的理解不同,分析出的结果自然会有所不同。欢迎读者提出质疑和不同的意见。我们共同探讨~)

它们的关系例如以下图所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHVhbmxvdWlz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

三、从MyBatis一次select 查询语句来分析MyBatis的架构设计

一、数据准备(很熟悉和应用过MyBatis 的读者能够迅速浏览此节就可以)

1. 准备数据库数据,创建EMPLOYEES表,插入数据:      

  1. --创建一个员工基本信息表
  2. create table "EMPLOYEES"(
  3. "EMPLOYEE_ID" NUMBER(6) not null,
  4. "FIRST_NAME" VARCHAR2(20),
  5. "LAST_NAME" VARCHAR2(25) not null,
  6. "EMAIL" VARCHAR2(25) not null unique,
  7. "SALARY" NUMBER(8,2),
  8. constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")
  9. );
  10. comment on table EMPLOYEES is '员工信息表';
  11. comment on column EMPLOYEES.EMPLOYEE_ID is '员工id';
  12. comment on column EMPLOYEES.FIRST_NAME is 'first name';
  13. comment on column EMPLOYEES.LAST_NAME is 'last name';
  14. comment on column EMPLOYEES.EMAIL is 'email address';
  15. comment on column EMPLOYEES.SALARY is 'salary';
  16.  
  17. --加入数据
  18. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  19. values (100, 'Steven', 'King', 'SKING', 24000.00);
  20.  
  21. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  22. values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
  23.  
  24. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  25. values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
  26.  
  27. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  28. values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
  29.  
  30. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  31. values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
  32.  
  33. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  34. values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
  35.  
  36. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  37. values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
  38.  
  39. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
  40. values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);

2. 配置Mybatis的配置文件,命名为mybatisConfig.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  3. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  4. <configuration>
  5. <environments default="development">
  6. <environment id="development">
  7. <transactionManager type="JDBC" />
  8. <dataSource type="POOLED">
  9. <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
  10. <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
  11. <property name="username" value="louis" />
  12. <property name="password" value="123456" />
  13. </dataSource>
  14. </environment>
  15. </environments>
  16. <mappers>
  17. <mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
  18. </mappers>
  19. </configuration>

3.     创建Employee实体Bean 以及配置Mapper配置文件

  1. package com.louis.mybatis.model;
  2.  
  3. import java.math.BigDecimal;
  4.  
  5. public class Employee {
  6. private Integer employeeId;
  7.  
  8. private String firstName;
  9.  
  10. private String lastName;
  11.  
  12. private String email;
  13.  
  14. private BigDecimal salary;
  15.  
  16. public Integer getEmployeeId() {
  17. return employeeId;
  18. }
  19.  
  20. public void setEmployeeId(Integer employeeId) {
  21. this.employeeId = employeeId;
  22. }
  23.  
  24. public String getFirstName() {
  25. return firstName;
  26. }
  27.  
  28. public void setFirstName(String firstName) {
  29. this.firstName = firstName;
  30. }
  31.  
  32. public String getLastName() {
  33. return lastName;
  34. }
  35.  
  36. public void setLastName(String lastName) {
  37. this.lastName = lastName;
  38. }
  39.  
  40. public String getEmail() {
  41. return email;
  42. }
  43.  
  44. public void setEmail(String email) {
  45. this.email = email;
  46. }
  47.  
  48. public BigDecimal getSalary() {
  49. return salary;
  50. }
  51.  
  52. public void setSalary(BigDecimal salary) {
  53. this.salary = salary;
  54. }
  55. }
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.louis.mybatis.dao.EmployeesMapper" >
  4.  
  5. <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
  6. <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
  7. <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
  8. <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
  9. <result column="EMAIL" property="email" jdbcType="VARCHAR" />
  10. <result column="SALARY" property="salary" jdbcType="DECIMAL" />
  11. </resultMap>
  12.  
  13. <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
  14. select
  15. EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
  16. from LOUIS.EMPLOYEES
  17. where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
  18. </select>
  19. </mapper>

4. 创建eclipse 或者myeclipse 的maven项目,maven配置例如以下:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4.  
  5. <groupId>batis</groupId>
  6. <artifactId>batis</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <packaging>jar</packaging>
  9.  
  10. <name>batis</name>
  11. <url>http://maven.apache.org</url>
  12.  
  13. <properties>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. </properties>
  16.  
  17. <dependencies>
  18. <dependency>
  19. <groupId>junit</groupId>
  20. <artifactId>junit</artifactId>
  21. <version>3.8.1</version>
  22. <scope>test</scope>
  23. </dependency>
  24.  
  25. <dependency>
  26. <groupId>org.mybatis</groupId>
  27. <artifactId>mybatis</artifactId>
  28. <version>3.2.7</version>
  29. </dependency>
  30.  
  31. <dependency>
  32. <groupId>com.oracle</groupId>
  33. <artifactId>ojdbc14</artifactId>
  34. <version>10.2.0.4.0</version>
  35. </dependency>
  36.  
  37. </dependencies>
  38. </project>

 5. client代码:

  1. package com.louis.mybatis.test;
  2.  
  3. import java.io.InputStream;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7.  
  8. import org.apache.ibatis.io.Resources;
  9. import org.apache.ibatis.session.SqlSession;
  10. import org.apache.ibatis.session.SqlSessionFactory;
  11. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  12.  
  13. import com.louis.mybatis.model.Employee;
  14.  
  15. /**
  16. * SqlSession 简单查询演示类
  17. * @author louluan
  18. */
  19. public class SelectDemo {
  20.  
  21. public static void main(String[] args) throws Exception {
  22. /*
  23. * 1.载入mybatis的配置文件,初始化mybatis,创建出SqlSessionFactory,是创建SqlSession的工厂
  24. * 这里仅仅是为了演示的须要,SqlSessionFactory暂时创建出来,在实际的使用中,SqlSessionFactory仅仅须要创建一次。当作单例来使用
  25. */
  26. InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
  27. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  28. SqlSessionFactory factory = builder.build(inputStream);
  29.  
  30. //2. 从SqlSession工厂 SqlSessionFactory中创建一个SqlSession。进行数据库操作
  31. SqlSession sqlSession = factory.openSession();
  32.  
  33. //3.使用SqlSession查询
  34. Map<String,Object> params = new HashMap<String,Object>();
  35.  
  36. params.put("min_salary",10000);
  37. //a.查询工资低于10000的员工
  38. List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
  39. //b.未传最低工资。查全部员工
  40. List<Employee> result1 = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");
  41. System.out.println("薪资低于10000的员工数:"+result.size());
  42. //~output : 查询到的数据总数:5
  43. System.out.println("全部员工数: "+result1.size());
  44. //~output : 全部员工数: 8
  45. }
  46.  
  47. }

二、SqlSession 的工作过程分析:

1. 开启一个数据库訪问会话---创建SqlSession对象:

  1. SqlSession sqlSession = factory.openSession();

MyBatis封装了对数据库的訪问。把对数据库的会话和事务控制放到了SqlSession对象中。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHVhbmxvdWlz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

2. 为SqlSession传递一个配置的Sql语句 的Statement Id和參数,然后返回结果:

  1. List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);

上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml
的Statement ID,params 是传递的查询參数。

让我们来看一下sqlSession.selectList()方法的定义:

  1. public <E> List<E> selectList(String statement, Object parameter) {
  2. return this.selectList(statement, parameter, RowBounds.DEFAULT);
  3. }
  4.  
  5. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  6. try {
  7. //1.依据Statement Id。在mybatis 配置对象Configuration中查找和配置文件相相应的MappedStatement
  8. MappedStatement ms = configuration.getMappedStatement(statement);
  9. //2. 将查询任务托付给MyBatis 的运行器 Executor
  10. List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  11. return result;
  12. } catch (Exception e) {
  13. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  14. } finally {
  15. ErrorContext.instance().reset();
  16. }
  17. }

MyBatis在初始化的时候。会将MyBatis的配置信息所有载入到内存中,使用org.apache.ibatis.session.Configuration实例来维护。使用者能够使用sqlSession.getConfiguration()方法来获取。MyBatis的配置文件里配置信息的组织格式和内存中对象的组织格式差点儿全然相应的。上述样例中的

  1. <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
  2. select
  3. EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
  4. from LOUIS.EMPLOYEES
  5. <if test="min_salary != null">
  6. where SALARY < #{min_salary,jdbcType=DECIMAL}
  7. </if>
  8. </select>

载入到内存中会生成一个相应的MappedStatement对象,然后会以key="com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary"
valueMappedStatement对象的形式维护到Configuration的一个Map中。当以后须要使用的时候。仅仅须要通过Id值来获取就能够了。

从上述的代码中我们能够看到SqlSession的职能是:

SqlSession依据Statement ID, 在mybatis配置对象Configuration中获取到相应的MappedStatement对象,然后调用mybatis运行器来运行详细的操作。

3.MyBatis运行器Executor依据SqlSession传递的參数运行query()方法(因为代码过长,读者仅仅需阅读我凝视的地方就可以):

  1. /**
  2. * BaseExecutor 类部分代码
  3. *
  4. */
  5. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  6.  
  7. // 1.依据详细传入的參数。动态地生成须要运行的SQL语句,用BoundSql对象表示
  8. BoundSql boundSql = ms.getBoundSql(parameter);
  9. // 2.为当前的查询创建一个缓存Key
  10. CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  11. return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  12. }
  13.  
  14. @SuppressWarnings("unchecked")
  15. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  16. ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  17. if (closed) throw new ExecutorException("Executor was closed.");
  18. if (queryStack == 0 && ms.isFlushCacheRequired()) {
  19. clearLocalCache();
  20. }
  21. List<E> list;
  22. try {
  23. queryStack++;
  24. list = resultHandler == null ?
  25.  
  26. (List<E>) localCache.getObject(key) : null;
  27. if (list != null) {
  28. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  29. } else {
  30. // 3.缓存中没有值,直接从数据库中读取数据
  31. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  32. }
  33. } finally {
  34. queryStack--;
  35. }
  36. if (queryStack == 0) {
  37. for (DeferredLoad deferredLoad : deferredLoads) {
  38. deferredLoad.load();
  39. }
  40. deferredLoads.clear(); // issue #601
  41. if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  42. clearLocalCache(); // issue #482
  43. }
  44. }
  45. return list;
  46. }
  47.  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  48. List<E> list;
  49. localCache.putObject(key, EXECUTION_PLACEHOLDER);
  50. try {
  51.  
  52. //4. 运行查询。返回List 结果,然后 将查询的结果放入缓存之中
  53. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  54. } finally {
  55. localCache.removeObject(key);
  56. }
  57. localCache.putObject(key, list);
  58. if (ms.getStatementType() == StatementType.CALLABLE) {
  59. localOutputParameterCache.putObject(key, parameter);
  60. }
  61. return list;
  62. }
  1. /**
  2. *
  3. *SimpleExecutor类的doQuery()方法实现
  4. *
  5. */
  6. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  7. Statement stmt = null;
  8. try {
  9. Configuration configuration = ms.getConfiguration();
  10. //5. 依据既有的參数。创建StatementHandler对象来运行查询操作
  11. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  12. //6. 创建java.Sql.Statement对象。传递给StatementHandler对象
  13. stmt = prepareStatement(handler, ms.getStatementLog());
  14. //7. 调用StatementHandler.query()方法。返回List结果集
  15.  return handler.<E>query(stmt, resultHandler);
  16. } finally {
  17. closeStatement(stmt);
  18. }
  19. }

上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象。然后将必要的參数传递给StatementHandler,使用StatementHandler来完毕对数据库的查询。终于返回List结果集。

从上面的代码中我们能够看出,Executor的功能和作用是:

(1、依据传递的參数。完毕SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用;

(2、为查询创建缓存,以提高性能(详细它的缓存机制不是本文的重点,我会单独拿出来跟大家探讨,感兴趣的读者能够关注我的其它博文)。

(3、创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果。

4. StatementHandler对象负责设置Statement对象中的查询參数、处理JDBC返回的resultSet,将resultSet加工为List 集合返回:

接着上面的Executor第六步,看一下:prepareStatement()
方法的实现:

  1. /**
  2. *
  3. *SimpleExecutor类的doQuery()方法实现
  4. *
  5. */
  6. 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.准备Statement对象,并设置Statement对象的參数 stmt = prepareStatement(handler, ms.getStatementLog()); // 2. StatementHandler运行query()方法,返回List结果 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
  7.  
  8. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  9. Statement stmt;
  10. Connection connection = getConnection(statementLog);
  11. stmt = handler.prepare(connection);
  12. //对创建的Statement对象设置參数,即设置SQL 语句中 ? 设置为指定的參数
  13. handler.parameterize(stmt);
  14. return stmt;
  15. }

以上我们能够总结StatementHandler对象主要完毕两个工作:

(1. 对于JDBCPreparedStatement类型的对象,创建的过程中。我们使用的是SQL语句字符串会包括
若干个?

占位符。我们其后再对占位符进行设值。

StatementHandler通过parameterize(statement)方法对Statement进行设值。

(2.StatementHandler通过List<E>
query(Statement statement, ResultHandler resultHandler)方法来完毕运行Statement,和将Statement对象返回的resultSet封装成List

5.   StatementHandler 的parameterize(statement) 方法的实现:

  1. /**
  2. * StatementHandler 类的parameterize(statement) 方法实现
  3. */
  4. public void parameterize(Statement statement) throws SQLException {
  5. //使用ParameterHandler对象来完毕对Statement的设值
  6. parameterHandler.setParameters((PreparedStatement) statement);
  7. }
  1. /**
  2. *
  3. *ParameterHandler类的setParameters(PreparedStatement ps) 实现
  4. * 对某一个Statement进行设置參数
  5. */
  6. public void setParameters(PreparedStatement ps) throws SQLException {
  7. ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  8. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  9. if (parameterMappings != null) {
  10. for (int i = 0; i < parameterMappings.size(); i++) {
  11. ParameterMapping parameterMapping = parameterMappings.get(i);
  12. if (parameterMapping.getMode() != ParameterMode.OUT) {
  13. Object value;
  14. String propertyName = parameterMapping.getProperty();
  15. if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
  16. value = boundSql.getAdditionalParameter(propertyName);
  17. } else if (parameterObject == null) {
  18. value = null;
  19. } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
  20. value = parameterObject;
  21. } else {
  22. MetaObject metaObject = configuration.newMetaObject(parameterObject);
  23. value = metaObject.getValue(propertyName);
  24. }
  25.  
  26. // 每个Mapping都有一个TypeHandler,依据TypeHandler来对preparedStatement进行设置參数
  27. TypeHandler typeHandler = parameterMapping.getTypeHandler();
  28. JdbcType jdbcType = parameterMapping.getJdbcType();
  29. if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
  30. // 设置參数
  31. typeHandler.setParameter(ps, i + 1, value, jdbcType);
  32. }
  33. }
  34. }
  35. }

从上述的代码能够看到,StatementHandler 的parameterize(Statement) 方法调用了 ParameterHandler的setParameters(statement) 方法,

ParameterHandler的setParameters(Statement)方法负责 依据我们输入的參数,对statement对象的 ? 占位符处进行赋值。

6.   StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的实现:

  1. /**
  2. * PreParedStatement类的query方法实现
  3. */
  4. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  5. // 1.调用preparedStatemnt。execute()方法。然后将resultSet交给ResultSetHandler处理
  6. PreparedStatement ps = (PreparedStatement) statement;
  7. ps.execute();
  8. //2. 使用ResultHandler来处理ResultSet
  9. return resultSetHandler.<E> handleResultSets(ps);
  10. }
  1. /**
  2. *ResultSetHandler类的handleResultSets()方法实现
  3. *
  4. */
  5. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  6. final List<Object> multipleResults = new ArrayList<Object>();
  7.  
  8. int resultSetCount = 0;
  9. ResultSetWrapper rsw = getFirstResultSet(stmt);
  10.  
  11. List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  12. int resultMapCount = resultMaps.size();
  13. validateResultMapsCount(rsw, resultMapCount);
  14.  
  15. while (rsw != null && resultMapCount > resultSetCount) {
  16. ResultMap resultMap = resultMaps.get(resultSetCount);
  17.  
  18. //将resultSet
  19. handleResultSet(rsw, resultMap, multipleResults, null);
  20. rsw = getNextResultSet(stmt);
  21. cleanUpAfterHandlingResultSet();
  22. resultSetCount++;
  23. }
  24.  
  25. String[] resultSets = mappedStatement.getResulSets();
  26. if (resultSets != null) {
  27. while (rsw != null && resultSetCount < resultSets.length) {
  28. ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
  29. if (parentMapping != null) {
  30. String nestedResultMapId = parentMapping.getNestedResultMapId();
  31. ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  32. handleResultSet(rsw, resultMap, null, parentMapping);
  33. }
  34. rsw = getNextResultSet(stmt);
  35. cleanUpAfterHandlingResultSet();
  36. resultSetCount++;
  37. }
  38. }
  39.  
  40. return collapseSingleResultList(multipleResults);
  41. }

从上述代码我们能够看出,StatementHandler
的List<E> query(Statement statement, ResultHandler resultHandler)方法的实现。是调用了ResultSetHandler的handleResultSets(Statement)
方法。ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句运行后生成的resultSet
结果集转换成List<E> 结果集:

  1. //
  2. // DefaultResultSetHandler 类的handleResultSets(Statement stmt)实现
  3. //HANDLE RESULT SETS
  4. //
  5.  
  6. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  7. final List<Object> multipleResults = new ArrayList<Object>();
  8.  
  9. int resultSetCount = 0;
  10. ResultSetWrapper rsw = getFirstResultSet(stmt);
  11.  
  12. List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  13. int resultMapCount = resultMaps.size();
  14. validateResultMapsCount(rsw, resultMapCount);
  15.  
  16. while (rsw != null && resultMapCount > resultSetCount) {
  17. ResultMap resultMap = resultMaps.get(resultSetCount);
  18.  
  19. //将resultSet
  20. handleResultSet(rsw, resultMap, multipleResults, null);
  21. rsw = getNextResultSet(stmt);
  22. cleanUpAfterHandlingResultSet();
  23. resultSetCount++;
  24. }
  25.  
  26. String[] resultSets = mappedStatement.getResulSets();
  27. if (resultSets != null) {
  28. while (rsw != null && resultSetCount < resultSets.length) {
  29. ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
  30. if (parentMapping != null) {
  31. String nestedResultMapId = parentMapping.getNestedResultMapId();
  32. ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  33. handleResultSet(rsw, resultMap, null, parentMapping);
  34. }
  35. rsw = getNextResultSet(stmt);
  36. cleanUpAfterHandlingResultSet();
  37. resultSetCount++;
  38. }
  39. }
  40.  
  41. return collapseSingleResultList(multipleResults);
  42. }

因为上述的过程时序图太过复杂。就不贴出来了,读者能够下载MyBatis源代码, 使用Eclipse、Intellij IDEA、NetBeans 等IDE集成环境创建项目,Debug MyBatis源代码。一步步跟踪MyBatis的实现。这样对学习MyBatis框架非常有帮助~

作者的话

本文是《深入理解mybatis原理》系列的当中一篇。假设您有兴趣。请关注该系列的其它文章~

认为本文不错,顺手点个赞哦~~您的鼓舞,是我继续分享知识的强大动力!

-----------------------------------------------------------------------------------------------------------------------------------------

本文源自  http://blog.csdn.net/luanlouis/。如需转载,请注明出处。谢谢!

版权声明:本文博客原创文章,博客,未经同意,不得转载。

《深入了解mybatis原则》 MyBatis架构设计和案例研究的更多相关文章

  1. 关于SOA架构设计的案例分析

    关于SOA架构设计的案例分析 面向服务的体系结构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.它可以根据需求通过网络对松散耦合的粗粒度应 ...

  2. SOA架构设计的案例分析

    面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台. ...

  3. 架构设计经典案例:X窗体系统

    X Window在1984年由MIT研发.它的设计哲学之中的一个是:提供机制.而非策略(类似面向对象思想中的"针对接口编程,而不是针对实现编程").机制(mechanism)是指须 ...

  4. SOA架构设计经验分享—架构、职责、数据一致性

    阅读目录: 1.背景介绍 2.SOA的架构层次 2.1.应用服务(原子服务) 2.2.组合服务 2.3.业务服务(编排服务) 3.SOA化的重构 3.1.保留服务空间,为了将来服务的组合 4.运用DD ...

  5. SOA架构设计(转发)

    阅读目录: 1.背景介绍 2.SOA的架构层次 2.1.应用服务(原子服务) 2.2.组合服务 2.3.业务服务(编排服务) 3.SOA化的重构 3.1.保留服务空间,为了将来服务的组合 4.运用DD ...

  6. .NET Core实战项目之CMS 第九章 设计篇-白话架构设计

    前面两篇文章给大家介绍了我们实战的CMS系统的数据库设计,源码也已经上传到服务器上了.今天我们就好聊聊架构设计,在开始之前先给大家分享一下这几天我一直在听的<从零开始学架构>里面关于架构设 ...

  7. [转]SOA架构设计经验分享—架构、职责、数据一致性

    阅读目录: 1.背景介绍 2.SOA的架构层次 2.1.应用服务(原子服务) 2.2.组合服务 2.3.业务服务(编排服务) 3.SOA化的重构 3.1.保留服务空间,为了将来服务的组合 4.运用DD ...

  8. 【转】SOA架构设计经验分享—架构、职责、数据一致性

      1.背景介绍       最近一段时间都在做系统分析和设计工作,面对的业务是典型的重量级企业应用方向.突然发现很多以往觉得很简单的问题变得没有想象的那么容易,最大的问题就 是职责如何分配.论系统架 ...

  9. MyBatis架构设计及源代码分析系列(一):MyBatis架构

    如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...

随机推荐

  1. VS2010-win32下cocos2dx控制台打印的方法

    在xcode中  直接使用printf 或者 cout<<""<<endl;可以直接在控制台打印 但是在VS2010 却死活不好用   真郁闷 ------ ...

  2. mysql导出和导入数据库

    出口 在dos计划,切换到mysql按照该文件夹bin下一个.输入以下命令 mysqldump -u root -p nxu_life > nxu_life2.sql 运行完毕后,就能够看到在b ...

  3. CodeForces 484B Maximum Value

    意甲冠军: a序列n(2*10^5)数字  问道a[i]>=a[j]如果是  a[i]%a[j]最大值是多少 思路: 感觉是一道挺乱来的题-- 我们能够将ans表示为a[i]-k*a[j]  这 ...

  4. JavaScript 中创建对象的方法(读书笔记思维导图)

    面向对象(Object-Oriented, OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象.而 ECMAScript 中没有类的概念,所以我们可以使用 ...

  5. hdu5124(树状数组+离散化)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5124 题意:有n条线段,求被覆盖到次数最多的点的次数 分析: 1.可以转化成求前缀和最大的问题:将区间 ...

  6. spring web.xml配置服务启动后执行文件

    <bean id="readXmlService" class="com.xxx.xxx.readXmlServiceImpl" init-method= ...

  7. Android利用Get、Post 获取网络数据

    首先是Get: 布局非常easy.就一个button,主要看一下MainActivity吧: package com.francis.httpget; import android.app.Activ ...

  8. Spring实战笔记2---Bean的装配

    创建应用对象之间协作关系的行为通常成为装配,该篇的主要内容有两个,一个Spring装配Bean的几种方式以及Spring表达式,事实上这两者是分不开的,在Spring中,对象无需自己负责查找或者创建与 ...

  9. Get与Post的差别

    Http定义了与server交互的不同方法,最主要的方法有4种,各自是GET,POST.PUT,DELETE. URL全称是资源描写叙述符.我们能够这样觉得:一个URL地址,它用于描写叙述一个网络上的 ...

  10. FMOD在Android玩音响系统的抖动问题

    1. 基本介绍 在Android升级系统Android4.4之后,发现FMOD在Android音会出现抖动.导致声音不正常.边赫赫有名的"极品飞车"都有问题. 经查验,是FMOD的 ...