Mybatis源码剖析之Mybatis执行流程(proxy 代理)

预读

Mybatis在设计上,分为三层:接口层,数据处理层,框架支持层

在接口层中,分为
传统模式:通过sqlSession + statementId。
接口代理模式:sqlSession+mapper接口

准备

在这里插入图片描述
在这里插入图片描述

<configuration>

    <!--加载外部的properties文件-->
    <properties resource="jdbc.properties"></properties>

    <!--开启全局的二级缓存配置-->
<!--    <settings>-->

<!--        <setting name="lazyLoadingEnabled" value="true"/>-->
<!--    </settings>-->


    <!--给实体类的全限定类名给别名-->
    <typeAliases>
        <!--给单独的实体起别名-->
        <!--  <typeAlias type="com.xiaoxuu.pojo.User" alias="user"></typeAlias>-->
        <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
        <package name="com.xiaoxu.pojo"/>
    </typeAliases>


    <!--environments:运行环境-->
    <environments default="development">
        <environment id="development">
            <!--当前事务交由JDBC进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--当前使用mybatis提供的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射配置文件-->
    <mappers>
        <!-- <mapper class="com.xiaoxuu.mapper.IOrderMapperr"></mapper>-->
        <package name="com.xiaoxu.mapper"/>
    </mappers>
</configuration>

在这里插入图片描述

Mybatis执行流程(proxy 代理)

proxy代理的方式创建代理对象,通过代理对象调用,执行invocationHandler的invoke方法,本质上还是通过executor执行。

获取mappedStatement的方式,不再是传statementId字符串,而是通过 接口全限定类名+method名 构成statemennt字符串。

获取statementId的方式变了,这才是他们最大的区别


1、通过classLoader 读取核心配置文件 为输入流

在这里插入图片描述
在这里插入图片描述

注意:只要是流,底层就是数组,那么就存在两个指针 一个position指向数组存储元素的后一个节点,一个limit指向尾节点

2、解析配置文件,封装Configuration对象 创建DefaultSqlSessionFactory对象

在这里插入图片描述
在这里插入图片描述

通过XMLConfigBuilder创建XMLConfigParser
在这里插入图片描述
通过parse正式解析XML为Configuration对象
在这里插入图片描述

解析完后可以看到Configuration对象 包含了
数据源,事务管理器,解析到的Mappers,TypeHandler(数据库类型和JAVA类型互转),typeAlias(类型别名识别),mappedStatements(CURD标签对象),resultMaps(映射对象),cache等等

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意sql中的#{id},${id} 等占位符,全部会被解析到paramterMapping中
在这里插入图片描述
最终返回SqlSessionFactory

在这里插入图片描述

3、通过SqlSessionFactory创建sqlSession,创建事务对象Transaction,创建executor

在这里插入图片描述

注意executor,默认有三种 SImpleExecutor,ReuseExecutor,BatchExecutor。默认使用SImpleExecutor

在这里插入图片描述

在这里插入图片描述

3.1、获取全局环境,datasurce,事务管理器
在这里插入图片描述

3.2、默认创建一个sqlsession,那么就是开启事务,创建事务对象Transaction

在这里插入图片描述

3.3、通过指定的executor类型创建executor
在这里插入图片描述

3.4、创建SqlSession对象
在这里插入图片描述

4、sqlSession通过接口class+proxy生成代理对象

在这里插入图片描述
通过注册扫描到对应的mapper接口,封装成MapperProxyFactory
获取mapperProxyFactory
在这里插入图片描述

mapperProxyfactory本质上是一个代理工厂,通过代理工厂,调用jdk proxy 进行代理

在这里插入图片描述

在这里插入图片描述
mapperProxy是一个invocationHandler调度器,内部重写了invoke方法
在这里插入图片描述

在这里插入图片描述

5、通过代理对象调用,目标方法,本质上sqlSession进行调用,statementID = 接口全限定类名+方法名,获取mappedStatement

在这里插入图片描述
实际上还是走到了invoke

在这里插入图片描述

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
        switch (command.getType()) {
            case INSERT: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 执行 INSERT 操作
                // 转换 rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // 无返回,并且有 ResultHandler 方法参数,则将查询的结果,提交给 ResultHandler 进行处理
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                // 执行查询,返回列表
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                // 执行查询,返回 Map
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                // 执行查询,返回 Cursor
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                // 执行查询,返回单个对象
                } else {
                    // 转换参数
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 查询单条
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                            (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        // 返回结果为 null ,并且返回类型为基本类型,则抛出 BindingException 异常
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                    + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        // 返回结果
        return result;
    }

由于测试方法为findAll,点进去发现,本质上还是通过sqlSession进行调用

在这里插入图片描述

在这里插入图片描述

6、sqlSession调用对应方法执行sql

在这里插入图片描述

从configuration中获取mappedStatement
在这里插入图片描述
通过executor执行sql

在这里插入图片描述

6.1、获取BoundSql(sql,sql上解析的参数,用户传入的sql参数)

 // 获得 BoundSql 对象
    public BoundSql getBoundSql(Object parameterObject) {
        // 获得 BoundSql 对象
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        // 忽略,因为 <parameterMap /> 已经废弃,参见 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html 文档
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }

        // check for nested result maps in parameter mappings (issue #30)
        // 判断传入的参数中,是否有内嵌的结果 ResultMap 。如果有,则修改 hasNestedResultMaps 为 true
        // 存储过程相关,暂时无视
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            String rmId = pm.getResultMapId();
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }

        return boundSql;
    }

6.2、从mappedStatement中获取是否开启了缓存(二级缓存),如果开启了从缓存中(二级缓存中)获取,如果没有开启,从一级缓存中取

二级缓存存在于mapper,每一个mappedStatement都持有,一级缓存存在于sqlSession,因为一级缓存存在于SimpleExecutor,simpleExecutor存在于sqlSession中

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {

        // 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
        // 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
        // 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
        Cache cache = ms.getCache();

        // 如果配置文件中没有配置 <cache>,则 cache 为空
        if (cache != null) {
            //如果需要刷新缓存的话就刷新:flushCache="true"
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                // 暂时忽略,存储过程相关
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                // 从二级缓存中,获取结果
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 缓存查询结果
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                // 如果存在,则直接返回结果
                return list;
            }
        }
        // 不使用缓存,则从数据库中查询(会查一级缓存)
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

6.3、从sqlSession持有的一级缓存中获取,获取不到从数据中查

 public <E> List<E> 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());
        // 已经关闭,则抛出 ExecutorException 异常
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            // queryStack + 1
            queryStack++;
            // 从一级缓存中,获取查询结果
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // 获取到,则进行处理
            if (list != null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            // 获得不到,则从数据库中查询
            } else {
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // queryStack - 1
            queryStack--;
        }
        if (queryStack == 0) {
            // 执行延迟加载
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            // 清空 deferredLoads
            deferredLoads.clear();
            // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }

在这里插入图片描述

6.4、从数据库中查询数据

// 从数据库中读取操作
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 执行读操作
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 从缓存中,移除占位对象
            localCache.removeObject(key);
        }
        // 添加到缓存中
        localCache.putObject(key, list);
        // 暂时忽略,存储过程相关
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

6.5、数据库查询,先会获取statementHandler,其只要负责sql处理。而statementHandler有多种,默认使用PreparedStatementHandler。statementHandler 会设置boundSql(sql信息),parameterHandler(sql参数处理器),ResultSetHandler(结果集处理器),typeAlias,typeHandler(java与jdbc类型转换处理器),而这些处理器,在解析XML的时候已经存在,被封装到Configuration

在这里插入图片描述

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 获得 Configuration 对象
        this.configuration = mappedStatement.getConfiguration();

        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;

        // 获得 TypeHandlerRegistry 和 ObjectFactory 对象
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = configuration.getObjectFactory();

        // 如果 boundSql 非空,一般是写类操作,例如:insert、update、delete ,则先获得自增主键,然后再创建 BoundSql 对象
        if (boundSql == null) { // issue #435, get the key before calculating the statement
            // 获得自增主键
            generateKeys(parameterObject);
            // 创建 BoundSql 对象
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }
        this.boundSql = boundSql;

        // 创建 ParameterHandler 对象
        this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        // 创建 ResultSetHandler 对象
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    }

6.6、数据库查询,获取到statementHandler之后会应用插件,实现了mybatis interceptor的类,执行

在这里插入图片描述

6.7、创建prepareStatement,prepareStatement通过datasource获取connection开启事务,设置sql参数,statementHandler执行prepareStatement,执行预编译sql。

在这里插入图片描述

在这里插入图片描述

创建connection的时候日志级别默认RR
在这里插入图片描述

6.8、statementHandler通过prepareStatement执行sql之后,获取最后结果,通过resultSetHandler进行结果集封装,处理完后将结果放入一级缓存

在这里插入图片描述

@Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        // 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
        final List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        // 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        // 获得 ResultMap 数组
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); // 校验
        while (rsw != null && resultMapCount > resultSetCount) {
            // 获得 ResultMap 对象
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 处理 ResultSet ,将结果添加到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);
            // 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
            rsw = getNextResultSet(stmt);
            // 清理
            cleanUpAfterHandlingResultSet();
            // resultSetCount ++
            resultSetCount++;
        }

        // 因为 `mappedStatement.resultSets` 只在存储过程中使用,忽略即可
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {
            while (rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                    handleResultSet(rsw, resultMap, null, parentMapping);
                }
                rsw = getNextResultSet(stmt);
                cleanUpAfterHandlingResultSet();
                resultSetCount++;
            }
        }

        // 如果是 multipleResults 单元素,则取首元素返回
        return collapseSingleResultList(multipleResults);
    }

在这里插入图片描述

总结

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis Mapper 实现动态代理的调用思路: 1. 定义 Mapper 接口,该接口可以通过注解或 XML 文件的方式定义 SQL 语句和参数映射关系。 2. 使用 MyBatisMapperFactory 创建 MapperProxyFactory 对象,该对象用于创建 Mapper 接口的代理对象。 3. 创建 MapperProxy 对象,该对象实现了 InvocationHandler 接口,用于处理代理对象方法的调用。 4. 通过 MapperProxyFactory 对象的 newInstance 方法创建 Mapper 接口的代理对象,该对象会被传入 MapperProxy 对象的构造函数中。 5. 通过代理对象调用 Mapper 接口的方法时,会被 MapperProxy 对象的 invoke 方法拦截,该方法会根据方法名和参数类型动态生成 SQL 语句,并通过 MyBatis 的 SqlSession 执行 SQL 语句。 下面是使用 Java 代码实现 MyBatis Mapper 动态代理的调用过程: 1. 定义 Mapper 接口 ```java public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(int id); } ``` 2. 创建 MapperProxyFactory 对象 ```java public class MapperFactory { private final Configuration configuration; public MapperFactory(Configuration configuration) { this.configuration = configuration; } public <T> T getMapper(Class<T> mapperInterface) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MapperProxy(configuration, mapperInterface)); } } ``` 3. 创建 MapperProxy 对象 ```java public class MapperProxy<T> implements InvocationHandler { private final SqlSession sqlSession; private final Class<T> mapperInterface; public MapperProxy(Configuration configuration, Class<T> mapperInterface) { this.sqlSession = new SqlSessionFactoryBuilder().build(configuration).openSession(); this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String statement = mapperInterface.getName() + "." + method.getName(); MappedStatement mappedStatement = sqlSession.getConfiguration().getMappedStatement(statement); if (mappedStatement == null) { throw new RuntimeException("Can't find mapped statement for " + statement); } Object result = null; switch (mappedStatement.getSqlCommandType()) { case SELECT: result = sqlSession.selectOne(statement, args); break; case INSERT: result = sqlSession.insert(statement, args); break; case UPDATE: result = sqlSession.update(statement, args); break; case DELETE: result = sqlSession.delete(statement, args); break; default: throw new RuntimeException("Unknown SqlCommandType " + mappedStatement.getSqlCommandType()); } sqlSession.commit(); return result; } } ``` 4. 创建 Mapper 接口的代理对象 ```java Configuration configuration = new Configuration(); configuration.addMapper(UserMapper.class); MapperFactory mapperFactory = new MapperFactory(configuration); UserMapper userMapper = mapperFactory.getMapper(UserMapper.class); ``` 5. 调用 Mapper 接口的方法 ```java User user = userMapper.getUserById(1); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鸽呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值