mybatis的执行原理

构建sessionFactory

以下解析的是mybatis的一个构建解析流程,在创建会话工厂和拿到具体会话到拿到具体代理对象的一个过程,其实该流程当中最为总要的是创建Configuration.class对象和解析的一个过程。最终所有的核心配置都会被封装成该对象。本文是自己在阅读源码当中的一些领悟整理成文字分享给大家,有什么讲解分析错误的地方希望大家指出。

这里是我写的一个入口程序,由该案例来深入的解析执行流程。

    public static SqlSession getSqlSession() throws IOException {
        //获得核心文件配置
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlUserMapper.xml");
        //获取session工厂对象,默认创建的是DefaultSqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //获得session会话对象,这里默认创建的是DefaultSqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }

在获取到我们的核心配置文件之后,我们会构建一个sessionFactory工厂对象来获取我们的session来进行操作。执行SqlSessionFactoryBuilder().build(resourceAsStream)方法创建一个session工厂。

  /**
   * 最终调用这个build进行一个对象创建
   * @param inputStream 这里解析的是我们的核心配置文件
   * @param environment
   * @param properties
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //构造解析配置文件parser.parse()该方法进行一个配置文件的具体解析与构造,默认创建的是DefaultSqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    }
    ...忽略不核心代码
  }

这里我们将对象构造成了XMLConfigBuilder类我们关注下它的构造方法。这里进行了核心配置类的一个初始化Configuration.class,后期我们将围绕该类进行一个全面讲解,Configuration.class是整个mybatis的核心类。里面封装了mybatis的所有数据配置。

 /**
   * 这里对流构造构造成了XPathParser对象
   * @param parser
   * @param environment
   * @param props
   */
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //在父类的构造方法创建了Configuration对象,该对象是整个mybatis的核心对象
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

上面是我讲解到的初始化XMLConfigBuilder.class做的一些事情,这里主要讲解到了Configuration的一个初始化,其实Configuration.class初始化为我们做了很多事情,一些组件的初始化都是在这个时候进行构建的。这里不做详细的讲解。这里我们关注:build(parser.parse())该方法。执行parser.parse()方法会返回一个已经绑定了整个配置文件配置的Configuration.class。全局配置文件。

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //解析配置文件,这里解析的就是我们配置文件配置的每个标签,绑定到Configuration.class当中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  /**
   * 解析配置文件
   * @param root
   */
  private void parseConfiguration(XNode root) {
    try {
      //这里我们的properties配置文件的一个解析,里面配置了我们对mybatis的一些数据源配置
      propertiesElement(root.evalNode("properties"));
      //这个配置文件配置着我们需要开启一些格外的配置,例如二级缓存,还有延迟加载和log工厂等配置,这里配置mybatis的运行规则
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //这里要说明下,在我们使用mybatis的时候都会使用自定义的一个日志框架来记录,前提是否开启这个配置,这个地方就是开启定义的日志配置
      loadCustomLogImpl(settings);
      //定义别名解析别名,这里主要解析我们定义的别名映射解析该标签
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析我们配置的拦截器,这里拦截器可以配置多个
      pluginElement(root.evalNode("plugins"));
      //这里配置的是对象工厂,我们可以自己定义一些对象的实现工厂主要争对实体的一些作用
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
        /**
         * 设置默认的配置,其中最为重要的就是一些缓存配置,这里mybatis为我们默认开启了二级缓存
         * configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
         * 这里的配置改变mybatis的运行规则
         */
      settingsElement(settings);
      // 解析我们配置的数据库的链接驱动,创建对象的数据源
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //这里配置我们的类型处理器,可以配置多个
      typeHandlerElement(root.evalNode("typeHandlers"));
      //这里就是扫描我们具体的映射sql的配置文件或映射mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这里我们看build(parser.parse())方法为我们创建一个sessionFactory工厂对象来获取相应的一个会话。这里创建的是DefaultSqlSessionFactory工厂。


 public SqlSessionFactory build(Configuration config) {
    //这里我们默认创建的是DefaultSqlSessionFactory会话工厂
    return new DefaultSqlSessionFactory(config);
  }

构建session会话

上面讲解到了构建并得到sessionFactory会话工厂对象,得到工厂对象的可以为我们得到相应的会话对象,来执行操作。构建会话也是一个繁琐的一个解析过程。

SqlSession sqlSession = sqlSessionFactory.openSession();
//这里其实是使用了一个重载的,其实在创建会话的时候可以传递参数,根据具体的参数来创建具体的会话对象
@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  /**
   * 这里就是创建相应的一个会话,默认是DefaultSqlSession
   * @param execType 创建执行器的类型
   * @param level 对应的事务管理器
   * @param autoCommit 是否默认提交事务,这里默认是不提交的
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //拿到环境配置,里面包含主要包含着数据源信息
      final Environment environment = configuration.getEnvironment();
      //获取事务工厂,这里使用的是JdbcTransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //这里默认创建的是JdbcTransaction
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据事务和执行器类别创建具体的执行器,这里使用了简单工厂模式给我们创建具体的执行器对象
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

这里我们可以关注这个方法,该方法是为我们创建相应的执行器。后续执行代理操作都需要该执行器为我们执行任务。Executor.class

 Executor executor = configuration.newExecutor(tx, execType);该方法是为我们得到相应的执行器,来执行响应操作。源码分析如下:

    /**
     * 这里使用的是简单工厂面试,创建具体的执行器
     * @param transaction
     * @param executorType
     * @return
     */
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //批处理执行器,用于将多个SQL一次性输出到数据库
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
      //可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。
      // 内部的实现是通过一个HashMap来维护Statement对象的
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    //这里我们默认是开启缓存的,XMLConfigBuilder.class的settingsElement(settings)方法当中mybatis给我们设置了一些默认的熟悉,其中就包括缓存的开启
    if (cacheEnabled) {
        //这里进行了一个装饰,内部还是使用的是我们创建的执行器,只是在我们执行之前会进行查询缓存
      executor = new CachingExecutor(executor);
    }
    //这里就是我们为什么会被拦截的一个关键步骤,如果我们配置了拦截器,就会给我们的执行器进行一个代理,返回一个代理的执行器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

得到相应的执行器,构建了默认的会话器DefaultSqlSession

获取mapper代理对象

在上述获取到sessionFactory工厂为我们创建了DefaultSqlSession默认的一个会话器,得到了session会话器我们可以得到我们相应的代理类,让Executor为什么执行相应的操作。以上讲解如有错误点希望大家能够指出,谢谢大家。

以下我们根据会话器来获取我们的一个代理类,这里主要关注点就是:

mapperProxyFactory.newInstance(sqlSession);该方法使用代理工厂为我们创建一个代理类
 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   /**
     * 获取代理类
     * @param type Mapper interface class 需要代理的对象
     * @param <T>
     * @return
     */
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //获取代理工厂,这里每一个映射类都有一个代理工厂,这里可以观看在构建Configuration的时候进行一个创建
    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);
    }
  }

这里我讲解核心的代理工厂,为我们创建出代理对象,这里可以关注下MapperProxy.class类,该类为动态代理的核心类,实现于InvocationHandler.class接口,这里使用的是Java的动态代理,代理类在执行方法的时候会进行一个调用。

public class MapperProxyFactory<T> {

  //这里就是扫描到我们需要被代理的类
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }
  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }
  //这里是创建代理对象,使用jdk的动态代理
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

当前获取到的就是代理对象,这里就是执行的是我们的MapperProxy.class的invoke方法进行一个代理的过程。当前MapperProxy.class内部维护一个内部类PlainMethodInvoker.class类对象,在调用。

  @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 {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

在调用method.invoke(this, args)方法时会调用内部维护的PlainMethodInvoker.class的incoke方法

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

走到这一步就开始进行一个准备执行方法级别的数据查询了。以上有什么讲解分析不对的地方希望大家能够指出,谢谢各位大佬的意见。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值