1 层级结构

Mybatis层级结构.png

2 初始化

调动步骤:

  1. // 1. 读取配置文件,加载为字节输入流
  2. InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
  3. // 2. 解析配置文件,封装成Configuration对象;创建DefaultSqlSessionFactory对象
  4. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

源码:

  1. // build 方法
  2. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  3. SqlSessionFactory var5;
  4. try {
  5. // 这里使用XMLConfigBuilder解析配置文件
  6. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  7. // parser.parse() 获取配置文件的属性值,封装Configuration对象
  8. var5 = this.build(parser.parse());
  9. } catch (Exception var14) {
  10. throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
  11. } finally {
  12. ErrorContext.instance().reset();
  13. try {
  14. inputStream.close();
  15. } catch (IOException var13) {
  16. }
  17. }
  18. return var5;
  19. }
  • **parse()**方法封装**Configuration**对象

image.png

  • 重载的**build()**方法生成**SqlSessionFactory**

image.png

由源码可知,**SqlSessionFactoryBuilder****build**方法根据输入的配置文件流,用**XMLConfigBuilder**类的**parse**()方法,解析配置文件,封装**Configuration**对象;然后由**SqlSessionFactoryBuilder**重载的**build**方法生成**SqlSessionFactory**对象。

3 执行流程

3.1 传统方式

SqlSession

**DefaultSqlSession****SqlSession**的一个默认实现类;** SqlSession**是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀个SqlSession,并且在使⽤完毕后需要close。所以,**SqlSession**不是线程安全的。

  • **SqlSession**中有两个重要的参数:**Executor****Configuration**

image.png

  1. 使用:SqlSession sqlSession = sqlSessionFactory.openSession();获得**SqlSession**

ExecutorTypeExecutor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务 。
image.png

  1. 使用SqlSession中的API

List<Object> objects = sqlSession.selectList("namespace.id");
image.png

  • 根据传入的参数statementIdConfiguration中获取指定MappedStatement对象
  • 把具体的查询操作交给Executor
  1. Executor中的query()方法

query()方法在父类BaseExecutor中实现

  • 首先根据传递的参数动态获取SQL语句
  • 为本次查询生成缓存的Key
  • 最后重载query方法

image.png

  • 重载方法中,其他略过,如果缓存未命中,则从数据库中查询

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进⾏处理

image.png

  • handler.query(stmt, resultHandler);执行最后的操作

StatementHandler

StatementHandler对象主要完成两个⼯作:

  1. 对于JDBCPreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包含若⼲个占位符,我们其后再对占位符进⾏设值。StatementHandler通过parameterize(statement)⽅法对Statement进⾏设值;
  2. StatementHandler通过List query(Statement statement, ResultHandler resultHandler)⽅法来完成执⾏Statement,和将Statement对象返回的resultSet封装成Listimage.png
  • 调⽤preparedStatemnt.execute()⽅法,然后将resultSet交给ResultSetHandler处理

    ResultHandler

    1. public List<Object> handleResultSets(Statement stmt) throws SQLException {
    2. ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
    3. //多ResultSet的结果集合,每个ResultSet对应⼀个Object对象。⽽实际上,每个Object是List<Object> 对象。
    4. //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,也就是说,multipleResults最多就⼀个元素。
    5. List<Object> multipleResults = new ArrayList();
    6. int resultSetCount = 0;
    7. //获得⾸个ResultSet对象,并封装成ResultSetWrapper对象
    8. ResultSetWrapper rsw = this.getFirstResultSet(stmt);
    9. //获得ResultMap数组
    10. //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,也就是说,resultMaps就⼀个元素。
    11. List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
    12. int resultMapCount = resultMaps.size();
    13. this.validateResultMapsCount(rsw, resultMapCount);
    14. while(rsw != null && resultMapCount > resultSetCount) {
    15. //获得ResultMap对象
    16. ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
    17. //处理ResultSet,将结果添加到multipleResults中
    18. this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
    19. //获得下⼀个ResultSet对象,并封装成ResultSetWrapper对象
    20. rsw = this.getNextResultSet(stmt);
    21. //清理
    22. this.cleanUpAfterHandlingResultSet();
    23. ++resultSetCount;
    24. }
    25. String[] resultSets = this.mappedStatement.getResultSets();
    26. if (resultSets != null) {
    27. while(rsw != null && resultSetCount < resultSets.length) {
    28. ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
    29. if (parentMapping != null) {
    30. String nestedResultMapId = parentMapping.getNestedResultMapId();
    31. ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
    32. this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
    33. }
    34. rsw = this.getNextResultSet(stmt);
    35. this.cleanUpAfterHandlingResultSet();
    36. ++resultSetCount;
    37. }
    38. }
    39. //如果是multipleResults单元素,则取⾸元素返回
    40. return this.collapseSingleResultList(multipleResults);
    41. }

    3.2 代理方式

    1 初始化时

  • 首先在加载配置文件的时候,会把Mapper里的内容加载到MapperRegistry中。

  • MapperRegistryConfiguration的一个属性。
  • MapperRegistry 中用Map<Class<?>, MapperProxyFactory<?>> knownMappers,通过传过来的类型(key)存放mapper的动态代理工厂。

    2 getMapper()时

  • DefaultSqlSession->configuration.getMapper()->mapperRegistry.getMapper()

image.png

  • 进入MapperProxyFactory中的newInstance方法,这里就创建了代理对象

image.png

3 使用代理mapper调用方法

  • 代理对象调用接口中的任意方法,执行的都是动态代理中的**invoke**方法
  • 所以,找到MapperProxy<T>invoke方法,由于版本不同中间细节不同,但最终会去执行MapperMethod中的execute(sqlSession, args)方法

image.png

  • SELECT为例,根据返回结果的不同,执行的方法也不同。
  • 返回结果为List<E>时,会调用executeForMany(sqlSession, args)

image.png

  • executeForMany(sqlSession, args)则会调用SqlSession中的方法,下面就和传统方法一样了。

    4 二级缓存

    二级缓存(Mapper级别/Namespace级别)
    Mybatis默认只开启了一级缓存(SqlSession级别);如果开启了二级缓存,则Mybatis的加载顺序为:
    二级缓存-> 一级缓存-> 数据库
    与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的
    MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。

    1 Cache标签

  • 解析Cache标签

XMLMapperBuilder中的void configurationElement(XNode context)方法
image.png

  • 创建Cache对象。

this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
image.png

  • Cache对象包装到MapStatement

image.png

  • Mapper中每个标签对应一个MapStatment;所有MapStatement共用一个cache对象

parseStatementNode方法的最后一行this.builderAssistant.addMappedStatement

  • addMappedStatement中,.cache(this.currentCache);把Cache对象设置进去了

    1. org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType))
    2. .resource(this.resource)
    3. .fetchSize(fetchSize)
    4. .timeout(timeout)
    5. .statementType(statementType)
    6. .keyGenerator(keyGenerator)
    7. .keyProperty(keyProperty)
    8. .keyColumn(keyColumn)
    9. .databaseId(databaseId)
    10. .lang(lang)
    11. .resultOrdered(resultOrdered)
    12. .resultSets(resultSets)
    13. .resultMaps(this.getStatementResultMaps(resultMap, resultType, id))
    14. .resultSetType(resultSetType)
    15. .flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect))
    16. .useCache((Boolean)this.valueOrDefault(useCache, isSelect))
    17. .cache(this.currentCache);

    2 二级缓存流程

  • 当开启sqlMapConfig.xml中的二级缓存配置时,在底层调用Executor执行查询的时候调用的是CachingExecutor实现。

    1. <settings>
    2. <setting name="cacheEnable" value="true"/>
    3. </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

image.png

3 TransactionalCacheManager

注意⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置 中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个 事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中 tcm 变量对应的类型。

  1. TransactionalCacheManager中的:

private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap();
用来记录缓存和TransactionalCache的关系。而TransactionalCache则是具体解决问题的类。

  1. TransactionalCache中:

整整的缓存对象:
image.png
事务被提交前,所有从数据库中查询的结果将在缓存在此集合中:
image.png
事务被提交前,当缓存未命中时,CacheKey 将被存储在此集合中:
image.png

  1. 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集合中

image.png

  • 遍历所有属性,如果设置了延迟加载,则创建代理对象

configuration.getProxyFactory().createProxy(...)
image.png

  • 通过**JavassistProxyFactory**创建代理对象

configuration.getProxyFactory().createProxy(...)调用:
image.png

  • EnhancedResultObjectProxyImpl._createProxy_

image.png

  • JavassistProxyFactory.crateProxy(....)

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