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 和参数来操作数据库,这种形式诚然很简略和实用,然而它不合乎面向对象语言的概念和面向接口编程的编程习惯。因为面向接口的编程是面向对象的大趋势,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. 缓存机制
为了进步数据利用率和减小服务器和数据库的压力,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 的了解不同,剖析出的后果天然会有所不同,欢送读者提出质疑和不同的意见,咱们独特探讨~)
它们的关系如下图所示:
三、从 MyBatis 一次 select 查问语句来剖析 MyBatis 的架构设计
一、数据筹备(十分相熟和利用过 MyBatis 的读者能够迅速浏览此节即可)
1. 筹备数据库数据,创立 EMPLOYEES 表,插入数据:
-- 创立一个员工根本信息表 create table "EMPLOYEES"("EMPLOYEE_ID" NUMBER(6) not null, "FIRST_NAME" VARCHAR2(20), "LAST_NAME" VARCHAR2(25) not null, "EMAIL" VARCHAR2(25) not null unique, "SALARY" NUMBER(8,2), constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID") ); comment on table EMPLOYEES is '员工信息表'; comment on column EMPLOYEES.EMPLOYEE_ID is '员工 id'; comment on column EMPLOYEES.FIRST_NAME is 'first name'; comment on column EMPLOYEES.LAST_NAME is 'last name'; comment on column EMPLOYEES.EMAIL is 'email address'; comment on column EMPLOYEES.SALARY is 'salary'; -- 增加数据 insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (100, 'Steven', 'King', 'SKING', 24000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (105, 'David', 'Austin', 'DAUSTIN', 4800.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
2. 配置 Mybatis 的配置文件,命名为 mybatisConfig.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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.driver.OracleDriver" /> 1 <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" /> <property name="username" value="louis" /> <property name="password" value="123456" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/> </mappers> </configuration>
3. 创立 Employee 实体 Bean 以及配置 Mapper 配置文件
package com.louis.mybatis.model; import java.math.BigDecimal; public class Employee { private Integer employeeId; private String firstName; private String lastName; private String email; private BigDecimal salary; public Integer getEmployeeId() {return employeeId;} public void setEmployeeId(Integer employeeId) {this.employeeId = employeeId;} public String getFirstName() {return firstName;} public void setFirstName(String firstName) {this.firstName = firstName;} public String getLastName() {return lastName;} public void setLastName(String lastName) {this.lastName = lastName;} public String getEmail() {return email;} public void setEmail(String email) {this.email = email;} public BigDecimal getSalary() {return salary;} public void setSalary(BigDecimal salary) {this.salary = salary;} }
<?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.louis.mybatis.dao.EmployeesMapper" > <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" > <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" /> <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" /> <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" /> <result column="EMAIL" property="email" jdbcType="VARCHAR" /> <result column="SALARY" property="salary" jdbcType="DECIMAL" /> </resultMap> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY from LOUIS.EMPLOYEES where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL} </select> </mapper>
4. 创立 eclipse 或者 myeclipse 的 maven 我的项目,maven 配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>batis</groupId> <artifactId>batis</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>batis</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.7</version> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc14</artifactId> <version>10.2.0.4.0</version> </dependency> </dependencies> </project>
5. 客户端代码:
1. package com.louis.mybatis.test; 3. import java.io.InputStream; 4. import java.util.HashMap; 5. import java.util.List; 6. import java.util.Map; 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; 13. import com.louis.mybatis.model.Employee; 15. /** 16. * SqlSession 简略查问演示类 17. * @author louluan 18. */ 19. public class SelectDemo {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); 30. //2. 从 SqlSession 工厂 SqlSessionFactory 中创立一个 SqlSession,进行数据库操作 31. SqlSession sqlSession = factory.openSession(); 33. //3. 应用 SqlSession 查问 34. Map<String,Object> params = new HashMap<String,Object>(); 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. } 47. }
二、SqlSession 的工作过程剖析:
1. 开启一个数据库拜访会话 — 创立 SqlSession 对象:
SqlSession sqlSession = factory.openSession();
MyBatis 封装了对数据库的拜访,把对数据库的会话和事务管制放到了 SqlSession 对象中。
2. 为 SqlSession 传递一个配置的 Sql 语句 的 Statement Id 和参数,而后返回后果:
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. } 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”,value 为MappedStatement对象的模式保护到 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 { 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. } 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 ? (List<E>) localCache.getObject(key) : null; 25. if (list != null) {26. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 27. } else { 28. // 3. 缓存中没有值,间接从数据库中读取数据 29. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 30. } 31. } finally { 32. queryStack--; 33. } 34. if (queryStack == 0) {35. for (DeferredLoad deferredLoad : deferredLoads) {36. deferredLoad.load(); 37. } 38. deferredLoads.clear(); // issue #601 39. if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {40. clearLocalCache(); // issue #482 41. } 42. } 43. return list; 44. } 45. private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 46. List<E> list; 47. localCache.putObject(key, EXECUTION_PLACEHOLDER); 48. try { 50. //4. 执行查问,返回 List 后果,而后 将查问的后果放入缓存之中 51. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 52. } finally {53. localCache.removeObject(key); 54. } 55. localCache.putObject(key, list); 56. if (ms.getStatementType() == StatementType.CALLABLE) {57. localOutputParameterCache.putObject(key, parameter); 58. } 59. return list; 60. }
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); } } 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. 对于 JDBC 的PreparedStatement类型的对象,创立的过程中,咱们应用的是 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. } 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>(); 8. int resultSetCount = 0; 9. ResultSetWrapper rsw = getFirstResultSet(stmt); 11. List<ResultMap> resultMaps = mappedStatement.getResultMaps(); 12. int resultMapCount = resultMaps.size(); 13. validateResultMapsCount(rsw, resultMapCount); 15. while (rsw != null && resultMapCount > resultSetCount) {16. ResultMap resultMap = resultMaps.get(resultSetCount); 18. // 将 resultSet 19. handleResultSet(rsw, resultMap, multipleResults, null); 20. rsw = getNextResultSet(stmt); 21. cleanUpAfterHandlingResultSet(); 22. resultSetCount++; 23. } 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. } 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. // 6. public List<Object> handleResultSets(Statement stmt) throws SQLException {7. final List<Object> multipleResults = new ArrayList<Object>(); 9. int resultSetCount = 0; 10. ResultSetWrapper rsw = getFirstResultSet(stmt); 12. List<ResultMap> resultMaps = mappedStatement.getResultMaps(); 13. int resultMapCount = resultMaps.size(); 14. validateResultMapsCount(rsw, resultMapCount); 16. while (rsw != null && resultMapCount > resultSetCount) {17. ResultMap resultMap = resultMaps.get(resultSetCount); 19. // 将 resultSet 20. handleResultSet(rsw, resultMap, multipleResults, null); 21. rsw = getNextResultSet(stmt); 22. cleanUpAfterHandlingResultSet(); 23. resultSetCount++; 24. } 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. } 41. return collapseSingleResultList(multipleResults); 42. }
因为上述的过程时序图太过简单,就不贴出来了,读者能够下载 MyBatis 源码, 应用 Eclipse、Intellij IDEA、NetBeans 等 IDE 集成环境创立我的项目,Debug MyBatis 源码,一步步跟踪 MyBatis 的实现,这样对学习 MyBatis 框架很有帮忙~