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(....)

这时候,延迟加载的动态代理对象就创建成功了。
