Mybatis核心原理

本文阐述了Mybatis的查询执行流程,包含Session的获取、数据源信息加载以及查询过程中使用的一级、二级缓存。有疏漏之处,还望指出。

1.获取mybatis定义的xml文件。加载数据源配置信息。得到InputStream。

String resource = "mybatis-config.xml";

InputStream inputStream = Resources.getResourceAsStream(resource);

2.获取DefaultSqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这一步加载mybaits配置,解析xml文件得到Document对象,保存在SqlSessionFactory的Configuration

build时使用Configuration构造DefaultSqlSessionFactory,加载xml mapper,生成MappedStatement放入Configuration中的HashMap中缓存

DefaultSqlSessionFactory打开session

SqlSession session = sqlSessionFactory.openSession();

openSession执行流程:

Environment environment = configuration.getEnvironment();

DefaultSqlSessionFactory中的openSession,获取Configuration中的Environment。Environment含有数据连接的所有信息(URL,PWD,USER AND ETC)。

final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

通过environment获得事务工厂TransactionFactory,产生JdbcTransaction事务。JdbcTransaction持有

Connection,可以控制Connection的commit、rollback、close等方法。

Executor executor = configuration.newExecutor(tx, execType);

通过configuration的newExecutor方法使用JdbcTransaction新建Executor。

Executor有四种:

  • BatchExecutor
  • ReuseExecutor
  • SimpleExecutor
  • CachingExecutor

默认为CachingExecutor

Executor拥有对数据操作的全部方法。如query、update、commit(通过事务实现)。这里CachingExecutor为Executor的装饰者。

 

创建Executor的最后一步是植入插件pluginAll---https://www.cnblogs.com/xrq730/p/6984982.html

executor = (Executor) interceptorChain.pluginAll(executor);

(PS:生成插件共有四处:ParameterHandler/ResultHandler/StatementHandler/Executor)

IntercptorChain中加载有Configuration的来自于XML定义的插件。IntercptorChain对所有的插件进行plugin().Plugin是一个InvocationHandler,代理执行器。Plugin会调用wrap对原始对象(3中四种对象)进行包装,获取插件的类、接口、方法信息后,通过Proxy生成代理类,返回代理对象。当对象调用插件拦截的方法时,InvocationHandler会调用插件的intercept方法。

 

5.构建DefaultSqlSession。使用Configuration、executor创建DefaultSqlSession。

至此,SqlSession创建完成。


查询SelectOne:

 

1.DefaultSqlSession执行SelectOne

获取Configuration中的MappedStatement.调用executor的query方法,传入MappedStatement、查询 参数、查询行数。

MappedStatement ms = configuration.getMappedStatement(statement);

executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

 

使用MappedStatement创建查询Sql,并生成缓存的Key。这里的缓存为(第)二级缓存,执行清缓存操作。

缓存原理:

判断是否缓存命中,命中直接取出返回。否则代理的方式调用BaseExecutor的query方法委托查询。

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) {

       //装饰者模式调用 BaseExecutor

      list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

      tcm.putObject(cache, key, list); // issue #578 and #116

    }

    return list;

  }

}

return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);//不使用缓存

 

其中,二级缓存为TransactionalCacheManager持有的一个Map。

Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

二级缓存是跨 Session 共享缓存。随着session的commit,缓存清空失效。

 

一级缓存是Session级别共享。

BaseExecutor的query方法首先进行一级缓存判断。缓存使用PerpetualCache实现。PerpetualCache为最基础的缓存类,使用Map实现。

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

  }

  // issue #601

  deferredLoads.clear();

  if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

    // STATEMENT为语句级

    // SESSION为会话级

    // issue #482

    clearLocalCache();

  }

二级缓存补充:

对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。

 

但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。

二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。

举个简单的例子:

订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。

 

根据以上,想要使用二级缓存时需要想好两个问题:

1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。

2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值