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

2018-10-28  本文已影响0人  竹本辰

接上一篇文章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是如何进行查询的呢?

下一篇文章再见!

上一篇下一篇

猜你喜欢

热点阅读