MyBatis源码阅读

通过一次SQL查询,查看mybatis源码记录。

1. MyaBatis原生API使用

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 第一步
sqlSessionFactory = builder.build(inputStream);
// 第二步
SqlSession session = sqlSessionFactory.openSession();
// 第三步
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 第四步
Blog blog = mapper.selectBlogById(100001);
// 第五步
session.close();

2. 解析配置文件

(1)解析的时候都做了什么
  • 使用XMLConfigBuilder解析mybtais-config.xml文件。

  • XMLConfigBuilder.java

    // mybtais-config 配置文件解析,生成一个configuration对象
      public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
      // 从这里开始处理mybatis-config。xml配置文件,从configuration根节点开始
    private void parseConfiguration(XNode root) {
      try {
        // issue #117 read properties first
        // properties 标签处理
        propertiesElement(root.evalNode("properties"));
        // settings 设置标签处理
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        loadCustomLogImpl(settings);
        // typeAliases 别名标签处理
        typeAliasesElement(root.evalNode("typeAliases"));
        // plugins 插件标签处理
        pluginElement(root.evalNode("plugins"));
        // objectFactory 标签处理
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // configuration中属性赋值
        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);
      }
    }
    
  • 解析Environment配置,创建数据源和事务工厂。environmentsElement()方法

    if (isSpecifiedEnvironment(id)) {
      // 事务控制 JDBC 或者 MANAGED
      TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
      // 获取数据源
      DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
      DataSource dataSource = dsFactory.getDataSource();
      // 创建一个环境信息类
      Environment.Builder environmentBuilder = new Environment.Builder(id)
          .transactionFactory(txFactory)
          .dataSource(dataSource);
      // configuration中配置enviroment
      configuration.setEnvironment(environmentBuilder.build());
    }
    
  • mapper映射器解析,使用XMLMapperBuilder解析映射器文件。

    // mapper映射器配置,也是使用package和mapper两种方式配置 package需要配置接口的实现类
    private void mapperElement(XNode parent) throws Exception {
      if (parent != null) {
        for (XNode child : parent.getChildren()) {
          if ("package".equals(child.getName())) {
            String mapperPackage = child.getStringAttribute("name");
            configuration.addMappers(mapperPackage);
          } else {
            // 非 package方式配置 ,又分为resource url class 三种配置方式,三种方式是互斥的
            String resource = child.getStringAttribute("resource");
            String url = child.getStringAttribute("url");
            String mapperClass = child.getStringAttribute("class");
            if (resource != null && url == null && mapperClass == null) {
              // 我使用的是resource方式配置
              ErrorContext.instance().resource(resource);
              // resource中配置的是个路径,拿到之后可以读取mapper映射器文件
              InputStream inputStream = Resources.getResourceAsStream(resource);
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            } else if (resource == null && url != null && mapperClass == null) {
              ErrorContext.instance().resource(url);
              InputStream inputStream = Resources.getUrlAsStream(url);
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            } else if (resource == null && url == null && mapperClass != null) {
              Class<?> mapperInterface = Resources.classForName(mapperClass);
              configuration.addMapper(mapperInterface);
            } else {
              throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
            }
          }
        }
      }
    }
    
  • 解析mapper文件

    InputStream inputStream = Resources.getResourceAsStream(resource);
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    mapperParser.parse();
    
  • 按配置顺序开始解析mapper文件

    // 从 mapper 根结点开始解析
    private void configurationElement(XNode context) {
      try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
          throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析cache属性,只有配置这个,才会创建一个Cache对象,这个对象经过了好几层的封装
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
      } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
      }
    }
    
  • XMLStatementBuilder用来解析mapper文件中配置的增删改查语句。所有的SQL语句都会封装成一个MappedStatement对象。

  • bindMapperForNamespace();把namespace对应的接口和它对应的MapperProxyFactory绑定到一起。

  • 最终通过new DefaultSqlSessionFactory(config)返回一个DefaultSqlSessionFactory对象。

(2)产生了哪些对象
  • 创建XMLConfigBuilder时同时创建了Configuration对象。专门解析mybatis-config.xml配置文件。

  • typeAliasRegistry 对象保存别名的配置。

    数据结构是Map<String, Class<?>> typeAliases = new HashMap<>();

  • typeHandlerRegistry保存类型转换映射。

    数据结构是Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();

  • XMLMapperBuilder专门解析映射器文件。

  • XMLStatementBuilder专门解析增删改查语句。

  • MappedStatement用来保存SQL语句的解析结果,由MapperBuilderAssistant创建,并把Cache对象放到MappedStatement中

  • Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>()。这是mybtais自己实现的一个map,在保存的时候会把statementId对应的SQL语句按namespace+statementId和statementId两种key,分别保存一次。即一个ms对象在map中用两个长度不同的key保存了两次。

  • mapperRegistry保存mapper接口和对应的工厂类。

    数据结构是Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  • MapperProxyFactory

  • DefaultSqlSessionFactory

(3)解析结果是怎么保存的
  • Configuration保存了所有的属性。
  • typeAliasRegistry 对象保存别名的配置
  • typeHandlerRegistry保存类型转换映射
  • mapperRegistry保存mapper接口和对应的工厂类
  • mappedStatements用来保存ms对象

3. 获取SqlSession

从 sqlSessionFactory.openSession();开始获取到一个sqlSession对象。

DefaultSqlSessionFactory.java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // mybatis-config.xml中配置的environment对应的对象
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 获取一个事务
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 创建一个执行器
    final Executor executor = configuration.newExecutor(tx, execType);
    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();
  }
}

需要注意一下的是configuration.newExecutor(tx, execType);这句话去创建一个Executor执行器。

Configuration.java
// 在Configuration类中创建执行器 创建一个executor执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  // 根据参数创建具体类型的执行器
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  // 如果二级缓存开关开了,用 CachingExecutor修饰一下当前执行器
  // cacheEnabled=true走这一句,默认也是true
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  // 插件植入操作
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
返回SqlSession

最终返回一个DefaultSqlSession类型的SqlSession。

4. 获取Mapper

BlogMapper mapper = session.getMapper(BlogMapper.class);

session是DefaultSqlSession类型的对象。从DefaultSqlSession中的getMapper方法开始看。

(1)mapper对象是什么
  1. 从DefaultSqlSession中的getMapper方法一路往下可以看到,最终从mapperRegistry中获取到了在解析mapper文件时绑定的对应的mapperProxyFactory的工厂类。

  2. mapperProxyFactory.newInstance(sqlSession) 通过这句话就返回了一个mapper对象。mapper对象到底是什么呢?是一个动态代理类。

  3. MapperProxyFactory.java

    // JDK的动态代理
    protected T newInstance(MapperProxy<T> mapperProxy) {
      // jdk动态代理的返回对象类型和 class是保持一致的。因此返回的就是 BlogMapper
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    
    public T newInstance(SqlSession sqlSession) {
      // 创建一个MapperProxy类,这个类实现了InvocationHandler接口,触发管理类h
      final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
      return newInstance(mapperProxy);
    }
    
(2)为什么要通过sqlSession去创建

我们已经知道,最终创建的实际上就是BlogMapper的一个代理对象。而这个代理对象的属性包含sqlSession、mapperInterface和methodCache。

5. 执行SQL(mapper方法调用)

所有获得的mapper接口对象,都是JDK动态代理过的对象,在真正进行方法调用的时候是通过invoke()方法对真实方法进行调用,那么这个代理类要做的事情是:

(1)怎么根据方法名找到对应的statement并且去执行它呢?(以查询为例)

a. 从MapperProxy类中的invoke()方法开始执行。return cachedInvoker(method).invoke(proxy, method, args, sqlSession);

b. 执行PlainMethodInvoker中的invoke方法。

c. mapperMethod.execute(sqlSession, args)

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
  // 真正开始要执行sql语句了
  return mapperMethod.execute(sqlSession, args);
}

d. 一直往下调用,最终又调回到DefaultSqlSession中的SQL执行语句

// 一直往下调用,最终又调回到DefaultSqlSession中的SQL执行语句
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

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

使用 Executor 执行对应query方法,这里的Executor一般是CachingExecutor类型
如果有插件拦截,先走插件拦截;如果二级缓存开关没有关,再走CachingExecutor;最后走BaseExecutor中的方法。

f.走到CachingExecutor的query中

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  // 计算一个cacheKey,判断是否可以从缓存中得到执行结果
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

cacheKey例子:

-1281788015:884735269:com.tomas.mysql.mybatis.mapper.BlogMapper.selectBlogById:0:2147483647:select * from blog where bid = ?:100001:development

g. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

Cache cache = ms.getCache();
// 如果没有配置缓存,就没有获取缓存、更新缓存的操作
if (cache != null) {
  // 获取缓存
  List<E> list = (List<E>) tcm.getObject(cache, key);
  ......
    // 添加到缓存中,实际上只是到了一个临时map中,结果并没有在缓存对象中。
    tcm.putObject(cache, key, list);
}
// 二级缓存是和事务绑定的,只有事务成功执行,才会真正的写数据到二级缓存中,防止因事务执行失败回滚导致缓存脏数据的情况的发生。

h. 最后执行到BaseExcutor类中的query方法。一层一层调用之后,最后调用simpleExecutor类的doQuery()方法。再往下就是调用jdbc的查询了。

(2)它是怎么从invoke方法中去找到真正要执行的SQL呢?

在执行cachedInvoker(method)的时候把method封装成了MapperMethod对象。MapperMethod中包含sqlCommand属性和MethodSignature属性。最后MapperMethod对象保存在PlainMethodInvoker对象中。

6. 关闭session

session.close()方法。如果用到了缓存,在事务执行成功之后,遍历map,把查询结果放到缓存对象中。

7. 插件可以拦截的类

opensession()时处理Executor

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

执行SQL创建StatementHandler时下面三个一起处理

  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

8. 一级缓存和二级缓存

一级缓存

BaseExecutor中有一个PerpetualCache(localCache 一级缓存)

二级缓存

new 一个PerpetualCache,然后用装饰器层层包装。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341