Mybatis 框架解说

缘由

Mybatis 框架为作为Java开发人员的必备工具,现在大多使用 mybatis-plus,但是mybatis框架中的运行原理和技术点还是值得一探究竟的。

简述

Mybatis官网 上面介绍的非常精炼,支持自定义SQL、存储过程以及高级映射,不过存储过程现在已经很少使用,其余两点使用频率非常的高。框架最大的特点,也是ORM最大的特点,替代开发人员编写JDBC代码、参数设置、获取结果集,开发人员只要在注解或者xml文件中编辑SQL。减轻开发人员工作量,能够在更有意义的事情上投入更多的精力。

正文

框架替开发人员做了很多事情,这些事情是如何做到的呢?本文会娓娓道来。

一个Mybatis应用的中有一个实例非常的关键,就是 SqlSessionFactory。可以从它是怎么来的、起到了什么作用两个方面来详细说说。

SqlSessionFactotry的由来

SqlSessionFactory 是一个接口类,有两个实现类 SqlSessionManager\ DefaultSqlSessionFactory,我们先把注意力放在 DefaultSqlSessionFactory 上面,它的创建则是在 SqlSessionFactoryBuilder 中,显然是通过构造者模式进行的创建,若一个对象创建的过程不是简单两行代码的事情,有必要使用构造器模式,将创建逻辑独立出来,与类的定义进行隔离。

SqlSessionFactoryBuilder 中的构建逻辑最终目的就是
new DefaultSqlSessionFactory(Configuration config),所有的构建方式最终会生成 Configuration,可以是 XMLConfigBuilder.parse(),也可以使用new Configuration。两种方式都会将开发人员对Mybatis应用的配置转换到 SqlSessionFactory 的创建过程中。

SqlSessionFactotry的使用

SqlSessionFactory 的目的就是为了创建一个SqlSession,有多个重载的接口方法 openSession(),最终会执行到 new DefaultSqlSession(configuration, executor),configuration还是熟悉的那个,Executor 是一个接口,有几个简单的实现 BatchExecutor 、ReuseExecutor、SimpleExecutor、CachingExecutor,可以根据参数执行指定,两外开发人员自定义的Interceptor也会集成到Executor实现当中,Executor 的构造需要 Configuration 和 transaction(后续单独说明)。

SqlSession接口定义了crud 和 事务相关 缓存相关 的方法。最终的执行还是会使用 Executor 实现类。比如所有的查询方法会进入到 List selectList(String statement, Object parameter, RowBounds rowBounds),statement就是开发人员指定的SQL语句标识符,调用executor之前,通过 configuration.getMappedStatement(statement) 获取到 MappedStatement 作为执行对象,参与执行过程。

Executor 执行器的query(),主要是调用 doQuery(),在调用前会创建缓存key,doQuery()中存在sqlSession 级别的缓存逻辑,缓存没有命中会进入 queryFromDatabase()。

queryFromDatabase() 方法会将执行结果放入缓存,执行逻辑是在 doQuery(ms, parameter, rowBounds, resultHandler, boundSql) 中,此方法是一个抽象方法,进入 SimpleExecutor的抽象实现中。

Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 根据开发人员定义的sql语句时指定的 StatementType 生成对应的 StatementHandler
      // 这一点也是一个扩展实现
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      // 创建 statement ,并将sql参数放入到 statement 中
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 执行查询,并映射结果集
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
// 执行查询,并映射结果集
public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
  }

以上就是mybatis应用中 sqlSessionFactory 大致执行逻辑,下面会从框架的扩展性方面说明。


扩展性

StatementHandler 扩展

其实现类 RoutingStatementHandler 采用简单工厂和装饰器的方式,根据 StatementType 枚举创建不同的实现。

    switch (ms.getStatementType()) {
      case STATEMENT:
        // sql 直接执行
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        // 使用 PreparedStatement 预处理参数,防止sql注入
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        // 使用 CallableStatement 方式,可以指定额外的回调
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

Executor 扩展

Configuration 类中可根据 ExecutorType 创建指定的 Executor 实现类

  public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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);
    }
    if (cacheEnabled) {
      // 装饰模式,增加sqlSessionFactory级别的缓存
      // 二级缓存当中的事务也需要特别处理
      executor = new CachingExecutor(executor, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

插件机制

SQL执行过程中,可以对其进行监控和拦截调用。插件的执行逻辑入口在 configuration中,ParameterHandler、ResultSetHandler、StatementHandler、Executor 获取过程中都会执行 interceptorChain.pluginAll() 方法。

interceptorChain负责收集开发人员定义的interceptor,并执行pluginAll() 对目标对象进行增强,采用动态代理的方式,将目标对象进行包装。

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public static Object wrap(Object target, Interceptor interceptor) {
    // 获取自定义的interceptor上面注解
    Map, Set> signatureMap = getSignatureMap(interceptor);
    Class type = target.getClass();
    // 获取目标对象的接口与拦截器的注解对比,匹配到的接口
    // 下面的代理对象会根据匹配到的接口创建
    Class[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          // 指定代理逻辑所在的类
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set 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);
    }
  }

Mapper 代理对象

开发过程当中总会在使用依赖注入的方式引入 Mapper 对象,开发业务功能时总会定义一些UserMapper 之类的接口,方法上面使用注解SQL或者xml文件与Mapper绑定的方式编写SQL。

本身是一个接口,却可以当作对象引入,必定是Mybatis提供了动态代理机制,将一个接口生成为一个代理对象,供其他类使用。

SqlSession 提供一个接口方法 T getMapper(Class type); 定义获取Mapper代理对象的行为,DefaultSqlSession 中使用了 configuration.getMapper(type,this); --> mapperRegistry.getMapper(type, sqlSession); 具体实现是在 mapperRegistry当中:

  public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
        // mapperProxyFactory 肯定会创建一个 mapperProxy 对象,代理对象mapper的执行 也会在 mapperProxy 当中
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  // 实现 InvocationHandler 接口
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    // MapperMethod 对象代表一个userMapper中的接口方法    
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // execute 方法中的执行逻辑最终会调用到 sqlSession 完成与数据库的交互
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

事务

开发人员可指定事务管理 TransactionFactory,不过一般都是 JdbcTransactionFactory,使用数据库事务,获取sqlSession 指定是事务隔离级别,作用在JdbcTransactionFactory 创建数据库连接时指定到连接当中。

JdbcTransactionFactory 创建的 JdbcTransaction 会参与到执行器executor的创建中,执行器中的事务方法最终会调用到 JdbcTransaction 执行事务调用。

  public void commit(boolean required) throws SQLException {
    if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
    // 清空一级缓存
    clearLocalCache();
    flushStatements();
    if (required) {
      // 事务提交
      transaction.commit();
    }
  }

  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          // 事务回滚
          transaction.rollback();
        }
      }
    }
  }

总结

Mybatis框架提供了半orm机制,开发人员可以编辑SQL,剩下的工作交给框架处理,节省开发人员精力。框架的原理进行了梳理,执行流程基本说明,篇幅有限,其中的技术细节可自行延申。

你可能感兴趣的:(后端java)