乐趣区

关于后端:Mybatiss-框架解说

原因

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<E> 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.<E>query(stmt, resultHandler);
    } finally {closeStatement(stmt);
    }
// 执行查问,并映射后果集
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> 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<Class<?>, Set<Method>> 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<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);
    }
  }

Mapper 代理对象

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

自身是一个接口,却能够当作对象引入,必然是 Mybatis 提供了动静代理机制,将一个接口生成为一个代理对象,供其余类应用。

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

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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,剩下的工作交给框架解决,节俭开发人员精力。框架的原理进行了梳理,执行流程根本阐明,篇幅无限,其中的技术细节可自行延申。

退出移动版