Mybatis源码解析-SqlSession执行过程

321 阅读8分钟

使用Mybatis执行数据库操作,首先要获取SqlSession,通过它进一步获取Mapper接口代理对象,最后通过代理对象发起数据库操作

这是使用Mybatis进行数据库操作的一个Demo,通过构建DataSourceTransactionFactoryEnvironmentConfiguration并将它们组装在一起获得SqlSessionFactory,此后就可以通过它获取SqlSession

sqlSessionFactory.openSession();通过会话工厂开启一个会话

@Test
public void readerTest() throws IOException {
    PageUtil.setPagingParam(1,11);
    //得到一个Reader处理类(字符流)
    try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/mytest/mybatis-config.xml")) {
        // environment:数据源ID 在mybatis-config.xml <environments> 标签里面配置的 id 为development的数据源信息(非必传)
        // properties :配置文件信息。一般是数据库配置信息。可以在这里进行配置 也可以在mybatis-config.xml 通过<properties> 标签进行引入properties配置文件
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader,"development", Resources.getResourceAsProperties("org/apache/ibatis/mytest/db.properties"));
    }
    SqlSession sqlSession = sqlSessionFactory.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    mapper.insertStudent(new StudentDto(11, "12", 24, Arrays.asList(1, 2, 3, 4, 5)));
    sqlSession.commit();
    List<StudentDto> rs = mapper.getStudentsByAlias();
    System.out.println(rs.toString());
}

看下openSession里面是怎么实现的

1.得到配置的环境数据源信息

2.根据数据源信息得到一个事务工厂

3.新建一个事务

4.根据事务得到执行器(代表一个事务对应一个执行器)

5.创建默认的SqlSession对象

/**
 * 打开一个session 来自 数据源
 *
 * @param execType   执行器类型       默认ExecutorType是 ExecutorType.SIMPLE
 * @param level      事务隔离级别
 * @param autoCommit 是否自动提交事务
 *
 * @return SqlSession会话对象
 */
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    /*
        之前sqlSessionFactoryBuilder.build(InputStream inputStream)
          ===》 xMLConfigBuilder.parse()  ====》 将配置文件中的数据源和sql语句封装赋值给了Configuration对象
        所以,这边可以直接获取对象中的相关信息
     */
    Transaction tx = null;
    try {
        // 获取环境配置
        final Environment environment = configuration.getEnvironment();
        // 事务工厂
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 新建一个事务
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 拿到执行器Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        // 创建默认的SqlSession对象
        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();
    }
}

看下执行器的创建过程

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        // 批处理执行器
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        // 复用执行器
        executor = new ReuseExecutor(this, transaction);
    } else {
        // 默认执行器
        executor = new SimpleExecutor(this, transaction);
    }
    /*
            1、一级缓存默认是开启的
            2、二级缓存默认是关闭的
            3、先执行一级缓存还是二级缓存,先执行二级缓存
     */
    // cacheEnabled一级缓存默认是开启的,为true的话代表开启二级缓存
    // 二级缓存通过装饰器模式的方式加载进执行器中
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    // 重点:插件拦截器过滤链 把执行器怼到里面去
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

看下插件的植入过程

mybatis会遍历所有配置的插件来进行代理。多个插件会重复代理

/**
 * 简化动态代理创建的方法
 *
 * @param target
 * @param interceptor
 *
 * @return
 */
public static Object wrap(Object target, Interceptor interceptor) {
    // 获取自定义插件中,通过@Intercepts注解指定的方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 获取目标对象的类型
    Class<?> type = target.getClass();
    // 根据类型获取所有拦截的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        // 如果需要拦截,则用JDK动态代理生成一个代理对象
        return Proxy.newProxyInstance(
                type.getClassLoader(),
                interfaces,
                new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

看下Plugin

Plugin有三个参数

target:目标对象Executor、ParameterHandler、ResultSetHandler、StatementHandler实例 或者另一个插件代理对象。

interceptor:对应插件类加载对象

signatureMap:插件注解生效的方法

package org.apache.ibatis.plugin;
​
import org.apache.ibatis.reflection.ExceptionUtil;
​
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
​
/**
 * @author Clinton Begin
 */
public class Plugin implements InvocationHandler {
​
    /** 目标对象Executor、ParameterHandler、ResultSetHandler、StatementHandler实例 或者另一个插件代理对象*/
  /**多个插件的加载采用在target里面传入代理对象实现的*/
    private final Object target;
    /** 用户自定义拦截器实例 */
    private final Interceptor interceptor;
    /** Intercepts注解指定的方法 */
    private final Map<Class<?>, Set<Method>> signatureMap;
​
    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }
​
    /**
     * 简化动态代理创建的方法
     *
     * @param target
     * @param interceptor
     *
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        // 获取自定义插件中,通过@Intercepts注解指定的方法
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        // 获取目标对象的类型
        Class<?> type = target.getClass();
        // 根据类型获取所有拦截的接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            // 如果需要拦截,则用JDK动态代理生成一个代理对象
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            // 如果是@Intercepts注解指定的方法,就调用拦截的逻辑
            if (methods != null && methods.contains(method)) {
                // 将目标方法信息封装成Invocation给拦截器的intercept()方法使用
                return interceptor.intercept(new Invocation(target, method, args));
            }
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }
​
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        // 获取@Intercepts注解信息
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // issue #251
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        // 获取@Signature注解信息
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
            // 把所有@Signature指定拦截的组件、方法添加到map中去
            Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }
​
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[0]);
    }
​
}

可以看出,创建sqlsession经过了以下几个主要步骤:

  • 从配置中获取Environment;
  • 从Environment中取得DataSource;
  • 从Environment中取得TransactionFactory;
  • 从DataSource里获取数据库连接对象Connection;
  • 在取得的数据库连接上创建事务对象Transaction;
  • 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
  • 创建sqlsession对象。

Mapper的获取和执行

通过之前的步骤咱们已经得到了一个SqlSession会话对象了。这里可以通过SqlSession来进行增删改查以及事务控制操作

还是从一个例子来看Mapper下面的sql的执行过程

@Test
public void readerTest() throws IOException {
    PageUtil.setPagingParam(1,11);
    //得到一个Reader处理类(字符流)
    try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/mytest/mybatis-config.xml")) {
        // environment:数据源ID 在mybatis-config.xml <environments> 标签里面配置的 id 为development的数据源信息(非必传)
        // properties :配置文件信息。一般是数据库配置信息。可以在这里进行配置 也可以在mybatis-config.xml 通过<properties> 标签进行引入properties配置文件
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader,"development", Resources.getResourceAsProperties("org/apache/ibatis/mytest/db.properties"));
    }
    SqlSession sqlSession = sqlSessionFactory.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    mapper.insertStudent(new StudentDto(11, "123", 24, Arrays.asList(1, 2, 3, 4, 5)));
    sqlSession.commit();
    List<StudentDto> rs = mapper.getStudentsByAlias();
    System.out.println(rs.toString());
}

得到SqlSession后从SqlSession中获得咱们自己定义的Mapper接口过程

直接去configuration中找

 /**
  * 直接去configuration中找
  */
 @Override
 public <T> T getMapper(Class<T> type) {
   return configuration.<T>getMapper(type, this);
 }

SqlSession把包袱甩给了Configuration, 接下来就看看Configuration。源码如下:

/**
  * 烫手的山芋,俺不要,你找mapperRegistry去要
  * @param type
  * @param sqlSession
  * @return
  */
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   return mapperRegistry.getMapper(type, sqlSession);
 }

Configuration不要这烫手的山芋,接着甩给了MapperRegistry, 那咱看看MapperRegistry。 源码如下:

/**
  * 烂活净让我来做了,没法了,下面没人了,我不做谁来做
  * @param type
  * @param sqlSession
  * @return
  */
 @SuppressWarnings("unchecked")
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   //能偷懒的就偷懒,俺把粗活交给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创建出一个Mapper的代理类来执行我们的Sql语句

 /**
  * 别人虐我千百遍,我待别人如初恋
  * @param mapperProxy
  * @return
  */
 @SuppressWarnings("unchecked")
 protected T newInstance(MapperProxy<T> mapperProxy) {
   //动态代理我们写的dao接口
   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }
 
 public T newInstance(SqlSession sqlSession) {
   final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
   return newInstance(mapperProxy);
 }
​

这样的话我们就得到了一个Mapper接口 然后去调用他的方法

MapperProxy Mapper代理类的invoke方法

/**
 * 核心方法invoke,创建代理类
 *
 * @param proxy
 * @param method
 * @param args
 *
 * @return
 *
 * @throws Throwable
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            // 排除掉Object继承的方法
            return method.invoke(this, args);
        } else {
            // 其他的 通过调用缓存中的方法 反射来处理,最终调用的是MapperMethod的execute()方法
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}
​

通过调用缓存中的方法 反射来处理,最终调用的是MapperMethod的execute()方法

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
        // It should be removed once the fix is backported to Java 8 or
        // MyBatis drops Java 8 support. See gh-1929
        // 这里对应https://zhuanlan.zhihu.com/p/364340936这篇文章
        // 具体的原因是jdk8 computeIfAbsent有问题,https://bugs.openjdk.java.net/browse/JDK-8161372这里说明在jdk9已解决
        // 上面英文注释说下面的代码只是为了临时解决jdk8的情况,mybatis以后可能会弃用jdk8

        // 缓存中拿到就直接返回
        MapperMethodInvoker invoker = methodCache.get(method);
        if (invoker != null) {
            return invoker;
        }

        // 如果缓存拿到为null,添加一个
        return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {
                // 这里判断一下 是不是接口上的default方法
                try {
                    if (privateLookupInMethod == null) {
                        // jdk8的实现方法
                        return new DefaultMethodInvoker(getMethodHandleJava8(method));
                    } else {
                        // jdk9+的实现方法
                        return new DefaultMethodInvoker(getMethodHandleJava9(method));
                    }
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                        | NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            } else {
                // 一般都是进这里,新建一个MapperMethod对象来处理,主要是创建一个SqlCommand对象和一个MethodSignature对象
                // SqlCommand:用来获取SQL语句的类型、Mapper的Id信息等
                // MethodSignature:用于获取方法的签名信息,例如入参名、注解信息等
                return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

MapperMethod.execute()

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 这个command就是SqlCommand,解析出来的SQL信息
    switch (command.getType()) {
        // 插入语句
        case INSERT: {
            // 获取入参
            Object param = method.convertArgsToSqlCommandParam(args);
            // 调用SqlSession的insert方法,然后用rowCountResult()方法来统计行数
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        // 更新语句
        case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            // 调用SqlSession的update方法,然后用rowCountResult()方法来统计行数
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        // 删除语句
        case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            // 调用SqlSession的delete方法,然后用rowCountResult()方法来统计行数
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        // 查询语句
        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);
                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());
    }
    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;
}

可以看到不论是插入还是查询 最后调用的方法都是SqlSession中的 select update insert delete等方法。所以所有Mapper层面的最终调用都是通过SqlSession。而SqlSession又是通过Executor来执行的。后面会通过Executor部分的源码来进行分析