Mybatis源码阅读(三)SqlSession的创建和运行

接上一篇文章SqlSessionFactory的创建
https://www.jianshu.com/p/eb3d06a7c77d

SqlSession的创建过程

既然已经得到了SqlSessionFactory,那么SqlSession将由SqlSessionFactory进行创建。

SqlSession sqlSession=sqlSessionFactory.openSession();

这样,我们就来看看这个SqlSessionFactoryopenSession方法是如何创建SqlSession对象的。根据上面的分析,这里的SqlSessionFactory类型对象其实是一个DefaultSqlSessionFactory对象,因此,需要到DefaultSqlSessionFactory类中去看openSession方法。

// DefaultSqlSessionFactory 类

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

/**
 * 这里对参数类型进行说明
 * ExecutorType 指定Executor的类型,分为三种:SIMPLE, REUSE, BATCH
 * TransactionIsolationLevel 指定事务隔离级别
 * 使用null,则表示使用数据库默认的事务隔离界别
 * autoCommit 是否自动提交
 */
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);
        // 创建Executor,即执行器
        // 它是真正用来Java和数据库交互操作的类,后面会展开说。
        final Executor executor = configuration.newExecutor(tx, execType);
        // 创建DefaultSqlSession对象返回,因为SqlSession是一个接口
        // 可以类比DefaultSqlSessionFactory
        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();
    }
}

这样我们就得到了DefaultSqlSession(SqlSession)。可以看到基本覆盖数据库的各种操作,增删查改,以及简单的事务的操作。

image-20181028144109252

接下来就要看看它的执行过程。

SqlSession的执行过程

获取到了SqlSession之后,则需要执行下面的语句

CountryMapper countryMapper=
    sqlSession.getMapper(CountryMapper.class);

因此我们要看看getMapper这个方法干了什么。上面的分析知道,这里的sqlSession其实是DefaultSqlSession对象,因此需要在DefaultSqlSession中去查看这个方法。

// DefaultSqlSession 类
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
}

这里的configurationConfiguration类的实例,在SqlSessionFactory中创建并完成所有配置的解析后,初始化DefaultSqlSession时,SqlSessionFactory将配置作为属性传给DefaultSqlSession,因此前面解析的所有配置,都能在这里查到。

因此我们继续往下看,这里就要定位到Configuration类的getMapper方法了。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // mapperRegistry 是一个mapper配置的容器,前面有提到
    // 配置路径下所有扫描到的mapper在初始化完成Configuration以后,都会加载进来
    // 每一个mapper都被存储在了MapperRegistry的knownMappers中了
    // 在初始化配置的时候执行addMapper,在获取Mapper的时候执行getMapper
    return mapperRegistry.getMapper(type, sqlSession);
}

因此,我们就要来看看MapperRegistrygetMapper方法

// MapperRegistry的getMapper方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从knownMappers集合中获取mapper,创建MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 获取代理对象,并返回
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

看到了MapperProxyFactory很明显这是一个工厂类,所以肯定会有MapperProxy这么一个类,而看到Proxy,这里肯定用到了代理,也肯定就是动态代理了。

我们来看看获取代理对象的方法 newInstance

// MapperProxyFactory 类
...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    // MapperProxy实现了InvocationHandler,扩展了invoke方法,维护代理逻辑。
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
...

这里我们可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象。这里最终返回了CountryMapper接口的代理对象。

而代理对象则被放到了MapperProxy中。通过idea打断点,来查看CountryMapper的详细信息,我们也可以看到这是一个MapperProxy对象。

image-20181028102601358

因此,在执行countryMapper.selectAll()方法时,便会进入到MapperProxy的invoke方法中来。

我们来看一下MapperProxy的部分代码。

// MapperProxy类
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 判断是否是一个类
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 显然,我们这里是一个接口,则执行下面的流程
    // 生成MapperMethod对象,通过cachedMapperMethod初始化
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 执行execute方法, 把sqlSession和当前参数传递进去
    return mapperMethod.execute(sqlSession, args);
}
...

接着来看看execute方法

// MapperMethod 类的方法
// MapperMethod采用命令模式运行,根据上下文跳转

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        ...
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                // 主要看这个方法
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        ...
    }
}

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 将Java参数转换为Sql命令行参数
    Object param = method.convertArgsToSqlCommandParam(args);
    // 是否需要分页
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        // 通过SqlSession对象执行查询,带分页
        // command.getName() 获取Mapper接口当前执行方法selectAll的全路径名
        result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
        // 通过SqlSession对象执行查询,不带分页
        result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
            return convertToArray(result);
        } else {
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
    }
    return result;
}

至此已经基本可以明白了,Mybatis为什么只用Mapper接口就可以运行SQL,因为映射器的XML文件的命名空间对应的便是这个接口的全路径,它能根据全路径和方法名绑定起来,通过动态代理技术,让这个接口跑起来。然后采用命令模式,根据上下文跳转,最终还是使用SqlSession接口的方法使得它能够进行查询。

接着我们就来看看selectList的源码。

result = sqlSession.<E>selectList(command.getName(), param);

注意这里的sqlSession,其实是DefaultSqlSession的对象,因此要去看DefaultSqlSession的selectList方法。

@Override
public <E> List<E> selectList(String statement, Object parameter) {
    // 分页参数选择默认,表示不分页
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 从Configuration配置中获取MappedStatement对象
        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();
    }
}

接着就进入了Executor查询之后,将结果返回。那么Executor是如何进行查询的呢?

下一篇文章再见!

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

推荐阅读更多精彩内容

  • 1 Mybatis入门 1.1 单独使用jdbc编程问题总结 1.1.1 jdbc程序 上边使...
    哇哈哈E阅读 3,306评论 0 38
  • 单独使用mybatis是有很多限制的(比如无法实现跨越多个session的事务),而且很多业务系统本来就是使用sp...
    七寸知架构阅读 3,446评论 0 53
  • # 前言 在java程序员的世界里,最熟悉的开源软件除了 Spring,Tomcat,还有谁呢?当然是 Mybat...
    莫那一鲁道阅读 3,290评论 3 11
  • 在床上待了一天,肯定了自己一年以来狂补性知识的工作,看了一些接地气的帖子,把对未来的期待调整得宽泛而具体,艰难地作...
    胡涂格格阅读 137评论 0 0
  • 今天5月21日星期二小雨 今天早上起床一看下雨了,起床做饭,宝宝们吃了饭,宝爸去送的大宝,下雨了和二宝在家玩了一上...
    fba947bf0ca5阅读 168评论 0 0