Mybatis工作原理分析

Mybatis工作原理分析

myBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述Mybatis的工作原理,以及结合一次select查询的实例,查看myBatis源码,探究其实现。

一、Mybatis工作原理简介

1.1 结合代码简单讲解

public class MybatisTest {
    public static void main(String[] args) throws IOException {
        // 获得mybatis中的全局配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("static/mybatis-config.xml");
        // 创建SqlSessionFactoryBuilder,并通过build(inputStream)方法解析出配置信息,并保存在SqlSessionfactory中。
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = sqlSessionFactoryBuilder.build(resourceAsStream);
        // 通过factory获得sqlSession对象,同时默认打开事务
        SqlSession sqlSession = factory.openSession();
        // 通过处理类从sqlSession中获得mapper。
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //通过调用接口中的api,内部最终以statementId为标志,找寻出对应的sql字符串,最终底层调用jdbc执行相应的sql
        Long userSize = mapper.getUserSize();
        System.out.println(userSize);

    }
}

1.2 mybatis工作流程简单讲解

(1)加载配置: 存储在内存中。

  1. mybatis-config.xml,全局配置文件
  2. mapper配置文件,其中来源于两个地方,一处是xml配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置)

(2)SQL解析

  1. 当API接口层接收到调用请求时,会接收到传入SQL的statementID和入参列表。
  2. Mybatis会根据SQL的statementID以及入参列表,找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。

(3) SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。

(4)结果映射: 将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

1.3 Mybatis中主要的核心部件

核心部件 部件职责
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

二、简单查询中Mybatis工作原理分析

主要分为一下步骤

1、手动读取配置文件;

2、build(InputStream)方法中从Xml文件中解析···中的配置信息

3、获取SqlSession对象

4、通过class对象获得sqlSession中保存的相应mapper

5、通过实例化的mapper对象,就可以使用MapperProxy调用invoke方法执行sql

其中涉及到:

5.1 通过statementId获得对应的查询信息

5.2 通过不同的查询类型(增、删、改、查),执行相应的方法【示例为查询】

5.1 对于不同的返回类型执行相应的方法

5.3 动态生成sql

5.4 将参数中的动态变量与sql脚本进行绑定

5.5 执行sql

5.6 使用resultHandler处理jdbc返回的结果集

一下为代码层面解析

  1. 手动读取配置文件

    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    InputStream resourceAsStream = Resources.getResourceAsStream("static/mybatis-config.xml");
    SqlSessionFactory factory = sqlSessionFactoryBuilder.build(resourceAsStream);
    
  2. build(InputStream)方法中从Xml文件中解析···中的配置信息

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    	// ···有省略···
        // 获得Xml文件内容信息
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 使用parser.parse()方法对xml文件进行解析
        return build(parser.parse());
        //···有省略···
      }
    
    //以下为parper.parse()详细内容
    public Configuration parse() {
        // ···有省略···
        //解析
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
        // ···有省略···
    }
    
    // 以下为parseConfiguration()详细内容
    private void parseConfiguration(XNode root) {
        try {
          // issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          // 解析数据库配置信息,以及事务配置
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          //通过mappers配置信息获得包名,xml文件名,接口名,进一步解析mapper文件、mapper接口文件
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    
    // 以下为mapperElement()方法详细内容
    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            // 读取package信息
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              // 通过package信息读取将并解析相应package中的mapper接口,并添加到对象中的MAP,knownMappers中
              configuration.addMappers(mapperPackage);
            } else {
              // 记录相关的mapper信息,并且对于每种情况下的mapper进行解析,并添加到对象中的MAP,knownMappers中
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
                  XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                  mapperParser.parse();
                }
              } else if (resource == null && url != null && mapperClass == null) {
                ErrorContext.instance().resource(url);
                try(InputStream inputStream = Resources.getUrlAsStream(url)){
                  XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                  mapperParser.parse();
                }
              } else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }
    
  3. 获取SqlSession对象

    // 通过SqlSessionFactory获取SqlSession对象
    SqlSession sqlSession = factory.openSession();
    
    // 以defaultSqlSessionFactory为例,最终调用openSessionFromDataSource()方法
    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);
          // 配置MyBatis执行器
          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();
        }
      }
    
    
    
  4. 通过class对象获得sqlSession中保存的相应mapper

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
    //其中最终会调用到SqlSession中,并从configration中进行读取mapper对象
    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }
    
    // 之前解析mybatis-config.xml文件中就将mapper对象存入到knowMappers对象中,通过类名作为key,MapperProxyFactory作为value,最后通过动态代理工厂生成示例
    Map<Class<?>, MapperProxyFactory<?>> configuration.mapperRegistry.knownMappers
    // 获得MapperProxy对象后,联合configration对象中的sqlsession创建mapper对象
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
    
  5. 通过实例化的mapper对象,就可以使用MapperProxy调用invoke方法执行sql

    @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 if (method.isDefault()) {
                if (privateLookupInMethod == null) {
                    return invokeDefaultMethodJava8(proxy, method, args);
                } else {
                    return invokeDefaultMethodJava9(proxy, method, args);
                }
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    
    //对于不同类型的sql脚本执行对应的操作,这里为SELECT操作
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            // ···省略···
          }
          case SELECT:
            // 通过返回类型确定Handler,执行相应的方法
            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);
              // 通过接口名确定sql,并传入参数列表,执行查询
              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;
      }
    
    // 执行selectList并传入statementId以及参数列表
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        // 执行sql,返回如果是null,或者多条数据,将会抛出错误
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
    
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 通过statementId获得mapperStatement对象
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    
    // 通过确定的mapperStatement对象以及参数列表执行query操作 
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获得sql语句
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 确定sql,参数列表,返回值等信息
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    
    // ···省略部分调用堆栈
    
    // query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)最终调用方法
    @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          // 通过configuration获得StatementHandler,负责操作 Statement 对象与数据库进行交流
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          // 参数信息set到statement中
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
    // stmt = prepareStatement(handler, ms.getStatementLog());最终调用方法。将参数值set到sql中的对应参数列表中
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              Object value;
              String propertyName = parameterMapping.getProperty();
              if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                value = boundSql.getAdditionalParameter(propertyName);
              } else if (parameterObject == null) {
                value = null;
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
              } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
              }
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    
    //handler.query(stmt, resultHandler)最终调用方法
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // 执行已经处理后的PreparedStatement
        ps.execute();
        // 对执行结果进行处理,解析为resultType对应的数据类型,并返回
        return resultSetHandler.handleResultSets(ps);
    }
    
    

三、小结

所谓ORM框架,其实都是在JDBC上封装了一层,底层用的都是JDBC的代码。

MyBatis对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。这将我们的java代码与sql脚本解耦,通过Mybaits进行操作,就可以直接映射为实体的对象,减少了代码开发量。

注:

参考博文:

《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

MyBatis原理分析

同时参考部分网上资源

你可能感兴趣的:(学习随笔,java,Mybaits,Mybatis工作原理)