文档章节

Mybatis源码概览(一)

robin-yao
 robin-yao
发布于 2016/03/22 16:07
字数 1769
阅读 843
收藏 10

      一般拿到源码会无从下手,我的基本思路一般就是根据一个基本的helloWorld Debug下去,把主线先大概理一遍,然后再具体分析细节,没有必要一个类一个类细看,看了也会忘掉。自己理源码的时候看不下去时,可以结合网上的分析文章,一边看别人的解析,一边自己对照源码。了解框架设计原理,以后项目中出了问题可以更容易定位。再往上一层面,以后自己可以根据需求扩展框架。

先执行个HelloWorld

   去github上 clone Mybatis代码,然后再其测试源码里添加如下代码

示例代码,里面未贴出来的类自行补全。

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession(true);

        try {
            //后面介绍mybatis通过动态代理来避免手工调用session,直接调用dao接口;
            //BlogDao mapper = session.getMapper(BlogDao.class);
            //List<Blog> blogs= mapper.selectAll();
            List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");
            System.out.println(blogs);

        } finally {
            session.close();
        }

  mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/BlogMapper.xml"/>
    </mappers>
</configuration>

  BlogMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.robin.BlogDao">
    <resultMap type="org.apache.ibatis.robin.Blog" id="blog">
        <result property="id"        column="id"/>
        <result property="context"     column="context"/>
        <result property="dateCreate"  column="date_create"/>
    </resultMap>
    <insert id="insert" parameterType="org.apache.ibatis.robin.Blog">
            insert into blog(id,context,date_create)
            values (#{id},#{context},now())
    </insert>

    <select id="selectAll" resultMap="blog" flushCache="true" >
        SELECT * from blog
    </select>
</mapper>

注意xml文件放到项目的resource位置,可以通过ide来设置,否则程序会获取不到。 


一步一步DEBUG

    解析配置,构建SqlSessionFactory

    从上面的代码可以看到mybatis重要的执行顺序:输入配置流,由SqlSessionFacotryBuilder来根据输入的配置构建出来一个SqlSessionFactory。

//简略代码 
//解析配置
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//根据配置返回SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

   

    构建打开SqlSession (executor生成,plugin织入,缓存开闭)

    获取到SqlSessionFactory后,下一步肯定是获得sqlSession。

sqlSessionFactory.openSession(true);
//这里的true 表示是否自动commit


//debug 进去 这部分是openSession的大体过程;
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //先通过configuration获取我们再xml中配的environment,里面包含事务和DataSource;
    final Environment environment = configuration.getEnvironment();
    //这一步是获取JDBC事务
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    
    //打开Executor过程,见下段落的分析
    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();
  }
}


   打开Executor的过程,Executor默认的类型是SIMPLE

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);
  }
  //如果在配置文件settings中打开 <setting name="cacheEnabled" value="true" /> 
  // cacheEnabled 着为true,开启全局缓存,也就是二级缓存;
  //下面查询具体分析CachingExecutor执行查询过程
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  //如果自定义了Plugin拦截器,在xml通过plugins配置后,这一步会通过JDK动态代理织入到executor中,
  //生成一个带了拦截器方法功能的Executor
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}


在根BaseExecutor中可以看到 ,Executor不仅包含了事务,还同时加入localCache ,也就是Session级别的缓存,也是大家常叫的一级缓存,这里提一下一级缓存是默认的,如果非要去掉只能通过在select 语句配置中 flushCache="true"。

protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
  this.localCache = new PerpetualCache("LocalCache");
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
}

  Executor组装好之后通过new DefaultSqlSession返回我们的SqlSession;

new DefaultSqlSession(configuration, executor, autoCommit);
  
    执行具体查询(重要概念MappedStatement)

SqlSession是直接对数据库发号施令的组件。通过发起下面一个SQL查询,继续Debug进去

List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");

  先mybatis自动补全一些默认参数(rowBounds主要是指代返回的行数限制)后,进去下面的代码

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //MappedStatement是Mybatis的精髓,如果Bean对Spring是一样的道理;这里我们要重点介绍下
    //属性MappedStatement 是一个SQL执行语句的包装,里面的属性有SqlSource(指代SQL具体的语句);
    //属性StatementType 如果是PREPARED表明执行预编译执行,这样不仅防止SQL注入,而且还能避免SQL重复解析;
    //属性id 指代我们唯一确定MappedStatement唯一标识;
    //还有ResultMap 和ParameterMap等等,这里就不解释来;
    //可以说MappedStatement是发起SQL请求的所需的必备数据;
    MappedStatement ms = configuration.getMappedStatement(statement);
    //进行query查询,转下段分析
    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();
  }
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //获取SQL
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //通过参数,sql,和rowBounds一起拼装出cacheKey
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//进入到查询
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  //先判断是否有缓存中去取
  Cache cache = ms.getCache();
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  //缓存中取不到,直接执行query,这里delegate指代我们前面生成的SimpleExecutor;
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//跳转到下面的方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  //清掉cache
  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) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}
//从这个方法可以明显看出里面的localCache,指代前面说的Session缓存,即一级缓存
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    //这一步主要是获取Connection,然后准备PreparedState,执行查询返回结果
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  //把新结果缓存起来
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

至此我们的查询SQL就执行完了。

这一路涉及了 Configuration --->SqlSessionFactory------>SqlSession(里面又包装了 Executor,Cache,MappedStatement) 这几个重要概念;

下节主要讲Mybatis通过代理 把原先自己需要直接调用 sqlSession来执行 改成只需调用相应dao接口类,在实际项目中省掉大量代码,以及与spring结合的实现原理;


本文链接 http://my.oschina.net/robinyao/blog/645263

© 著作权归作者所有

robin-yao
粉丝 164
博文 54
码字总数 61436
作品 0
杭州
私信 提问
Mybatis源码概览(二) ---Plugin扩展与Spring结合原理

本文主要介绍Mybatis通过动态代理避免对sqlSession直接调用,而是通过MapperProxy代理技术生成了具体dao接口的Mapper实例,里面封装了对sqlSession的调用;Mybatis预留了Interceptor接口,用...

robin-yao
2016/03/23
530
0
Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析

相关文章 Mybatis 解析配置文件的源码解析 Mybatis 类型转换源码分析 Mybatis 数据源和数据库连接池源码解析(DataSource) Mybatis Mapper 接口源码解析(binding包) Mybatis 解析 SQL 源码...

tsmyk0715
2018/11/18
0
0
深入浅出MyBatis_Index

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/22
932
0
早前学习Java记录

Spring 对 iBATIS 的支持】 Spring 通过 DAO 模式,提供了对 iBATIS 的良好支持。 SqlMapClient:是 iBATIS 中的主要接口,通过 xml 配置文件可以让 Spring 容器来管理 SqlMapClient 对象的创...

大风厂蔡成功
2016/07/10
43
0
MyBatis 源码分析——介绍

笔者第一次接触跟MyBatis框架是在2009年未的时候。不过那个时候的他并不叫MyBatis,而是叫IBatis。2010年的时候改为现在的名字——MyBatis。这几年过去了,对于笔者来讲有一点陌生了。而且那...

Java小铺
2018/08/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

mac搭建mysql环境

这里记录一下mac中搭建mysql中环境的过程,主要记录一下操作,以便日后再次安装。 进入mysql官方网站下载dmg包,解压,安装,生成数据库登录密码。 在bash_profile中加入PATH=$PATH:/usr/loc...

JerryLin123
26分钟前
1
0
以太坊如何计算交易成本

在发送比特币交易时,其费用与其大小成比例。输入和输出越多,它就越贵。再加上未决交易的因素,交易费用可能仅基于这两个因素就会飙升。 对于以太坊,鉴于我们正在谈论协议中的编程语言,对...

笔阁
34分钟前
1
0
java修饰符的一些问题

作者总结的好 http://www.cnblogs.com/lixiaolun/p/4311727.html

南桥北木
37分钟前
1
0
Fabric-sdk-java链码访问快速上手【无痛】

在超级账本Fabric区块链中,应用通过节点的RPC协议接口访问链码。Java应用可以使用官方提供的Fabric-sdk-java开发包来实现对链码的访问,开发包封装了Fabric区块链的GRPC链码访问协议,有利于...

geek12345
38分钟前
1
0
python setup.py egg_info" failed with error code 1 in /tmp/pip-install-fwot3_uw/mysqlclient/

解决方法: yum install python-devel yum install mysql-devel yum install gcc

MedivhXu
41分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部