1 层级结构
2 初始化
调动步骤:
// 1. 读取配置文件,加载为字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. 解析配置文件,封装成Configuration对象;创建DefaultSqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
源码:
// build 方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 这里使用XMLConfigBuilder解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse() 获取配置文件的属性值,封装Configuration对象
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
**parse()**
方法封装**Configuration**
对象
- 重载的
**build()**
方法生成**SqlSessionFactory**
由源码可知,**SqlSessionFactoryBuilder**
的**build**
方法根据输入的配置文件流,用**XMLConfigBuilder**
类的**parse**()
方法,解析配置文件,封装**Configuration**
对象;然后由**SqlSessionFactoryBuilder**
重载的**build**
方法生成**SqlSessionFactory**
对象。
3 执行流程
3.1 传统方式
SqlSession
**DefaultSqlSession**
是**SqlSession**
的一个默认实现类;** SqlSession**
是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal
绑定,⼀个会话使⽤⼀个SqlSession
,并且在使⽤完毕后需要close
。所以,**SqlSession**
不是线程安全的。
**SqlSession**
中有两个重要的参数:**Executor**
和**Configuration**
- 使用:
SqlSession sqlSession = sqlSessionFactory.openSession();
获得**SqlSession**
ExecutorType
为Executor
的类型,TransactionIsolationLevel
为事务隔离级别,autoCommit
是否开启事务 。
- 使用
SqlSession
中的API
List<Object> objects = sqlSession.selectList("namespace.id");
- 根据传入的参数
statementId
从Configuration
中获取指定MappedStatement
对象 - 把具体的查询操作交给
Executor
Executor
中的query()
方法
query()
方法在父类BaseExecutor
中实现
- 首先根据传递的参数动态获取SQL语句
- 为本次查询生成缓存的Key
- 最后重载
query
方法
- 重载方法中,其他略过,如果缓存未命中,则从数据库中查询
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)
中执行doQuery(ms, parameter, rowBounds, resultHandler, boundSql)
方法**doQuery()**
- 传⼊参数创建
StatementHanlder
对象,query
方法来执⾏查询 - 创建
jdbc
中的statement
对象 this.getConnection(ms.getStatementLog());
从连接池中获取连接对象StatementHandler
进⾏处理
- 传⼊参数创建
handler.query(stmt, resultHandler);
执行最后的操作
StatementHandler
StatementHandler
对象主要完成两个⼯作:
- 对于
JDBC
的PreparedStatement
类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包含若⼲个?
占位符,我们其后再对占位符进⾏设值。StatementHandler
通过parameterize(statement)
⽅法对Statement
进⾏设值; StatementHandler
通过List query(Statement statement, ResultHandler resultHandler)
⽅法来完成执⾏Statement
,和将Statement
对象返回的resultSet
封装成List
;
调⽤
preparedStatemnt.execute()
⽅法,然后将resultSet
交给ResultSetHandler
处理ResultHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
//多ResultSet的结果集合,每个ResultSet对应⼀个Object对象。⽽实际上,每个Object是List<Object> 对象。
//在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,也就是说,multipleResults最多就⼀个元素。
List<Object> multipleResults = new ArrayList();
int resultSetCount = 0;
//获得⾸个ResultSet对象,并封装成ResultSetWrapper对象
ResultSetWrapper rsw = this.getFirstResultSet(stmt);
//获得ResultMap数组
//在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,也就是说,resultMaps就⼀个元素。
List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
this.validateResultMapsCount(rsw, resultMapCount);
while(rsw != null && resultMapCount > resultSetCount) {
//获得ResultMap对象
ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
//处理ResultSet,将结果添加到multipleResults中
this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
//获得下⼀个ResultSet对象,并封装成ResultSetWrapper对象
rsw = this.getNextResultSet(stmt);
//清理
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
String[] resultSets = this.mappedStatement.getResultSets();
if (resultSets != null) {
while(rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
}
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
}
//如果是multipleResults单元素,则取⾸元素返回
return this.collapseSingleResultList(multipleResults);
}
3.2 代理方式
1 初始化时
首先在加载配置文件的时候,会把Mapper里的内容加载到
MapperRegistry
中。MapperRegistry
是Configuration
的一个属性。MapperRegistry
中用Map<Class<?>, MapperProxyFactory<?>> knownMappers
,通过传过来的类型(key)存放mapper的动态代理工厂。2 getMapper()时
DefaultSqlSession
->configuration.getMapper()
->mapperRegistry.getMapper()
- 进入
MapperProxyFactory
中的newInstance
方法,这里就创建了代理对象
3 使用代理mapper调用方法
- 代理对象调用接口中的任意方法,执行的都是动态代理中的
**invoke**
方法 - 所以,找到
MapperProxy<T>
的invoke
方法,由于版本不同中间细节不同,但最终会去执行MapperMethod
中的execute(sqlSession, args)
方法
- 以
SELECT
为例,根据返回结果的不同,执行的方法也不同。 - 返回结果为
List<E>
时,会调用executeForMany(sqlSession, args)
executeForMany(sqlSession, args)
则会调用SqlSession
中的方法,下面就和传统方法一样了。4 二级缓存
二级缓存(Mapper级别/Namespace级别)
Mybatis默认只开启了一级缓存(SqlSession级别);如果开启了二级缓存,则Mybatis的加载顺序为:
二级缓存-> 一级缓存-> 数据库
与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的
MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。1 Cache标签
解析Cache标签
XMLMapperBuilder
中的void configurationElement(XNode context)
方法
- 创建Cache对象。
this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
- 把
Cache
对象包装到MapStatement
中
- Mapper中每个标签对应一个MapStatment;所有MapStatement共用一个cache对象
parseStatementNode
方法的最后一行this.builderAssistant.addMappedStatement
addMappedStatement
中,.cache(this.currentCache);
把Cache对象设置进去了org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType))
.resource(this.resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(this.getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect))
.useCache((Boolean)this.valueOrDefault(useCache, isSelect))
.cache(this.currentCache);
2 二级缓存流程
当开启
sqlMapConfig.xml
中的二级缓存配置时,在底层调用Executor
执行查询的时候调用的是CachingExecutor
实现。<settings>
<setting name="cacheEnable" value="true"/>
</settings>
执行的
CachingExecutor
中的<E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException
方法。- 从ms中获取Cache对象
this.flushCacheIfRequired(ms);
如果标签中配置了flushCache,则每次都要清空缓存- 先从二级换从中查询
(List)this.tcm.getObject(cache, key);
delegate.query()
走的BaseExecutor
(底层先进行一级缓存查询,然后才走数据库)this.tcm.putObject(cache, key, list);
存放到事务缓存管理器:TransactionalCacheManager
3 TransactionalCacheManager
注意⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置 中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个 事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中 tcm 变量对应的类型。
TransactionalCacheManager
中的:
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap();
用来记录缓存和TransactionalCache
的关系。而TransactionalCache
则是具体解决问题的类。
TransactionalCache
中:
整整的缓存对象:
事务被提交前,所有从数据库中查询的结果将在缓存在此集合中:
事务被提交前,当缓存未命中时,CacheKey 将被存储在此集合中:
- 经
TransactionalCache
的源码可知,缓存都是存在了Map<Object, Object> entriesToAddOnCommit;
中,只有当执行了commit()
方法,才会被刷进Cache对象中。4 Commit方法存入缓存
调用情况:sqlSession.commit()
->DefaultSqlSession.commit()
->CachingExecutor.commit()
->tcm.commit()
->**TransactionalCacheManager**
存入Cache缓存中。
调用sqlSession.close()
时同理,**close()**
之前也会执行tcm.commit()
。5 二级缓存的刷新
6 总结
在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的 装饰器。
- ⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
- ⼆级缓存具有丰富的缓存策略
- ⼆级缓存可由多个装饰器,与基础缓存组合⽽成
- ⼆级缓存⼯作由⼀个缓存装饰执⾏器
CachingExecutor
和⼀个事务型预缓存TransactionalCache
完成5 延迟加载
原理
使⽤**CGLIB**
或**Javassist**
( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的**getting**
⽅法时,进⼊拦截器⽅法。⽐如调⽤**a.getB().getName()**
⽅法,进⼊拦截器的**invoke(...)**
⽅法,发现**a.getB()**
需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的SQL,把 B 查询上来,然后调⽤**a.setB(b)**
⽅法,于是 a 对象 b 属性就有值了,接着完成**a.getB().getName()**
⽅法的调⽤。
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。
调用链
SimpleStatementHandler
->DefaultResultSetHandler
->handleResultSet
->handleRowValues
->**createResultObject**
- 加载 resultMap中的属性到List集合中
- 遍历所有属性,如果设置了延迟加载,则创建代理对象
configuration.getProxyFactory().createProxy(...)
- 通过
**JavassistProxyFactory**
创建代理对象
configuration.getProxyFactory().createProxy(...)
调用:
EnhancedResultObjectProxyImpl._createProxy_
JavassistProxyFactory.crateProxy(....)
这时候,延迟加载的动态代理对象就创建成功了。