IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Mybatis工作原理分析 -> 正文阅读

[Java知识库]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,表示和数据库交互的会话,完成必要数据库增删改查功能
ExecutorMyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatementMappedStatement维护了一条<select
SqlSource负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql表示动态生成的SQL语句以及相应的参数信息
ConfigurationMyBatis所有的配置信息都维持在Configuration对象之中。

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

主要分为一下步骤

1、手动读取配置文件;

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

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文件中解析<configuration>···</configuration>中的配置信息

    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() {
        // ···有省略···
        //解析<configuration></configuration>
        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 {
          // 获得上一步从配置文件中取出<environments>信息
          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知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-23 12:12:19  更:2021-11-23 12:13:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:09:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码