美文网首页
Mybatis源码阅读(三)SqlSession的创建和运行

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

作者: 竹本辰 | 来源:发表于2018-10-28 16:48 被阅读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是如何进行查询的呢?

    下一篇文章再见!

    相关文章

      网友评论

          本文标题:Mybatis源码阅读(三)SqlSession的创建和运行

          本文链接:https://www.haomeiwen.com/subject/acfmtqtx.html