前言
在前文《MyBatis 原理浅析——基本原理》一文中简单分析了 MyBatis 的实现原理,MyBatis 的数据库操作是通过 Executor 执行的。Executor 是一个接口,有三个实现类,分别是 SimpleExecutor、ReuseExecutor 和 BatchExecutor。
查询数据的流程
查询数据是通过 SqlSession 的方法实现的,SqlSession 封装了 Executor 的相关操作。以 select 为例,首先根据 SQL 语句关联的 statement 从 configuration 中获取 MappedStatement 对象,然后调用 Executor 的 query 方法执行查询操作。statement 的格式是命名空间+ID,ID 即是 XML 中 select 标签的 id 属性。MappedStatement 对象存储了 XML 中的 SQL 配置,如 ParameterMap、ResultMap 等。
@Override public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
默认情况下,使用 Executor 接口的 SimpleExecutor 实现。SimpleExecutor 继承了抽象类 BaseExecutor。BaseExecutor 实现了 Executor 接口的方法,实现了缓存机制,可以从缓存中直接返回查询结果,但更具体的数据库操作交给子类 SimpleExecutor 实现。在 SimpleExecutor 类中执行查询的 doQuery 方法如下所示。首先从 Configuration 中创建 StatementHandler 接口实例,默认使用 RoutingStatementHandler 实现。然后在 prepareStatement 方法中获取数据库连接、初始化 Statement、设置参数等。再调用 StatementHandler 的 query 方法,执行数据查询,查询结果交给 resultHandler 处理。最后关闭 Statement。
@Override 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); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
在 RoutingStatementHandler 中会根据配置选择一个 StatementHandler 的实现用于进一步的数据处理,默认是 PreparedStatementHandler 类。在该类的 query 方法中会执行 Statement 数据查询请求,请求完成后调用 ResultSetHandler 对查询结果进行处理和封装。ResultSetHandler 接口定义了对返回结果集进行处理的方法,默认使用的实现类是 DefaultResultSetHandler,在 handleResultSets 方法中完成结果集到 Java Bean 的映射。
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
查询结果的缓存
MyBatis 支持对查询结果进行缓存,以减少数据库操作提高效率,默认情况下执行 insert、update、delete 等操作会清理缓存。执行查询操作前,首先创建 CacheKey 对象,根据 SQL 语句、id、offset、limit 和参数更新 CacheKey 对象,CacheKey 对象会记录这些数据并更新 hashcode、checksum、count 几个整数,这些数据都用于判断 CacheKey 对象是否相等。
public void update(Object object) { int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); }
select 语句的查询结果以 Map 形式存放在 PerpetualCache 类中,Map 的 key 是 CacheKey 对象,Map 的 value 是查询结果。
更新数据的流程
DefaultSqlSession 的 insert、delete 操作都是复用 update 操作实现的,update 的操作流程与 select 类似,不做过多阐述。所不同的是,执行 update 操作前需要先清理缓存,在 update 操作完成以后,还需要返回受影响的行数和 KeyGenerator 配置的自增值。
事务提交与回滚
DefaultSqlSession 的 commit 和 rollback 操作都是在 BaseExecutor 中实现的,先清除缓存,然后清理 Statement,如果有需要再调用 Transaction 的相关方法。如果要求强制执行,或者不自动提交且有脏数据,就会执行 Transaction 的 commit 或 rollback 操作。
每周 3 篇学习笔记或技术总结,面向有一定基础的 Java 程序员,内容涉及 Java 进阶、虚拟机、MySQL、NoSQL、分布式计算、开源框架等多个领域。关注作者或微信公众号 backend-develop 第一时间获取最新内容。
MyBatis 原理浅析 3 ——数据操作