2. Mybatis 中SQL 执行原理

发布时间 2024-01-09 19:33:14作者: 长城VIP

2. Mybatis 中SQL 执行原理

这里有两种方式,一种为常用的 Spring 依赖注入 Mapper 的方式。另一种为直接使用 SqlSessionTemplate 执行 Sql 的方式。

Spring 依赖注入 Mapper 的方式

Mapper 接口注入 SpringIOC 容器

  1. Spring 容器在扫描 BeanDefinition 阶段会扫描 Mapper 接口类,并生成这些类的 MapperFactoryBean 的工厂 bean 定义。
  2. Spring 容器在 createBean 阶段的时候,会根据 BeanDefintion 创建 bean。在创建完 factoryBean 的时候,会调用 factoryBean 的 getObject()方法,从 DefaultSqlSession 的 knownMapper 重获取 Mapper 接口类的 mapperProxy。
  3. 使用 MapperProxy 创建出代理类。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

public T newInstance(SqlSession sqlSession) {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}

	protected T newInstance(MapperProxy<T> mapperProxy) {
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

Mapper 类调用

在 Service 层或者 Controller 层通过注解引入 Bean,这个时候引入的 Mapper 就是上文创建的 MapperProxy。MapperProxy 的增强逻辑首先过滤掉了 Object 类中的 toString()、equal()等方法。

  1. 如果调用的是 Object 类中的方法,直接放过不代理

  2. 对于 Mapper 接口中的方法进行代理。代理前先检查 methodCache 是否缓存了该方法的 invoke 逻辑。

    1. default 方法的逻辑

    2. 非 default 方法的逻辑比较重要。

      1. 通过 PlainMethodInvoker 这个类代理了其他接口方法,代理逻辑在 MapperMethod 中。

      2. MapperMethod 是最为核心的逻辑。MapperMethod 在执行构建方法时,就会创建一个 SqlCommand 和一个 MethodSignature 方法签名。

        1. SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。

        2. 调用 execute 方法。传参为 MappedStatement 的增删改查的类型和参数

          1. 根据增删改查的类型选择不同的执行逻辑

          2. 增删改的逻辑:

            1. 解析参数得到 param,反射根据 mybatis 中参数注解解析
            2. sqlSession.insert(this.command.getName(), param) ​或者
            3. sqlSession.update(this.command.getName(), param) ​或者
            4. sqlSession.delete(this.command.getName(), param) ​或者
            5. 处理结果返回值
          3. select 语句根据返回值类型不同调用不同执行逻辑

            1. returnVoid:返回值为空,且有专门的结果类型处理器
            2. returnsMany:​this.executeForMany(sqlSession, args);
            3. returnsMap:​this.executeForMap(sqlSession, args);
            4. returnsCursor:​this.executeForCursor(sqlSession, args);
            5. returnsOne 返回一行:sqlSession.selectOne(this.command.getName(), param);
          4. flush 刷新类型的 SQL:​result = sqlSession.flushStatements();

如果调用的是 Object 类中的方法,直接放过布袋里

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 对于Object类中的方法,放过不增强,直接执行即可。
        return Object.class.equals(method.getDeclaringClass()) ? 
                method.invoke(this, args) :  
                this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}

在 MapperProxy 中有一个缓存结构 methodCache:Map<Method, MapperMethodInvoker> methodCache

增强逻辑中会先判断当前方法是否被缓存在 methodCache 中,如果没有,则创建一个放到缓存中。

 MapUtil.computeIfAbsent(this.methodCache, method, (m)->{/*创建缓存*/});

创建逻辑为:

MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
    // default方法的逻辑DefaultMethodInvoker,java8和Java9的不一样。
    if (m.isDefault()) {
        return privateLookupInMethod == null ? 
            new DefaultMethodInvoker(this.getMethodHandleJava8(method)) :
            new DefaultMethodInvoker(this.getMethodHandleJava9(method));
    } else {
        return 
            new PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
    }
});   

default 的先不用管 DefaultMethodInvoker,直接看 else 中的 PlainMethodInvoker:

创建一个 MapperMethod,然后 PlainMethodInvoker 在 invoke 方法中调用 MapperMethod 的方法 execute()。

在构造 MapperMethod 方法中,创建了一个 SqlCommand 。SqlCommand 封装了从 SqlSession 中 Config 配置中获取到的 MappedStatement。在之后的 execute 方法中执行的就是 SqlCommand 中的 Mapped Statement。

// SqlCommand 封装了从SqlSession中Config配置中获取到的MappedStatement。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
}


private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
        this.mapperMethod = mapperMethod;
    }

    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        return this.mapperMethod.execute(sqlSession, args);
    }
}

接下来就是执行 SqlSession 中的增删改查方法了。可以先看一下 SqlCommand

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    String methodName = method.getName();
    Class<?> declaringClass = method.getDeclaringClass();
    MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
    if (ms <span style="font-weight: bold;" class="mark"> null) {
        if (method.getAnnotation(Flush.class) </span> null) {
            throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
        }
  
        this.name = null;
        this.type = SqlCommandType.FLUSH;
    } else {
        this.name = ms.getId();
        this.type = ms.getSqlCommandType();
        if (this.type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + this.name);
        }
    }
}

参数转化,然后 excute Sql,封装返回值:

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch (this.command.getType()) {
            case INSERT:
                // 将args参数数组转换成方法中的注解的参数
                param = this.method.convertArgsToSqlCommandParam(args);
                // 调用DefaultSqlSession的insert方法。
                // 处理结果返回值
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                // 将args参数数组转换成方法中的注解的参数
                param = this.method.convertArgsToSqlCommandParam(args);
                // 调用DefaultSqlSession的update方法。
                // 处理结果返回值
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                // 将args参数数组转换成方法中的注解的参数
                param = this.method.convertArgsToSqlCommandParam(args);
                // 调用DefaultSqlSession的delete方法。
                // 处理结果返回值
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                // select 语句情况较多,根据返回值类型不同调用不同执行逻辑。
                // returnVoid:返回值为空,且有专门的结果类型处理器
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } else if (this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } else if (this.method.returnsCursor()) {
                    result = this.executeForCursor(sqlSession, args);
                } else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                    if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

SqlSessionTemplate 执行 Sql

在创建 SqlSession 的时候已经创建了 Executor。默认为 Simple

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor());
    }

在 Spring Boot 自动配置这篇文章中已经讲过

  1. Configuration 类中有一个属性 mappedStatements​。这是一个 HashMap
  2. 解析过后的 MappedStatement ​被添加到了 map 中

当我们的 SqlSession 在执行 sql 语句时,会先从 configuration 中拿到 sql。然后执行。

    private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        List var6;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
        } catch (Exception var10) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
        } finally {
            ErrorContext.instance().reset();
        }

        return var6;
    }

然后看一下

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

再往下一层,就是执行 JDBC 那一套了,获取链接,执行,得到 ResultSet,解析 ResultSet 映射成 JavaBean。