一、mybatis执行大概流程
- 会话(SqlSession):SqlSession 是myBatis的门面(采用门面模式设计),核心作用是为用户提供API。API包括增、删、改、查以及提交、关闭等。其自身是没有能力处理这些请求的,所以内部会包含一个唯一的执行器 Executor,所有请求都会交给执行器来处理。
- 执行器(Executor):Executor是一个大管家,核心功能包括:缓存维护、获取动态SQL、获取连接、以及最终的JDBC调用等。Executor内部包含了若干个组件:(1)缓存维护 :cache(2)获取连接:Transaction,(3)获取动态sql:SqlSource,(4)调用jdbc:StatementHandler
- SQL处理器(StatementHandler):在JDBC中执行一次sql的步骤包括。预编译SQL、设置参数然后执行。StatementHandler就是用来处理这三步。同样它也需要两个助手分别是:(1)设置参数 ParameterHandler,(2)读取结果 ResultSetHandler
涉及到主要的类
类名 | 说明 |
---|---|
MapperProxy | 用于实现动态代理,是InvocationHandler接口的实现类。 |
MapperMethod | 主要作用是将我们定义的接口方法转换成MappedStatement对象。 |
DefaultSqlSession | 默认会话 |
CachingExecutor | 二级缓存执行器(这里没有用到) |
BaseExecutor | 抽像类,基础执行器,包括一级缓存逻辑在此实现 |
SimpleExecutor | 可以理解成默认执行器 |
JdbcTransaction | 事物管理器,会话当中的连接由它负责 |
PooledDataSource | myBatis自带的默认连接池数据源 |
UnpooledDataSource | 用于一次性获取连接的数据源 |
StatementHandler | SQL执行处理器 |
RoutingStatementHandler | 用于根据 MappedStatement 的执行类型确定使用哪种处理器:如STATEMENT(单次执行)、PREPARED(预处理)、CALLABLE(存储过程) |
BaseStatementHandler | StatementHandler基础类 |
PreparedStatementHandler | Sql预处理执行器 |
ConnectionLogger | 用于记录Connection对像的方法调用日志。 |
DefaultParameterHandler | 默认预处理器实现 |
BaseTypeHandler | java类型与JDBC类型映射处理基础类 |
IntegerTypeHandler | Integer与JDBC类型映射处理 |
PreparedStatementLogger | 用于记录PreparedStatement对像方法调用日志。 |
二、Executor执行器
- 简单执行器:SimpleExecutor是默认执行器,它的行为是每处理一次会话当中的SQL请求都会通过对应的StatementHandler 构建一个新个Statement,这就会导致即使是相同SQL语句也无法重用Statement,所以就有了(ReuseExecutor)可重用执行器
- 可重用执行器:ReuseExecutor 区别在于他会将在会话期间内的Statement进行缓存,并使用SQL语句作为Key。所以当执行下一请求的时候,不在重复构建Statement,而是从缓存中取出并设置参数,然后执行。
- 批处理执行器: BatchExecutor 顾名思议,它就是用来作批处理的。但会将所 有SQL请求集中起来,最后调用Executor.flushStatements() 方法时一次性将所有请求发送至数据库。只有连续相同的SQL语句并且相同的SQL映射声明,才会重用Statement,并利用其批处理功能。否则会构建一个新的Satement然后在flushStatements() 时一次执行。这么做的原因是它要保证执行顺序。跟调用顺序一至
- 基础执行器:BaseExecutor 基础执行器主要是用于维护缓存和事物。事物是通过会话中调用commit、rollback进行管理。重点在于缓存这块它是如何处理的? (这里的缓存是指一级缓存),它实现了Executor中的Query与update方法。会话中SQL请求,正是调用的这两个方法。Query方法中处理一级缓存逻辑,即根据SQL及参数判断缓存中是否存在数据,有就走缓存。否则就会调用子类的doQuery() 方法去查询数据库,然后在设置缓存。在doUpdate() 中主要是用于清空缓存。
- 缓存执行器:CachingExecutor,这是用于处理二级缓存的。为什么不把它和一级缓存一起处理呢?因为二级缓存和一级缓存相对独立的逻辑,而且二级缓存可以通过参数控制关闭,而一级缓存是不可以的。综上原因把二级缓存单独抽出来处理。抽取的方式采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。
2.1 一级缓存
一级缓存:也叫做会话级缓存,生命周期仅存在于当前会话,不可以直接关关闭。但可以通过flushCache和localCacheScope对其做相应控制。
二级缓存:也叫应用级性缓存,缓存对象存在于整个应用周期,而且可以跨线程使用。
2.1.1 一级缓存的命中场景
缓存命中参数:
- SQL与参数相同:
- 同一个会话:
- 相同的MapperStatement ID:
- RowBounds行范围相同:
触发清空缓存
- 手动调用clearCache
- 执行提交回滚
- 执行update
- 配置flushCache=true
- 缓存作用域为Statement
2.1.2 一级缓存源码解析
一级缓存逻辑就存在于 BaseExecutor (基础执行器)里面。当会话接收到查询请求之后,会交给执行器的doQuery方法,在这里会通过 Sql、参数、分页条件等参数创建一个缓存key,在基于这个key去 PerpetualCache中查找对应的缓存值,如果有主直接返回。没有就会查询数据库,然后在填充缓存。
BaseExecutor 中的代码
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // 通过这个key获取一级缓存的数据
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
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.");
}
// queryStack 主要是用来嵌套处理的,一、前置清空,即配置了flushCache=true
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache(); // 清空一级缓存
}
List<E> list;
try {
queryStack++;
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();
}
deferredLoads.clear();
// 二、后置清空,配置了缓存作用域为statement查询结束合会清空缓存。
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
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;
}
以SimpleExecutor为例
@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); //SQL处理器,
stmt = prepareStatement(handler, ms.getStatementLog()); // 参数处理
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
PreparedStatementHandler,后面会将
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps); //结果处理器 DefaultResultSetHandler
}
2.1.3 一级缓存的清空
缓存的清空对应BaseExecutor中的 clearLocalCache.方法。只要找到调用该方法地方,就知道哪些场景中会清空缓存了。
- update: 执行任意增删改
- select:查询又分为两种情况清空,一、前置清空,即配置了flushCache=true。二、后置清空,配置了缓存作用域为statement查询结束合会清空缓存。
- commit:提交前清空
- Rolback:回滚前清空
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
2.2 二级缓存
二级缓存也称作是应用级缓存,与一级缓存不同的,是它的作用范围是整个应用,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据。**在流程上是先访问二级缓存,在访问一级缓存。
2.2.1 二缓存需求
二级缓存是一个完整的缓存解决方案,那应该包含哪些功能呢?这里我们分为核心功能和非核心功能两类:
存储【核心功能】
即缓存数据库存储在哪里?常用的方案如下:
- 内存:最简单就是在内存当中,不仅实现简单,而且速度快。内存弊端就是不能持久化,且容易有限。
- 硬盘:可以持久化,容量大。但访问速度不如内存,一般会结合内存一起使用。
- 第三方集成:在分布式情况,如果想和其它节点共享缓存,只能第三方软件进行集成。比如Redis.
溢出淘汰【核心功能】
无论哪种存储都必须有一个容易,当容量满的时候就要进行清除,清除的算法即溢出淘汰机制。常见算法如下:
- FIFO:先进先出
- LRU:最近最少使用
- WeakReference: 弱引用,将缓存对象进行弱引用包装,当Java进行gc的时候,不论当前的内存空间是否足够,这个对象都会被回收
- SoftReference:软件引用,基机与弱引用类似,不同在于只有当空间不足时GC才才回收软引用对象。
其它功能
- 过期清理:指清理存放数据过久的数据
- 线程安全:保证缓存可以被多个线程同时使用
- 写安全:当拿到缓存数据后,可对其进行修改,而不影响原本的缓存数据。通常采取做法是对缓存对象进行深拷贝。
2.2.2二级缓存责任链设计
这么多的功能,如何才能简单的实现,并保证它的灵活性与扩展性呢?这里MyBatis抽像出Cache接口,其只定义了缓存中最基本的功能方法:
- 设置缓存
- 获取缓存
- 清除缓存
- 获取缓存数量
然后上述中每一个功能都会对应一个组件类,并基于装饰者加责任链的模式,将各个组件进行串联。在执行缓存的基本功能时,其它的缓存逻辑会沿着这个责任链依次往下传递。
这样设计有以下优点:
- 职责单一:各个节点只负责自己的逻辑,不需要关心其它节点。
- 扩展性强:可根据需要扩展节点、删除节点,还可以调换顺序保证灵活性。
- 松耦合:各节点之间不没强制依赖其它节点。而是通过顶层的Cache接口进行间接依赖。
2.2.3 二级缓存的使用
缓存空间声明
二级默认缓存默认是不开启的,需要为其声明缓存空间才可以使用,通过 @CacheNamespace 或 为指定的 MappedStatement 声明。声明之后该缓存为该 Mapper 所独有,其它 Mapper 不能访问。如需要多个Mapper共享一个缓存空间可通过@CacheNamespaceRef 或进行引用同一个缓存空间。@CacheNamespace 详细配置见下表:
配置 | 说明 |
---|---|
implementation | 指定缓存的存储实现类,默认是用HashMap存储在内存当中 |
eviction | 指定缓存溢出淘汰实现类,默认LRU ,清除最少使用 |
flushInterval | 设置缓存定时全部清空时间,默认不清空 |
size | 指定缓存容量,超出后就会按eviction指定算法进行淘汰 |
readWrite | true即通过序列化复制,来保证缓存对象是可读写的,默认true |
blocking | 为每个Key的访问添加阻塞锁,防止缓存击穿 |
properties | 为上述组件,配置额外参数,key对应组件中的字段名 |
缓存其它配置
除@CacheNamespace 还可以通过其它参数来控制二缓存
字段 | 配置域 | 说明 |
---|---|---|
cacheEnabled | 二级缓存全局开关,默认开启 | |
useCache | <select|update|insert|delete> | 指定的statement是否开启,默认开启 |
flushCache | <select|update|insert|delete> | 执行sql前是否清空当前二级缓存空间,update默认true。query默认false |
二级缓存的命中条件
二级缓存的命中场景与一级缓存类似,不同在于二级可以跨会放使用,还有就是二级缓存的更新,必须是在会话提交之后。
为什么要提交之后才能命中缓存?
如上图两个会话在修改同一数据,当会话二修改后,在将其查询出来,假如它实时填充到二级缓存,而会话一就能过缓存获取修改之后的数据,但实质是修改的数据回滚了,并没真正的提交到数据库。
所以为了保证数据一至性,二级缓存必须是会话提交之才会真正填充,包括对缓存的清空,也必须是会话正常提交之后才生效。
二级缓存结构
为了实现会话提交之后才变更二级缓存,MyBatis为每个会话设立了若干个暂存区,当前会话对指定缓存空间的变更,都存放在对应的暂存区,当会话提交之后才会提交到每个暂存区对应的缓存空间。为了统一管理这些暂存区,每个会话都一个唯一的事物缓存管理 器。所以这里暂存区也可叫做事物缓存。
二级缓存执行流程
原本会话是通过Executor实现SQL调用,这里基于装饰器模式使用CachingExecutor对SQL调用逻辑进行拦截。以嵌入二级缓存相关逻辑。
查询操作query
当会话调用query() 时,会基于查询语句、参数等数据组成缓存Key,然后尝试从二级缓存中读取数据。读到就直接返回,没有就调用被装饰的Executor去查询数据库,然后在填充至对应的暂存区。
请注意,这里的查询是实时从缓存空间读取的,而变更,只会记录在暂存区
更新操作update
当执行update操作时,同样会基于查询的语句和参数组成缓存KEY,然后在执行update之前清空缓存。这里清空只针对暂存区,同时记录清空的标记,以便当会话提交之时,依据该标记去清空二级缓存空间。
如果在查询操作中配置了flushCache=true ,也会执行相同的操作。
提交操作commit
当会话执行commit操作后,会将该会话下所有暂存区的变更,更新到对应二级缓存空间去。
2.2.4 二级缓存代码详解
CachingExecutor
public class CachingExecutor implements Executor {
private final Executor delegate; // 指向下一个执行器(BaseExecutor)
private final TransactionalCacheManager tcm = new TransactionalCacheManager(); // 缓存管理器
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@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, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key); // 获取二级缓存
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // 存入暂存区
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache); // 清空暂存区
}
}
TransactionalCacheManager (缓存管理器)
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
TransactionalCache
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit; //是否在commit时清除二级缓存的标记
private final Map<Object, Object> entriesToAddOnCommit; //暂存区,
private final Set<Object> entriesMissedInCache; // // 缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key); //获取二级缓存
if (object == null) {
entriesMissedInCache.add(key); // 记录未命中的key
}
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object); // 存至暂存区
}
public void commit() {
if (clearOnCommit) {
delegate.clear(); // 清空二级缓存
}
flushPendingEntries();
reset();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue()); // 存入二级缓存
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
三、Jdbc处理器StatementHandler解析
MyBatis一个基于JDBC的Dao框架,MyBatis把所有跟JDBC相关的操作全部都放到了StatementHandler中
JDBC处理器,基于JDBC构建JDBC Statement,并设置参数,然后执行Sql。每调用会话当中一次SQl,都会有与之相对应的且唯一的Statement实例。
3.1 StatementHandler结构
StatementHandler接口定义了JDBC操作的相关方法如下:
// 基于JDBC 声明Statement
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
// 为Statement 设置方法
void parameterize(Statement statement) throws SQLException;
// 添加批处理(并非执行)
void batch(Statement statement) throws SQLException;
// 执行update操作
int update(Statement statement) throws SQLException;
// 执行query操作
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
3.2 处理流程解析
总共执行过程分为三个阶段:
- 预处理:这里预处理不仅仅是通过Connection创建Statement,还包括设置参数。
- 执行:包含执行SQL和处理结果映射两部分。
- 关闭:直接关闭Statement。
参数处理和结果集封装,涉及数据库字段和JavaBean之间的相互映射,相对复杂。所以分别使用ParameterHandler与ResultSetHandler两个专门的组件实现。
3.3 参数处理
参数处理即将Java Bean转换成数据类型。总共要经历过三个步骤,参数转换、参数映射、参数赋值。
3.3.1 参数转换
即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。
@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@Param("name") String name, @Param("user") User user);
- 单个参数的情况下且没有设置@param注解会直接转换,勿略SQL中的引用名称。
- 多个参数情况:优先采用@Param中设置的名称,如果没有则用参数序号代替 即"param1、parm2…"
- 如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为key
以上所有转换逻辑均在ParamNameResolver中实现。
3.3.2 参数映射
映射是指Map中的key如何与SQL中绑定的参数相对应。以下这几种情况
- 单个原始类型:直接映射,勿略SQL中引用名称
- Map类型:基于Map key映射
- Object:基于属性名称映射,支持嵌套对象属性访问
在Object类型中,支持通过“.”方式映射属中的属性。如:user.age
3.3.3 参数赋值
通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler
3.3.4 结果集封装
指读取ResultSet数据,并将每一行转换成相对应的对象。用户可在转换的过程当中可以通过ResultContext来控制是否要继续转换。转换后的对象都会暂存在ResultHandler中最后统一封装成list返回给调用方
结果集转换中99%的逻辑DefaultResultSetHandler 中实现。整个流程可大致分为以下阶段:
- 读取结果集
- 遍历结果集当中的行
- 创建对象
- 填充属性
3.4 代码详解
SimpleExecutor
@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()); //声明Statement
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout()); //声明Statement
handler.parameterize(stmt); //设置参数
return stmt;
}
BaseStatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection); // 创建Statement
setStatementTimeout(statement, transactionTimeout); // 设置超时
setFetchSize(statement); // 设置查询数量
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
PreparedStatementHandler
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
// 设置参数,交给DefaultParameterHandler
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 执行SQL
return resultSetHandler.handleResultSets(ps); //结果处理,交给DefaultResultSetHandler
}
DefaultParameterHandler(难点)(对应流程图在3.3.1)
@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;
// 参数映射的名字,在ParamNameResolver中的getNamedParams映射好了
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // 与注解@Param相对应
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { //只有一个参数
value = parameterObject;
} else { // 多个参数。是Map类型
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// TypeHandler 类型处理器,mybatis内置了很多,如BooleanTypeHandler、IntegerTypeHandler等等
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType(); //参数的数据类型
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 设置参数数据,每一种数据类型对应的类型处理器来进行处理,如ps.setInt(i, parameter);
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
ParamNameResolver(参数转换)
//即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
DefaultResultSetHandler(难点)(对应流程图在3.3.4)(太复杂了,不做详细解析)
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
四、映射
4.1 映射工具MetaObject
所谓映射是指结果集中的列填充至JAVA Bean属性。这就必须用到反射,而Bean的属性 多种多样的有普通属性、对象、集合、Map都有可能。为了更加方便的操作Bean的属性,MyBatis提供了MeataObject 工具类,其简化了对象属性的操作。其具体功能如下:
- 查找属性:勿略大小写,支持驼峰、支持子属性 如:“blog.comment.user_name”
- 获取属性
- 基于点获取子属性 “user.name”
- 基于索引获取列表值 “users[1].id”
- 基于key获取map值 “user[name]”
- 设置属性:
- 可设置子属性值
- 支持自动创建子属性(必须带有空参构造方法,且不能是集合)
MetaObject 相继依赖了BeanWrapper、MetaClass、Reflector。这四个对象关系如下:
- BeanWrapper: 功能与MeataObject类似,不同点是BeanWrapper只针对单个当前对象属性进行操作,不能操作子属性。
- MetaClass :类的反射功能支持,获能获取整完整类的属性,包括属性的属性。
- Reflector :类的反射功能支持,仅支持当前类的属性。
4.1.1 Meata获取属性流程:
“comments[0].user.name”
MetaObjbt 解析获取流程如下图:
MeataObject.getValue()
获取值的入品,首先根据属性名"comments[0].user.name" 解析成PropertyTokenizer,并基于属性中的“.”来判断是否为子属性值,如果是就递归调用getValue()获取子属性对象。然后在递归调用getValue()获取子属性下的属性。直到最后的name属性获。
MeataObject.setValue() 流程与getValue()类似,不同在于如果子属性不存在,则会尝试创建子属性。
4.1.2 代码详解
MetaObject
public class MetaObject {
private final Object originalObject;
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
// ObjectWrapper 里面会包含有 MetaClass
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}
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) {
// 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);
}
}
public MetaObject metaObjectForProperty(String name) {
Object value = getValue(name);
return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}
PropertyTokenizer (这设计真不错,点个赞)
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
private String name;
private final String indexedName; //保存了全名,如a[]
private String index;
private final String children;
public PropertyTokenizer(String fullname) {
int delim = fullname.indexOf('.');
if (delim > -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
indexedName = name;
delim = name.indexOf('[');
if (delim > -1) {
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}
以BeanWrapper为例(在objectWrapper.get(prop)调用时)
public class BeanWrapper extends BaseWrapper {
private final Object object;
private final MetaClass metaClass;
@Override
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) { // 处理数组
Object collection = resolveCollection(prop, object);
return getCollectionValue(prop, collection);
} else {
return getBeanProperty(prop, object); // 最后会调用反射来获取value
}
}
@Override
public void set(PropertyTokenizer prop, Object value) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
setCollectionValue(prop, collection, value);
} else {
setBeanProperty(prop, object, value);
}
}
4.2 ResultMap结果集映射
映射是指返回的ResultSet列与Java Bean 属性之间的对应关系。通过ResultMapping进行映射描述,在用ResultMap封装成一个整体。
4.2.1 手动映射设置
一个ResultMap 中包含多个ResultMapping 表示一个具体的JAVA属性到列的映射,其主要值如下:
ResultMapping 有多种表现形式如下:
- constructor:构建参数字段
- id:ID字段
- result:普通结构集字段
- association:1对1关联字段
- Collection:1对多集合关联字段
4.2.2 自动映射
当前列名和属性名相同的情况下,可使用自动映射
自动映射条件
- 列名和属性名同时存在(勿略大小写)
- 当前列未手动设置映射
- 属性类别存在TypeHandler
- 开启autoMapping (默认开启)
4.3 嵌套子查询
但很多时候对象结构, 是树级程现的。即对象中包含对象。可以通过子查询获取子对象属性。
当依次解析Blog中的属性时,会先解析填充普通属性,当解析到复合对象时,就会触发对子查询。
五、懒加载 和 嵌套查询
5.1 懒加载
懒加载是为改善,解析对象属性时大量的嵌套子查询的并发问题。设置懒加载后,只有在使用指定属性时才会加载,从而分散SQL请求。
<resultMap id="blogMap" type="blog" autoMapping="true">
<id column="id" property="id"></id>
<association property="comments" column="id" select="selectCommentsByBlog" fetchType="lazy"/>
</resultMap>
在嵌套子查询中指定 fetchType=“lazy” 即可设置懒加载。在调用getComments时才会真正加载。此外调用:“equals”, “clone”, “hashCode”, “toString” 均会触发当前对象所有未执行的懒加载。通过设置全局参数aggressiveLazyLoading=true ,也可指定调用对象任意方法触发所有懒加载。
参数 | 描述 |
---|---|
lazyLoadingEnabled | 全局懒加载开关 默认false |
aggressiveLazyLoading | 任意方法触发加载 默认false |
fetchType | 加载方式 eager实时 lazy懒加载。默认eager |
5.1.1 set覆盖 &序列化
当调用setXXX方法手动设置属性之后,对应的属性懒加载将会被移除,不会覆盖手动设置的值。
5.1.2原理
通过对Bean的动态代理,重写所有属性的getXxx方法。在获取属性前先判断属性是否加载?然后加载之。
代理之后Bean会包含一个MethodHandler,内部在包含一个Map用于存放待执行懒加载,执行前懒加载前会移除。LoadPair用于针对反序列化的Bean准备执行环境。ResultLoader用于执行加载操作,执行前如果原执行器关闭会创建一个新的。
特定属性如果加载失败,不会在进行二次加载。
5.1.3 Bean代理过程
代理过程发生在结果集解析 交创建对象之后(DefaultResultSetHandler.createResultObject),如果对应的属性设置了懒加载,则会通过ProxyFactory 创建代理对象,该对象继承自原对象,然后将对象的值全部拷贝到代理对像。并设置相应MethodHandler(原对象直接抛弃)
5.1.4 代码详解
DefaultResultSetHandler
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 创建动态代理(JavassistProxyFactory.createProxy -> EnhancedResultObjectProxyImpl)
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
EnhancedResultObjectProxyImpl
// 使用了动态代理,调用时会触发这个方法
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
if (WRITE_REPLACE_METHOD.equals(methodName)) { // writeReplace ,有关系列化的知识
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
return original;
}
} else { // 一般会进入到这里
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
// aggressive:任意方法触发加载 默认false。
// lazyLoadTriggerMethods:默认"equals", "clone", "hashCode", "toString"会触发懒加载
// ResultLoaderMap lazyLoader
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll(); // 加载全部
} else if (PropertyNamer.isSetter(methodName)) { // 调用set方法
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) { // 调用get方法
final String property = PropertyNamer.methodToProperty(methodName);
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
return methodProxy.invoke(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
ResultLoaderMap
public void loadAll() throws SQLException {
final Set<String> methodNameSet = loaderMap.keySet();
String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
for (String methodName : methodNames) {
load(methodName);
}
}
public boolean load(String property) throws SQLException {
// 懒加载只会触发一次,不管成功是否失败
LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
if (pair != null) {
pair.load();
return true;
}
return false;
}
ResultLoaderMap.LoadPair
private transient MetaObject metaResultObject;
private transient ResultLoader resultLoader;
private final transient Object serializationCheck = new Object();
public void load() throws SQLException {
if (this.metaResultObject == null) {
throw new IllegalArgumentException("metaResultObject is null");
}
if (this.resultLoader == null) {
throw new IllegalArgumentException("resultLoader is null");
}
this.load(null);
}
public void load(final Object userObject) throws SQLException {
if (this.metaResultObject == null || this.resultLoader == null) {
if (this.mappedParameter == null) {
throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
+ "required parameter of mapped statement ["
+ this.mappedStatement + "] is not serializable.");
}
final Configuration config = this.getConfiguration();
final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
if (ms == null) {
throw new ExecutorException("Cannot lazy load property [" + this.property
+ "] of deserialized object [" + userObject.getClass()
+ "] because configuration does not contain statement ["
+ this.mappedStatement + "]");
}
this.metaResultObject = config.newMetaObject(userObject);
this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
metaResultObject.getSetterType(this.property), null, null);
}
// 判断是否为反序列化,但是永远不会被执行,上面已经判断了是否反序列化
if (this.serializationCheck == null) {
final ResultLoader old = this.resultLoader;
this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
}
// resultLoader.loadResult()
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
ResultLoader
public Object loadResult() throws SQLException {
List<Object> list = selectList();
resultObject = resultExtractor.extractObjectFromList(list, targetType);
return resultObject;
}
private <E> List<E> selectList() throws SQLException {
Executor localExecutor = executor;
// 跨线程使用,是否关闭
if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
localExecutor = newExecutor();
}
try {
// 调用查询方法
return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
} finally {
if (localExecutor != executor) {
localExecutor.close(false);
}
}
}
private Executor newExecutor() {
final Environment environment = configuration.getEnvironment();
if (environment == null) {
throw new ExecutorException("ResultLoader could not load lazily. Environment was not configured.");
}
final DataSource ds = environment.getDataSource();
if (ds == null) {
throw new ExecutorException("ResultLoader could not load lazily. DataSource was not configured.");
}
final TransactionFactory transactionFactory = environment.getTransactionFactory();
final Transaction tx = transactionFactory.newTransaction(ds, null, false);
return configuration.newExecutor(tx, ExecutorType.SIMPLE);
}
5.2 联合查询 和 嵌套映射
映射是指返回的ResultSet列与Java Bean 属性之间的对应关系。通过ResultMapping进行映射描述,在用ResultMap封装成一个整体。映射分为简单映射与复合嵌套映射。
简单映射:即返回的结果集列与对象属性是1对1的关系,这种情况下ResultHandler 会依次遍历结果集中的行,并给每一行创建一个对象,然后在遍历结果集列填充至对象的映射属性。
嵌套映射:但很多时候对象结构, 是树级程现的。即对象中包含对象。与之对应映射也是这种嵌套结构。
在配置方式上可以直接配置子映射,也以引入外部映射和自动映射。共有两类嵌套结构分别是一对多 与多对多 。
5.2.1 联合查询
有了映射之后如何获取结果?普通的单表查询是无法获取复合映射所需结果,这就必须用到联合查询。然后在将联合查询返回的数据列,拆分给不同的对象属性。1对1与1对多拆分和创建的方式是一样的。
1对1查询映射
select a.id,
a.title,
b.id as user_id,
b.name as user_name
from blog a
left join users b on a.author_id=b.id
where a.id = 1;
通过上述语句联合查询语句,可以得出下表中结果。结果中前两字段对应Blog,后两个字段对应User。然后在将User作为author属性填充至Blog对象。
1对多查询
select a.id,a.title,
c.id as comment_id,
c.body as comment_body
from blog a
left join comment c on a.id=c.blog_id
where a.id = 1;
上述语句可得出三条结果,前两个字段对应Blog,后两个字段对应Comment(评论)。与1对1不同的是,三行指向的是同一Blog。因为它ID都是一样的。
上述结果中,相同的三行Blog将会创建一个Blog,同时分别创建三个不同的Comment组成一个集合,并填充至comments对象。
RowKey创建机制
在1对多的查询过程中,是基于RowKey来断定两行数据是否相同的 。RowKey一般基于。但有时并不会指定 这时将会采用其它映射字段创建RowKey具体规则如下:
结果集解析流程
流程说明:
所有映射流程的解析都是在DefaultResultSetHandler当中完成。主要方法如下:
1. handleRowValuesForNestedResultMap()
嵌套结果集解析入口,在这里会遍历结果集中所有行。并为每一行创建一个RowKey对象。然后调用getRowValue()获取解析结果对象。最后保存至ResultHandler中。
注:调用getRowValue前会基于RowKey获取已解析的对象,然后作为partialObject参数发给getRowValue
2. getRowValue()
该方法最终会基于当前行生成一个解析好对象。具体职责包括,1.创建对象、2.填充普通属性和3.填充嵌套属性。在解析嵌套属性时会以递归的方式在调用getRowValue获取子对象。最后一步4.基于RowKey 暂存当前解析对象。
3. applyNestedResultMappings()
解析并填充嵌套结果集映射,遍历所有嵌套映射,然后获取其嵌套ResultMap。接着创建RowKey 去获取暂存区的值。然后调用getRowValue 获取属性对象。最后填充至父对象。
如果通过RowKey能获取到属性对象,它还是会去调用getRowsValue,因为有可能属下还存在未解析的属性。
六、动态SQL
<select id="findBlog"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
AND state = #{state}
</if>
</select>
可以通过OGNL表达式来判断条件是否成立
6.1 先介绍下OGNL表达示
OGNL全称是对象导航图语言(Object Graph Navigation Language)是一种JAVA表达示语言,可以方便的存取对象属和方法,已用于逻辑判断。其支持以下特性:
-
获取属性属性值,以及子属性值进行逻辑计算
id!=null||autho.name!=null
-
表达示中可直接调用方法,(如果是无参方法,可以省略括号)
!comments.isEmpty&&comments.get(0)!=null
-
通过下标访问数组或集合
comments[0].id!=null
-
遍历集合
Iterable<?> comments = evaluator.evaluateIterable(“comments”, blog);
6.2 动态SQL脚本
前面所说动态SQL xml元素最终都会被解成一个可执行的脚本。而MyBatis 正是通过为这个脚本传递参数,并执行脚本计算来生成动态SQL。脚本在MyBatis中体现即SqlNode 。
每个动态元素都会有一个与之对应的脚本类。如if 对应ifSqlNode、forEarch对应ForEachSqlNode 以此类推下去。这里要注意下面三个脚本
- StaticTextSqlNode 表示一段纯静态文本如: select * from user
- TextSqlNode 表示一个通过参数拼装的文本如:select * from ${user}
- MixedSqlNode 表示多个节点的集合
6.3 动态脚本结构
脚本之间是呈现嵌套关系的。比如if元素中会包含一个MixedSqlNode ,而MixedSqlNode下又会包含1至1至多个其它节点。最后组成一课脚本语法树。如下面左边的SQL元素组成右边的语法树。在节点最底层一定是一个StaticTextNode或 TextNode
6.4 动态脚本执行
SqlNode的接口非常简单,就只有一个apply方法,方法的作用就是执行当前脚本节点逻辑,并把结果应用到DynamicContext当中去。
public interface SqlNode {
boolean apply(DynamicContext context);
}
如IfSqlNode当中执行 apply时先计算If逻辑,如果通过就会继续去访问它的子节点。直到最后访问到TextNode 时把SQL文本添加至 DynamicContext。 通过这种类似递归方式Context就会访问到所有的的节点,并把最后最终符合条件的的SQL文本追加到 Context中。
//IfSqlNode
public boolean apply(DynamicContext context) {//计算if表达示
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
//StaticTextSqlNode
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
访问完所有节点之后,就会生成一个SQL字符串,但这个并不是可直接执行的SQL,因为里面的参数还是表达式的形式#{name=name} 就需要通过SqlSourceBuilder 来构建可执行的SQL和参数映射ParameterMapping 。然后才能生成BoundSql。下图表示了在上下文中执行所有节点之后,最生成BoundSql。
看源码从动态SQL到BoundSql 过程中,中间还经过了一次StaticSqlSource 生成?为什么要这么做呢,以及从XML中解析出的SqlNode集存储在哪?这里又要有一个新的概念SqlSource SQL源。
6.5 SqlSource(SQL数据源)
在上层定义上每个Sql映射(MappedStatement)中都会包含一个SqlSource 用来获取可执行Sql(BoundSql)。SqlSource又分为原生SQL源与动态SQL源,以及第三方源。其关系如下图:
- ProviderSqlSource :第三方法SQL源,每次获取SQL都会基于参数动态创建静态数据源,然后在创建BoundSql
- DynamicSqlSource:动态SQL源包含了SQL脚本,每次获取SQL都会基于参数又及脚本,动态创建创建BoundSql
- RawSqlSource:不包含任何动态元素,原生文本的SQL。但这个SQL是不能直接执行的,需要转换成BoundSql
- StaticSqlSource:包含可执行的SQL,以及参数映射,可直接生成BoundSql。前面三个数据源都要先创建StaticSqlSource然后才创建BoundSql。
6.6 SqlSource解析过程
SqlSource 是基于XML解析而来,解析的底层是使用Dom4j 把XML解析成一个个子节点,在通过 XMLScriptBuilder 遍历这些子节点最后生成对应的Sql源。其解析流程如下图:
从图中可以看出这是一种递归式的访问 所有节点,如果是文本节点就会直接创建TextNode 或StaticSqlNode。否则就会创建动态脚本节点如IfSqlNode等。这里每种动态节点都会对应的处理器(NodeHandler)来创建。创建好之后又会继续访问子节点,让递归继续下去。当然子节点所创建的SqNode 也会作为当前所创建的元素的子节点而存在。
6.7 代码详解
CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject); //获取BoundSql,从这里进去
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject); //通过sqlSource获取BoundSql
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
DynamicSqlSource(动态编译)
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context); // 节点遍历
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); //将#{}替换成?
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
SqlSourceBuilder
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
StaticSqlSource(最终返回StaticSqlSource)
public class StaticSqlSource implements SqlSource {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
七、Configuration配置体系
Configuration 是整个MyBatis的配置体系集中管理中心,前面所学Executor、StatementHandler、Cache、MappedStatement…等绝大部分组件都是由它直接或间接的创建和管理。此外影响这些组件行为的属性配置也是由它进行保存和维护。如cacheEnabled、lazyLoadingEnabled … 等。所以说它MyBatis的大管家很形象。
7.1、核心作用总结
-
存储全局配置信息,其来源于settings(设置)
-
初始化并维护全局基础组件
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- plugins(插件)
- environments(环境配置)
- cache(二级缓存空间)
-
初始化并维护MappedStatement
-
组件构造器,并基于插件进行增强
- newExecutor(执行器)
- newStatementHandler(JDBC处理器)
- newResultSetHandler(结果集处理器)
- newParameterHandler(参数处理器)
7.2 配置来源
Configuration 配置来源有三项:
- Mybatis-config.xml 启动文件,全局配置、全局组件都是来源于此。
- Mapper.xml SQL映射(MappedStatement) \结果集映射(ResultMapper)都来源于此。
- @Annotation SQL映射与结果集映射的另一种表达形式。
7.3 配置元素
Configuration 配置信息来源于xml和注解,每个文件和注解都是由若干个配置元素组成,并呈现嵌套关系,总体关系如下图所示:
关于各配置的使用请参见官网给出文档:https://mybatis.org/mybatis-3/zh/configuration.html#properties
7.4 元素承载
无论是xml 还是我注解这些配置元素最弱都要被转换成JAVA配置属性或对象组件来承载。其对应关系如下:
- 全配置(config.xml) 由Configuration对像属性承载
- sql映射<select|insert…> 或@Select 等由MappedStatement对象承载
- 缓存<cache…> 或@CacheNamespace 由Cache对象承载
- 结果集映射 由ResultMap 对象承载
7.5 配置文件解析
配置文件解析需要我们分开讨论,首先来分析XML解析过程。xml配置解析其底层使用dom4j先解析成一棵节点树,然后根据不同的节点类型与去匹配不同的解析器。最终解析成特定组件。
解析器的基类是BaseBuilder 其内部包含全局的configuration 对象,这么做的用意是所有要解析的组件最后都要集中归属至configuration。接下来了解一下每个解析器的作用:
- XMLConfigBuilder :解析config.xml文件,会直接创建一个configuration对象,用于解析全局配置 。
- XMLMapperBuilder :解析Mapper.xml文件,内容包含 等
- MapperBuilderAssistant:Mapper.xml解析辅助,在一个Mapper.xml中Cache是对Statement(sql声明)共享的,共享组件的分配即由该解析实现。
- XMLStatementBuilder:SQL映射解析 即<select|update|insert|delete>元素解析成MapperStatement。
- SqlSourceBuilder:Sql数据源解析,将声明的SQL解析可执行的SQL。
- XMLScriptBuilder:解析动态SQL数据源当中所设置 SqlNode脚本集。
7.6 XML文件解析流程
整体解析流程是从XmlConfigBuilder 开始,然后逐步向内解析,直到解析完所有节点。我们通过一个MappedStatement 解析过程即可了解到期整体解析流程。
流程说明:
- 【XmlConfigBuilder】 接收一个config.xml 输入流,然后创建一个空Configuration对象
- 【XmlConfigBuilder】解析全局配置
- 【XmlConfigBuilder】mapperElements解析,通过Resource或url 指定mapper.xml文件
- 【XmlMapperBuilder】解析缓存、结果集配置等公共配置
- 【XmlMapperBuilder】解析Sql映射<select|insert|upate|delete>
- 【XMLScriptBuilder】解析生成SQL数据源,包括动态脚本
- 【XmlMapperBuilder】构建Statement
- 【MapperBuilderAssistant】设置缓存并添加至Configuration
7.7 注解配置解析
注解解析底层实现是通过反射获取Mapper接口当中注解元素实现。有两种方式一种是直接指定接口名,一种是指定包名然后自动扫描包下所有的接口类。这些逻辑均由Mapper注册器(MapperRegistry)实现。其接收一个接口类参数,并基于该参数创建针对该接口的动态代理工厂,然后解析内部方法注解生成每个MapperStatement 最后添加至Configuration 完成解析。