目录
一. mybatis 极简示例
1.1 创建 mybatis 配置文件
1.2 创建数据库表
1.3 创建 javabean
1.4 创建 mapper 映射文件
1.5 运行测试
二. mybatis 的几大 "组件"
- 2.1 SqlSessionFactoryBuilder
- 2.2 SqlSessionFactory
- 2.3 SqlSession
- 2.4 Executor
- 2.5 StatementHandler
- 2.6 ParameterHandler
- 2.7 ResultSetHandler
- 2.8 TypeHandler
三. 总结
写这篇博客, 是因为一个面试题 "能介绍一下 MyBatis 执行 sql 的整个流程吗?"
之前也看过一下博客, 知道大概的流程, 无非就是: 启动 ->解析配置文件 ->创建 executor->绑定参数 ->执行 sql->结果集映射, 因为没有看过源码, 听别人解释, 自己心里还是有点虚的, 毕竟也不知道别人讲的是否正确, 使用 MyBatis 也快一年了, 所以写这篇博客总结一下.
一. mybatis 极简示例
1.1 创建 mybatis 配置文件
文件名随意, 这里命名为为 mybatis-config.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="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/test"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <mapper resource="cn/ganlixin/mappers/UserMapper.xml"/>
- </mappers>
- </configuration>
1.2 创建数据表
在 test 数据库中创建 user 表:
- create table user (
- id int not null primary key auto_increment,
- name char(20) not null,
- age int default 0,
- addr char(20) default ''
- ) engine=innodb default charset=utf8mb4;
- insert into user value (1, 'aaa', 18, '北京');
1.3 创建 javabean
创建 User 类, 包含 4 个字段:
- package cn.ganlixin.model;
- public class User {
- private Integer id;
- private String name;
- private Integer age;
- private String addr;
- // 省略 getter,setter,toString
- }
1.4 创建 mapper 文件
创建 UserMapper.xml 文件:
<?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="cn.ganlixin.mappers.UserMapper">
- <select id="selectUserById" parameterType="int" resultType="cn.ganlixin.model.User">
- select * from user where id=#{id}
- </select>
- <delete id="deleteUserById" parameterType="int">
- delete from user where id=#{id}
- </delete>
- </mapper>
1.5 运行测试
- public class TestMyBatis {
- public static void main(String[] args) throws IOException {
- String config = "resources/mybatis-config.xml";
- InputStream resourceAsStream = Resources.getResourceAsStream(config);
- SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
- SqlSession sqlSession = sqlSessionFactory.openSession();
- User user = (User) sqlSession.selectOne("cn.ganlixin.mappers.UserMapper.selectUserById", 1);
- System.out.println(user); // User{id=1, name='aaa', age=18, addr='北京'}
- // 下面的 delete 不会生效, 因为 mybatis 默认没有开启自动提交
- int delete = sqlSession.delete("cn.ganlixin.mappers.UserMapper.deleteUserById", 1);
- System.out.println(delete); // 1
- }
- }
到这里, 一个简单的 mybatis 使用示例就完成了, 后面的分析根据上面这个 TestMyBatis 的执行流程进行分析的.
二. mybatis 的几大 "组件"
我这里说的 "组件", 可以理解为 Mybatis 执行过程中的很重要的几个模块.
2.1 SqlSessionFactoryBuilder
从名称长可以看出来使用的建造者设计模式(Builder), 用于构建 SqlSessionFactory 对象
1. 解析 mybatis 的 xml 配置文件, 然后创建 Configuration 对象(对应 < configuration > 标签);
2. 根据创建的 Configuration 对象, 创建 SqlSessionFactory(默认使用 DefaultSqlSessionFactory);
2.2 SqlSessionFactory
从名称上可以看出使用的工厂模式(Factory), 用于创建并初始化 SqlSession 对象(数据库会话)
1. 调用 openSession 方法, 创建 SqlSession 对象, 可以将 SqlSession 理解为数据库连接(会话);
2.openSession 方法有多个重载, 可以创建 SqlSession 关闭自动提交, 指定 ExecutorType, 指定数据库事务隔离级别....
- package org.apache.ibatis.session;
- import java.sql.Connection;
- public interface SqlSessionFactory {
- /**
- * 使用默认配置
- * 1. 默认不开启自动提交
- * 2. 执行器 Executor 默认使用 SIMPLE
- * 3. 使用数据库默认的事务隔离级别
- */
- SqlSession openSession();
- /**
- * 指定是否开启自动提交
- * @param autoCommit 是否开启自动提交
- */
- SqlSession openSession(boolean autoCommit);
- /**
- * 根据已有的数据库连接创建会话(事务)
- * @param connection 数据库连接
- */
- SqlSession openSession(Connection connection);
- /**
- * 创建连接时, 指定数据库事务隔离级别
- * @param level 事务隔离界别
- */
- SqlSession openSession(TransactionIsolationLevel level);
- /**
- * 创建连接时, 指定执行器类型
- * @param execType 执行器
- */
- SqlSession openSession(ExecutorType execType);
- SqlSession openSession(ExecutorType execType, boolean autoCommit);
- SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
- SqlSession openSession(ExecutorType execType, Connection connection);
- /**
- * 获取 Configuration 对象, 也就是解析 xml 配置文件中的 < configuration > 标签后的数据
- */
- Configuration getConfiguration();
- }
- 2.3 SqlSession
如果了解 web 开发, 就应该知道 cookie 和 session 吧, SqlSession 的 session 和 Web 开发中的 session 概念类似.
session, 译为 "会话, 会议", 数据的有效时间范围是在会话期间 (会议期间), 会话(会议) 结束后, 数据就清除了.
也可以将 SqlSession 理解为一个数据库连接(但也不完全正确, 因为创建 SqlSession 之后, 如果不执行 sql, 那么这个连接是无意义的, 所以数据库连接在执行 sql 的时候才建立的).
SqlSession 是一个接口, 定义了很多操作数据库的方法声明:
- public interface SqlSession extends Closeable {
- /* 获取数据库连接 */
- Connection getConnection();
- /* 数据库的增删改查 */
- <T> T selectOne(String statement);
- <E> List<E> selectList(String statement);
- int update(String statement, Object parameter);
- int delete(String statement, Object parameter);
- /* 事务回滚与提交 */
- void rollback();
- void commit();
- /* 清除一级缓存 */
- void clearCache();
- // 此处省略了很多方法
- }
SqlSession 只是定义了执行 sql 的一些方法, 而具体的实现由子类来完成, 比如 SqlSession 有一个接口实现类 DefaultSqlSession.
MyBatis 中通过 Executor 来执行 sql 的, 在创建 SqlSession 的时候(openSession), 同时会创建一个 Executor 对象, 如下:
- @Override
- public SqlSession openSession() {
- return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
- }
- private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
- Transaction tx = null;
- try {
- final Environment environment = configuration.getEnvironment();
- final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
- tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
- // 利用传入的参数, 创建 executor 对象
- final Executor executor = configuration.newExecutor(tx, execType);
- return new DefaultSqlSession(configuration, executor, autoCommit);
- } catch (Exception e) {
- closeTransaction(tx); // may have fetched a connection so lets call close()
- throw ExceptionFactory.wrapException("Error opening session. Cause:" + e, e);
- } finally {
- ErrorContext.instance().reset();
- }
- }
- 2.4 Executor
Executor(人称 "执行器")是一个接口, 定义了对 JDBC 的封装;
MyBatis 中提供了多种执行器, 如下:
上面的图中, 虽然列出了 5 个 Executor(BaseExecutor 是抽象类), 其实 Executor 只有三种:
- public enum ExecutorType {
- SIMPLE, // 简单
- REUSE, // 复用
- BATCH; // 批量
- }
CacheExecutor 其实是一个 Executor 代理类, 包含一个 delegate, 需要创建时手动传入(要入 simple,reuse,batch 三者之一);
ClosedExecutor, 所有接口都会抛出异常, 表示一个已经关闭的 Executor;
创建 SqlSession 时, 默认使用的是 SimpleExecutor;
下面是创建 Executor 的代码: org.apache.ibatis.session.Configuration#newExecutor()
上面说了, Executor 是对 JDBC 的封装. 当我们使用 JDBC 来执行 sql 时, 一般会先预处理 sql, 也就是 conn.prepareStatement(sql), 获取返回的 PreparedStatement 对象 (实现了 Statement 接口), 再调用 statement 的 executeXxx() 来执行 sql.
也就是说, Executor 在执行 sql 的时候也是需要创建 Statement 对象的, 下面以 SimpleExecutor 为例:
2.5 StatementHandler
在 JDBC 中, 是调用 Statement.executeXxx()来执行 sql;
在 MyBatis, 也是调用 Statement.executeXxx()来执行 sql, 此时就不得不提 StatementHandler, 可以将其理解为一个工人, 他的工作包括
1. 对 sql 进行预处理;
2. 调用 statement.executeXxx()执行 sql;
3. 将数据库返回的结果集进行对象转换(ORM);
- public interface StatementHandler {
- /**
- * 获取预处理对象
- */
- Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
- /**
- * 进行预处理
- */
- void parameterize(Statement statement) throws SQLException;
- /**
- * 批量 sql(内部调用 statement.addBatch)
- */
- void batch(Statement statement) throws SQLException;
- /**
- * 执行更新操作
- * @return 修改的记录数
- */
- int update(Statement statement) throws SQLException;
- /**
- * 执行查询操作
- * @param statement sql 生成的 statement
- * @param resultHandler 结果集的处理逻辑
- * @return 查询结果
- */
- <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
- <E> Cursor<E> queryCursor(Statement statement) throws SQLException;
- BoundSql getBoundSql();
- ParameterHandler getParameterHandler();
- }
StatementHandler 的相关子类如下图所示:
以 BaseStatementHandler 为例:
2.6 ParameterHandler
ParameterHandler 的功能就是 sql 预处理后, 进行设置参数:
- public interface ParameterHandler {
- Object getParameterObject();
- void setParameters(PreparedStatement ps) throws SQLException;
- }
ParamterHandler 有一个 DefaultParameterHandler, 下面是其重写 setParameters 的代码:
2.7 ResultSetHandler
当执行 statement.execute()后, 就可以通过 statement.getResultSet()来获取结果集, 获取到结果集之后, MyBatis 会使用 ResultSetHandler 来将结果集的数据转换为 Java 对象(ORM 映射).
- public interface ResultSetHandler {
- /**
- * 从 statement 中获取结果集, 并将结果集的数据库属性字段映射到 Java 对象属性
- * @param stmt 已经 execute 的 statement, 调用 state.getResultSet()获取结果集
- * @return 转换后的数据
- */
- <E> List<E> handleResultSets(Statement stmt) throws SQLException;
- <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
- void handleOutputParameters(CallableStatement cs) throws SQLException;
- }
ResultSetHandler 有一个实现类, DefaultResultHandler, 其重写 handlerResultSets 方法, 如下:
2.8 TypeHandler
TypeHandler 主要用在两个地方:
1. 参数绑定, 发生在 ParameterHandler.setParamenters()中.
MyBatis 中, 可以使用 < resultMap > 来定义结果的映射关系, 包括每一个字段的类型, 比如下面这样:
- <resultMap id="baseMap" type="cn.ganlixin.model.User">
- <id column="uid" property="id" jdbcType="INTEGER" />
- <result column="uname" property="name" jdbcType="VARCHAR" />
- </resultMap>
TypeHandler, 可以对某个字段按照 xml 中配置的类型进行设置值, 比如设置 sql 的 uid 参数时, 类型为 INTEGER(jdbcType).
2. 获取结果集中的字段值, 发生在 ResultSetHandler 处理结果集的过程中.
TypeHandler 的定义如下:
- public interface TypeHandler<T> {
- /**
- * 设置预处理参数
- *
- * @param ps 预处理 statement
- * @param i 参数位置
- * @param parameter 参数值
- * @param jdbcType 参数的 jdbc 类型
- */
- void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
- /**
- * 获取一条结果集的某个字段值
- *
- * @param rs 一条结果集
- * @param columnName 列名(字段名称)
- * @return 字段值
- */
- T getResult(ResultSet rs, String columnName) throws SQLException;
- /**
- * 获取一条结果集的某个字段值(按照字段的顺序获取)
- *
- * @param rs 一条结果集
- * @param columnIndex 字段列的顺序
- * @return 字段值
- */
- T getResult(ResultSet rs, int columnIndex) throws SQLException;
- T getResult(CallableStatement cs, int columnIndex) throws SQLException;
- }
三. 总结
对于上面的一些组件进行介绍后, 这里将其串联起来, 那么就能知道 mybatis 执行 sql 的具体流程了, 于是我花了下面这个流程:
来源: https://www.cnblogs.com/-beyond/p/13232624.html