mybatis源码解析

首先以如下测试代码进行追踪
public class TestMybatis {
    public static void main(String[] args) throws IOException {
        InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
        SqlSession sqlSession = build.openSession();
        TestMapper mapper = sqlSession.getMapper(TestMapper.class);
        Goods goods = mapper.queryById(7);
        sqlSession.commit();
        System.out.println(goods);
    }
}
一、启动解析配置文件

mybatis以SqlSessionFactoryBuilder为入口,在其bulid方法中,创建XMLConfigBuilder,解析配置文件,以下是调用XMLConfigBuilder中的parse方法,在parse方法中解析/configuration的根节点

    private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

mapperElement方法主要负责mappers节点的解析,主要有两种方法的解析,
一种是package子节点配置的name属性,也就是通过包扫描的方法加载mapper;
另外一种是mapper子节点,通过配置resource、url或者class任意一种方法,去解析对应的mapper

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

以package的方式为例,扫描指定包下的所有类,然后过滤出所有的接口类;创建一个MapperProxyFactory的代理工厂并放入Map<Class<?>, MapperProxyFactory<?>>容器中,然后创建一个MapperAnnotationBuilder扫描所有包含Select.class,SelectProvider.class等等这些注解的接口方法,调用MapperAnnotationBuilder的parse方法解析
package方法配置的,会通过接口的全路径名称,找到其对应的xml配置文件,然后再使用XMLMapperBuilder解析xml配置文件

 private void loadXmlResource() {
        if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
            String xmlResource = this.type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = null;

            try {
                inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
            } catch (IOException var4) {
            }

            if (inputStream != null) {
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
                xmlParser.parse();
            }
        }

    }

最终所有的配置解析完成之后,由Configuration统一管理所有的静态配置。Mapper.xml的所有内容都放置在MappedStatement中

二、openSession

调用DefaultSqlSessionFactory的openSessionFromDataSource方法,该方法中,主要做了以下事情:
1.创建事务
2.创建执行器Executor
3.创建包含执行器和事务的DefaultSqlSession对象

通过Configuration的newExecutor方法创建执行器,默认创建SimpleExecutor,如果配置了二级缓存,那么创建CachingExecutor执行器

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object 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);
        }

        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }
         //插件的调用
        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }
三、创建Mapper的代理对象

首先获取第一步创建的MapperProxyFactory,通过MapperProxyFactory的newInstance方法创建MapperProxy对象,然后Mapper的代理对象

public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), 
                                                 new Class[]{this.mapperInterface}, mapperProxy);
    }
四、执行sql语句(以查询方法为例)

调用MapperProxy的invoke方法,然后调用其excute方法,决定执行的方法类型,这里调用的queryById方法最终执行的是sqlSession.selectOne->selectList->CachingExecutor.query方法

如果启用二级缓存,那么查询时首先会去二级缓存中获取数据,如果没有获取到才会执行后续查询操作

    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) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, parameterObject, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

没有查询到数据,执行BaseExecutor的query方法;调用this.localCache.getObject(key)从一级缓存中获取,没有查询到则进行数据库的查询

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        list = resultHandler == null ? (List) this.localCache.getObject(key) : null;
        if (list != null) {
            this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }

        return list;
    }

进入SimpleExecutor的doQuery方法,执行Configuration的newStatementHandler创建一个StatementHandler。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

在StatementHandler创建过程中,执行Configuration的newParameterHandler和newResultSetHandler分别创建ParameterHandler和ResultSetHandler,并且分别执行对应的插件

执行完StatementHandler的创建,执行SimpleExecutor的prepareStatement,获取数据库的链接,进行预编译以及参数的设置

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }

最后调用PreparedStatementHandler的query方法,通过PreparedStatement 执行sql并将结果交于ResultSetHandler处理,返回最终的结果

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleResultSets(ps);
    }
五、二级缓存

二级缓存的获取是通过TransactionalCacheManager的TransactionalCache获取,然后依次通过下列顺序逐级获取。

org.apache.ibatis.cache.TransactionalCacheManager#getObject
org.apache.ibatis.cache.decorators.TransactionalCache#getObject
org.apache.ibatis.cache.decorators.SynchronizedCache#getObject
org.apache.ibatis.cache.decorators.LoggingCache#getObject
org.apache.ibatis.cache.decorators.SerializedCache#getObject
org.apache.ibatis.cache.decorators.LruCache#getObject
org.apache.ibatis.cache.impl.PerpetualCache#getObject

但是在执行putObject的操作的时候,并没有像获取时那样逐级存入,而是存在了一个名为entriesToAddOnCommit的Map集合中。在最终执行sqlSession的commit操作或者close方法的时候,才会放入最终的PerpetualCache中。因为二级缓存真正生效是在会话提交或者关闭的时候,这样做的目的也是为了防止脏读。

org.apache.ibatis.cache.TransactionalCacheManager#putObject
org.apache.ibatis.cache.decorators.TransactionalCache#putObject

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

推荐阅读更多精彩内容