Mybatis Mapper生成原理

5 10 月, 2022 163点热度 0人点赞 1条评论

在前面的章节中我们探讨了configuration类型的加载过程,过程执行还是很清晰的,今天这篇文章我们主要从源码的角度探讨mapper的工作原理,更深入一次的了解mybatis框架,也为后面我们深入了解在mybatis上扩展的框架打下基础。

SqlSessionFactory

这里又再一次回到了这个类上面,在正式使用Mybatis时,这个类也是非常重要的。在前面初始化的文章中,该类中主要包含了Configuration类的依赖,因此我们再次回顾一下该类是如何使用,代码片段如下:

public void add(User user) {
        if (user == null) {
            return;
        }

        try (SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            userMapper.add(user);
            sqlSession.commit();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

从上面的使用中可以看出,在SqlSessionFactory使用的时候,主要使用该类创建SqlSession类,而SqlSession类维持了操作数据库,事务管理等事项,因此我们看看openSession()做了哪些事情。

openSession()

该方法主要是获取SqlSession对象,我们具体查看一下如何创建SqlSession对象:

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);
      // 创建执行器
      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();
    }
  }

源码的代码是比较简单,主要包含了一下步骤:

  • 创建事务工厂(TransactionFactory):事务工厂的主要作用在于创建事务管理对象Trasaction。在Mybatis中,TrasactionFactory默认有两种类型:
    • JDBC: 该类型对应的JdbcTransactionFactory,该类型的事务对应着JdbcTrasaction事务管理对象
    • MANAGED: 该类型对应的ManagedTransactionFactory,同时创建的事务管理对象为ManagedTrasaction
  • 创建Executor: Executor对象主要负责操作数据库的具体实现,包含执行查询、缓存管理、事务提交、回滚等。Excecutor类型也分为很多类,可以根据需要使用。
    • BATCH: 对应着BatchExecutor
    • REUSE: 对应着ReuseExecutor
    • SIMPLE: 对应着SimpleExecutor, 该类型也是mybatis中的默认实现
    • 另外一种类型为CacheExecutor, 该类型主要为在开启缓存的时候使用
  • 创建SqlSession, 在创建时默认使用DefaultSqlSession来创建,其中关联了Configuraion以及Executor对象。因此我理解为SqlSession是更抽象,真正的操作数据库其实都是通过Executor来实现。

TransactionFactory

事务工厂主要目的在于创建Trasaction对象,不同类型创建其实都大同小异,只是在具体的事务管理的时候会存在差别。我们这里主要看JdbcTrasactionFactory对象的代码.

public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

这个工厂类提供了非常简单的实现,都是直接创建JdbcTrasaction对象即可。

Executor

Exectur的创建并不是由工厂类完成的,而是由Configuration#newExecutor实现,这个类从类的定义看主要定义了操作数据库和事务管理的方法,因此他和事务管理的trasaction本身就是一种组合关系。

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

Executor中大多都是处于基础功能的实现,包括了数据库的查询、缓存管理、事务提交等,但是可以看到的是,在Executor中并没有与业务关联的部分,因此Executor更多的是对数据库层面的抽象,更多的业务抽象都放在了SqlSession中实现。

newExecutor()

接下来就直接看一下在Configuration中如何创建Executor对象。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 这里确认执行类型,默认为SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 创建BatchExecutor
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      // 创建ReuseExecutor
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 创建SimpleExecutor
      executor = new SimpleExecutor(this, transaction);
    }
    // 判断是否开启缓存,如果开启,则创建CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 设置interceptor内容
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

创建这里使用了简单工厂模式, 根据不同的类型创建不同的Executor即可。不同的是,当使用缓存的时候,使用CachingExecutor来做了一层代理,因此这里可以理解为代理模式,只不过是静态代理罢了。在创建时,同时也设计到了Interceptor的设置,这里是将Interceptor与具体的Executor进行关联设置,我们具体看下都做了写什么事情。

pluginAll

该方法主要是为目标方法设置拦截器,该方法调用通过InterceptorChain来完成,这是一个调用链模式实现的类,来看下该类主要做了什么事情

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

因为InterceptorChain类维护了mybatis中所有的Interceptor的列表,因此这pluginAll方法中分别调用Interceptorplugin方法。

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

从这里看出,plugin方式是在接口中定义的默认方法,包含了具体的默认实现。都是通过Plugin.wrap()方法来实现。

Plugin.wrap()

这里是设置目标对象target与Inteceptor关系的地方,我们看下具体怎么样做的关联:

public static Object wrap(Object target, Interceptor interceptor) {
  // 该行主要是从Intercetor中获取@Intercepts注解中的所有内容,
  // 并返回一个map关系
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  
  // 目标对象的类型
  Class<?> type = target.getClass();
  // 获取目标对象target的所有实现的接口
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  // 如果包含了接口, 则使用代理的方式进行代理
  if (interfaces.length > 0) {
    // 创建代理对象
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

对象target与Interceptor之间的关系主要通过JDK自身的动态代理实现的,不过代理的前提是,target对象一定需要实现接口,我们知道,在JDK动态代理中,最终的方法的实现都是通过InvocationHandler来实现的,在这里最终的实现都是通过Plugin对象实现,我们简单看下Plugin的处理逻辑。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 判断是否定义方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 如果有拦截对应的方法,则执行拦截器
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // 否则就直接执行方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

这里的定义其实很简单,因为在解析的时候,会通过Interceptor上的@Intercets注解来定义拦截的方法,因此这里只需要判断执行的方法是否在拦截范围内即可。

获取Mapper对象

在上面的流程中,我们知道了SqlSession对象是如何被创建的,以及各个重要类之间的关系。当我们拿到SqlSession时候,就需要创建Mapper对象,然后通过Mapper对象来实现对数据库的操作。因此我们主要探讨Mapper的生成、使用过程。

getMapper()

通过源码可以得知,mybatis默认使用的是DefaultSqlSession类,因此我们看下getMapper()方法的代码.

@Override
 public <T> T getMapper(Class<T> type) {
   return configuration.getMapper(type, this);
 }

获取Mapper对象最终是通过Configuration类型来实现的,这是因为我们mapper的所有定义都是放在Configuration配置类中,需要从Configuration中获取mapper完整的定义。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     // 从MapperRegistry中获取
     return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegistry#getMapper()

在前面源码分析中,我们知道MapperRegistry类型存储了Mapper配置的所有内容,包括了类型、sql片段、操作sql等。类型主要来自于两个方面:

  • 如果是xml配置,则使用namespace作为类型加载并作为映射关系的key
  • 如果通过类型载入,则使用当前类型
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根据类型获取mapper映射的factory工厂类
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // 如果没有映射,则跑出异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 创建mapper对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

这里获取mapper首先要找到对应的mapper配置类,因此从map中获取定义对象MapperProxyFactory即可。创建mapper对象也是通过MapperProxyFactory#newInstance()方法完成

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

  public T newInstance(SqlSession sqlSession) {
    // 获取mapper代理对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 创建mapper
    return newInstance(mapperProxy);
  }

从这里我们知道mapper本身也是代理对象,这里也是使用的JDK的动态代理实现。

因为我们定义mapper的时候,本身就是接口的定义,因此这种场景本身使用JDK动态代理是最简单的

JDK动态代理本身执行方法的时候是需要InvocationHandler来执行具体的方法的,这里的实现类是MapperProxy实现的,我们看下MapperProxy如何处理具体的方法执行的。

MapperProxy

代理类执行时,会执行到invoke()方法,这里我们看下MapperProxy#invoke方法执行逻辑:

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 如果是Object方法,直接执行
      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);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        // 如果是接口默认方法
        if (m.isDefault()) {
          try {
            // 不同版本方法的执行逻辑
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          // 否则直接执行方法
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
违章点赞

如果文章有帮助到你,请问文章点赞~~

专注着

一个奋斗在编程路上的小伙

文章评论