mybatis架构原理

mybatis是大家经常用到的ORM框架,之前一直使用却没有静下来心来好好整理下,今天抽空来整理整理框架以及流程,以便后续回顾学习使用。

本文参考的mybatis版本为:mybatis-3.5.2

1、首先来看下核心架构图

image

核心模块图

接口层:主要是sqlSession封装增删改查接口,提供给应用调用

核心层:核心层主要功能为配置解析、参数映射处理、动态SQL、核心执行引擎、以及结果映射处理等

基础层:包括缓存的封装、日志、反射机制、连接池管理等

2、执行流程图

1、贴一段正确调用的代码,方便理解流程图:

public static void main(String[] args) {
SqlSession sqlSession =null;
try {

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");

SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
// 打开session
sqlSession = sqlSessionFactory.openSession();
//查询数据库中数据
sqlSession.selectOne("XXX.getOrder",1);

}catch (Exception e) {
    e.printStackTrace();
}finally {
    if (sqlSession !=null) {
    sqlSession.close();
}
}
}

下面是主要流程:

image.png

3、主要类、接口以及职责

上面的流程一些同学看了觉得怎么那么简单?是的,主流程就是这么简单,但是里面的执行逻辑还是有点复杂,下面咱们分析主要核心流程内部是怎么处理的。

image

加载XML主流程入口

1、解析mybatis.xml,通过一组ClassLoaders以及文件路径,解析mybatis.xml并返回InputStream流

image

通过classload解析XML

2、创建SqlSessionFactory,通过SqlSessionFactoryBuilder的名字可以看出这是一个建造者设计模式。

进入核心代码:


public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser =new XMLConfigBuilder(inputStream, environment,properties);
        return build(parser.parse());
    }catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    }finally {
        ErrorContext.instance().reset();

    try {

        inputStream.close();

    }catch (IOException e) {
    }
  }
}

通过XMLConfigBuilder的parse方法,生成Configuration对象,然后生成SqlSessionFactory


public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

3、打开SqlSession


public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(),null,false);
}

通过getDefaultExecutorType我们可以跟踪到,默认使用的执行器类型是simple
protected ExecutorTypedefaultExecutorType = ExecutorType.SIMPLE;
下面是核心的获取sqlSession的过程:


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit) {

    Transaction tx =null;

    try {
        //从configuration中获取以及解析完成的环境变量信息,Environment中包含事务 工厂以及数据源对象
        final Environment environment =configuration.getEnvironment();
        //如果environment的事务工厂对象为空,则新建一个,否则返回environment中的事务对象
        final TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment);
        //根据数据源,事务级别以及是否自动提交标识,创建事务对象
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //创建核心SQL执行器
        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();
    }
}

下面看下final Executor executor =configuration.newExecutor(tx, execType);具体做了哪些事情

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        //下面2行代码没太看明白,第一行已经判断是非为空并设置默认值了,第二行executorType肯定不为空,这目的是什么?哪位老铁看懂了奥妙可以留言,不胜感激!!!
        executorType = executorType ==null ?defaultExecutorType : executorType;//1

        executorType = executorType ==null ? ExecutorType.SIMPLE : executorType;//2

        Executor executor;

        if (ExecutorType.BATCH == executorType) {
            //是否批量处理
            executor =new BatchExecutor(this, transaction);
        }else if (ExecutorType.REUSE == executorType) {
            //ReuseExecutor为可重用执行器,无需释放
            executor =new ReuseExecutor(this, transaction);
        }else {
            //最后兜底的就是SimpleExecutor啦,也是默认类型
            executor =new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
            //如果开启了缓存,则使用缓存执行器,上面的流程就忽略了
            executor =new CachingExecutor(executor);
        }
            //此处需要注意,是将执行器添加到所有插件拦截器里,所有的拦截器都能拦截到excutor的执行信息,具体看源码是通过JDK的动态代理实现的,此处就不展开讲解了
            executor = (Executor)interceptorChain.pluginAll(executor);
            return executor;
}

4、通过DefaultSqlSession执行SQL,以查询一条数据为例


@Override
public T selectOne(String statement, Object parameter) {
      // Popular vote was to return null on 0 results and throw exception on too many.
      List list =this.selectList(statement, parameter);
      if (list.size() ==1) {
          return list.get(0);
      }else if (list.size() >1) {
         //是不是经常看到这个错误^_^
         throw new TooManyResultsException("Expected one result (or null) to be  returned by selectOne(), but found: " + list.size());
      }else {
        return null;
     }
}

public List selectList(String statement, Object parameter, RowBounds rowBounds) {

    try {
        MappedStatement ms =configuration.getMappedStatement(statement);
        //executor终于开始干活了
        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 List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)throws SQLException {
    //获取装订好的SQL
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

执行查询


public List 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.");
    }
    if (queryStack ==0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List list;
    try {
        queryStack++;
        list = resultHandler ==null ? (List)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();
    }
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache();
    }
  }
    return list;
}

小结:

核心类以及职责:

SqlSessionFactoryBuilder:创建SqlSessionFactory工厂,此类可以被实例化、使用和丢弃,最佳使用就是用在局部方法变量

SqlSessionFactory:创建SqlSession,SqlSessionFactory最佳实践是在应用运行期间不要重复创建多次。

SqlSession :包含了面向数据库执行 SQL 命令所需的所有方法,增删改查等操作。另外SqlSession 非线程安全,所以不能线程间共享

Configuration:配置类,目的是为了在调用过程中从JVM的Heap内存中获取,防止每次都需要加载XML文件

Executor:SQL执行器,包括:BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor。 SqlSession是对外提供接口使用,Executor则是对DB使用

StatementHandler:Statement处理器,封装了Statement的各种数据库操作方法execute()

ResultSetHandler:返回结果集处理器,对返回的结果包装成具体的对象,完成ORM操作

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

推荐阅读更多精彩内容