Mybatis原理简介

185 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

Mybatis

1、基础架构介绍
  • API接口层
    • 就是我们接口调用的api,api层接收到调用请求,会到数据处理层完成相应的数据处理;
    • 主要是两种调用方式
      • 传统基于statementid的api访问模式
      • 基于Mapper接口代理方式
  • 数据服务处理层
    • 主要是负责sql的解析、执行、以及执行结果的映射处理等;总之就是通过它操作数据库;
  • 基础支撑层
    • 主要是构建一些框架的基础能力,例如连接管理,加载文件,加载缓存,事务管理等
2、流转过程
  1. 加载配置文件
    • 配置文件一般有基于XML和注解方式的,将主配置文件解析成Configuration,将sql配置解析成mappedstatement对象;
  2. 调用api接口层
    • 调用api传入sql的id和入参对象,到数据处理层处理数据
  3. 数据处理层处理api请求
    • 根据sql的id查出相对应的MappedStatement对象;
    • 根据传参对象解析TikTokReplyServiceImpl,得到待执行的sql语句和参数;
    • 获取连接,执行,得到结果
    • 结果映射,拿到最终解析过的结果
    • 关闭连接
3、源码分析

先了解一下几个重要组件

  • SqlSession:mybatis的顶层接口,表示跟数据库的会话,完成一些增删改查
  • Executor:执行器。负责sql语句的执行和缓存查询
  • ParameterHandler:将传参转成jdbc statment的参数
  • ResultSetHandler:将jdbc返回的set转换成list
  • TypeHandler: java类型跟jdbc类型之间的转换
  • SqlSource:动态的生成sql,封装到BoundsQL
  • BoundSql:存放动态生成的sql语句和参数
1、加载解析配置文件创建SqlSessionFactory

1、通过加载配置文件,构建一个SqlSessionFactory


SqlSessionFactory factory = new
   SqlSessionFactoryBuilder().build(xml);

2、将配置文件解析成Configuration,创建一个SqlSessionFactory

 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {// 解析配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

3、parser.parse() 解析成Configuration,就是用来保存配置文件的各种信息

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

4、解析XML,就是解析各种标签

private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
      //解析properties标签,并set到Configration对象中
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

5、新建并返回SqlSessionFactory

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
2、sql的执行过程

sqlsession

sqlsession.png

SqlSession是⼀个接⼝,两个实现类:DefaultSqlSession (默认)和 SqlSessionManager,SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀ 个SqlSession,并且在使⽤完毕后需要close;

  1. 获得sqlsession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 获取执行器
    final Executor executor = configuration.newExecutor(tx, execType);
      // 根据执行器获取一个SqlSession
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

mybatis的缓存

1、一级缓存

一级缓存属于SqlSession级别的。;

一级缓存是个啥?

public class PerpetualCache implements Cache {

  private final String id;
//这就是
  private Map<Object, Object> cache = new HashMap<Object, Object>();

实际上就是一个SqlSession的本地Map,每⼀个SqISession都会存放⼀个map对象的引⽤,他也随着sqlsession创建、使用、删除;

  • 缓存的删除

    • 更新操作删除缓存

    • @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);
      }
      
      
      
      
       @Override
        public void clearLocalCache() {
          if (!closed) {
            localCache.clear();
            localOutputParameterCache.clear();
          }
        }
      
      以及缓存:private Map<Object, Object> cache = new HashMap<Object, Object>();
      
      
  • 缓存的创建

    • 基于Executor执行器来创建以及一级缓存;

    • createCacheKey方法创建缓存并返回;

    • public abstract class BaseExecutor implements Executor {
          @Override
        public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        
          CacheKey cacheKey = new CacheKey();
          cacheKey.update(ms.getId());
          cacheKey.update(rowBounds.getOffset());
          cacheKey.update(rowBounds.getLimit());
          cacheKey.update(boundSql.getSql());
              cacheKey.update(value);
            }
          }
          if (configuration.getEnvironment() != null) {
            // issue #176
            cacheKey.update(configuration.getEnvironment().getId());
          }
          return cacheKey;
        }
      
  • 缓存查询

    • 在查询操作的时候,会创建缓存,当缓存存在数据直接返回,不存在则是查询数据库到缓存再返回;

    •  @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);
         return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
      
2、二级缓存
  • ⼆级缓存的原理和⼀级缓存原理类似
  • 第⼀次查询,会将数据放⼊缓存中,然后第⼆次查询则会直接去 缓存中取。
  • ⼀级缓存是基于sqlSession的,⽽⼆级缓存是基于mapper⽂件的namespace的
  • 意思是多个sqlSession可以共享⼀个mapper中的⼆级缓存区域;
  • 并且如果两个mapper的namespace 相 同,那么这两个mapper中执⾏sql查询到的数据也将存在相同的⼆级缓存区域 中;

⼆级缓存是基于⼀级缓存之上的,在查询时,⾸先会查询⼆级缓存,若⼆级缓存未命 中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。 ⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的 MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。